This workflow follows the Chainllm → Gmail recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "01-api-lire-mails-du-jour",
"nodes": [
{
"parameters": {
"path": "mails-today",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
544,
352
],
"id": "d4ceb5f3-fc91-4731-8e42-7d9c7684e00e",
"name": "Webhook"
},
{
"parameters": {
"jsCode": "// Cr\u00e9e une date correspondant \u00e0 maintenant.\nconst now = new Date()\n\n// Cr\u00e9e une copie de la date actuelle.\nconst tomorrow = new Date(now)\n\n// Ajoute un jour pour obtenir la date de demain.\ntomorrow.setDate(now.getDate() + 1)\n\n// Cr\u00e9e une fonction qui transforme une date au format Gmail.\nfunction formatForGmail(date) {\n // R\u00e9cup\u00e8re l'ann\u00e9e.\n const year = date.getFullYear()\n\n // R\u00e9cup\u00e8re le mois avec deux chiffres.\n const month = String(date.getMonth() + 1).padStart(2, '0')\n\n // R\u00e9cup\u00e8re le jour avec deux chiffres.\n const day = String(date.getDate()).padStart(2, '0')\n\n // Retourne la date au format attendu par Gmail.\n return `${year}/${month}/${day}`\n}\n\n// Cr\u00e9e une fonction qui transforme une date au format React.\nfunction formatForReact(date) {\n // R\u00e9cup\u00e8re l'ann\u00e9e.\n const year = date.getFullYear()\n\n // R\u00e9cup\u00e8re le mois avec deux chiffres.\n const month = String(date.getMonth() + 1).padStart(2, '0')\n\n // R\u00e9cup\u00e8re le jour avec deux chiffres.\n const day = String(date.getDate()).padStart(2, '0')\n\n // Retourne la date au format simple.\n return `${year}-${month}-${day}`\n}\n\n// Pr\u00e9pare la date du jour pour Gmail.\nconst todayForGmail = formatForGmail(now)\n\n// Pr\u00e9pare la date de demain pour Gmail.\nconst tomorrowForGmail = formatForGmail(tomorrow)\n\n// Pr\u00e9pare la date du jour pour React.\nconst todayForReact = formatForReact(now)\n\n// Pr\u00e9pare la recherche Gmail des mails re\u00e7us aujourd'hui.\nconst searchQuery = `in:inbox after:${todayForGmail} before:${tomorrowForGmail}`\n\n// Retourne les donn\u00e9es au n\u0153ud suivant.\nreturn [\n {\n json: {\n today: todayForReact,\n searchQuery: searchQuery\n }\n }\n]"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
768,
352
],
"id": "deeee034-6f5f-48fa-875f-4ad82e7073e8",
"name": "Pr\u00e9parer la recherche du jour",
"alwaysOutputData": true
},
{
"parameters": {
"operation": "getAll",
"returnAll": true,
"simple": false,
"filters": {
"q": "=={{ $json.searchQuery }}"
},
"options": {}
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.2,
"position": [
992,
352
],
"id": "d8853610-4fac-4f17-a0c8-52806623e9d5",
"name": "R\u00e9cup\u00e9rer les mails du jour",
"alwaysOutputData": true,
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"messageId": "={{ $json.id }}",
"simple": false,
"options": {}
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.2,
"position": [
1888,
208
],
"id": "69d7be88-440e-4a0d-8a54-22d41a96393c",
"name": "Lire le d\u00e9tail de chaque mail",
"alwaysOutputData": true,
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re tous les e-mails re\u00e7us depuis le n\u0153ud Gmail pr\u00e9c\u00e9dent.\nconst items = $input.all()\n\n// Transforme une valeur quelconque en texte simple.\nfunction toText(value) {\n // V\u00e9rifie si la valeur est absente.\n if (value === undefined || value === null) {\n // Retourne un texte vide.\n return ''\n }\n\n // V\u00e9rifie si la valeur est d\u00e9j\u00e0 un texte.\n if (typeof value === 'string') {\n // Retourne directement le texte.\n return value\n }\n\n // V\u00e9rifie si la valeur est un nombre.\n if (typeof value === 'number') {\n // Convertit le nombre en texte.\n return String(value)\n }\n\n // V\u00e9rifie si la valeur est un tableau.\n if (Array.isArray(value)) {\n // Convertit chaque \u00e9l\u00e9ment du tableau en texte.\n return value.map((item) => toText(item)).filter(Boolean).join(', ')\n }\n\n // V\u00e9rifie si la valeur est un objet.\n if (typeof value === 'object') {\n // V\u00e9rifie si l'objet contient un nom et une adresse e-mail.\n if (value.name && value.email) {\n // Retourne le nom et l'adresse.\n return `${value.name} <${value.email}>`\n }\n\n // V\u00e9rifie si l'objet contient un nom et une adresse.\n if (value.name && value.address) {\n // Retourne le nom et l'adresse.\n return `${value.name} <${value.address}>`\n }\n\n // V\u00e9rifie si l'objet contient une valeur textuelle.\n if (value.text) {\n // Retourne le texte.\n return toText(value.text)\n }\n\n // V\u00e9rifie si l'objet contient une valeur HTML.\n if (value.html) {\n // Retourne le HTML.\n return toText(value.html)\n }\n\n // V\u00e9rifie si l'objet contient une valeur.\n if (value.value) {\n // Retourne la valeur.\n return toText(value.value)\n }\n\n // V\u00e9rifie si l'objet contient une adresse e-mail.\n if (value.email) {\n // Retourne l'adresse e-mail.\n return toText(value.email)\n }\n\n // V\u00e9rifie si l'objet contient une adresse.\n if (value.address) {\n // Retourne l'adresse.\n return toText(value.address)\n }\n\n // V\u00e9rifie si l'objet contient un body.\n if (value.body) {\n // Retourne le body.\n return toText(value.body)\n }\n\n // V\u00e9rifie si l'objet contient des donn\u00e9es.\n if (value.data) {\n // Retourne les donn\u00e9es.\n return toText(value.data)\n }\n }\n\n // Retourne un texte vide si rien n'est exploitable.\n return ''\n}\n\n// R\u00e9cup\u00e8re la premi\u00e8re valeur disponible parmi plusieurs champs possibles.\nfunction getFirstValue(object, names) {\n // Parcourt tous les noms possibles.\n for (const name of names) {\n // R\u00e9cup\u00e8re la valeur du champ courant.\n const value = toText(object?.[name])\n\n // V\u00e9rifie si la valeur existe.\n if (value) {\n // Retourne la premi\u00e8re valeur trouv\u00e9e.\n return value\n }\n }\n\n // Retourne un texte vide si rien n'est trouv\u00e9.\n return ''\n}\n\n// D\u00e9code un contenu Gmail encod\u00e9 en base64url.\nfunction decodeBase64Url(value) {\n // V\u00e9rifie si la valeur est vide.\n if (!value) {\n // Retourne un texte vide.\n return ''\n }\n\n // Remplace les caract\u00e8res base64url par des caract\u00e8res base64 classiques.\n const base64 = value.replace(/-/g, '+').replace(/_/g, '/')\n\n // D\u00e9code le contenu base64.\n return Buffer.from(base64, 'base64').toString('utf8')\n}\n\n// D\u00e9code les entit\u00e9s HTML fr\u00e9quentes.\nfunction decodeHtmlEntities(value) {\n // V\u00e9rifie si la valeur est vide.\n if (!value) {\n // Retourne un texte vide.\n return ''\n }\n\n // Remplace les entit\u00e9s HTML par des caract\u00e8res normaux.\n return value\n .replace(/ /gi, ' ')\n .replace(/‌/gi, ' ')\n .replace(/‍/gi, ' ')\n .replace(/­/gi, ' ')\n .replace(/‌/g, ' ')\n .replace(/‍/g, ' ')\n .replace(/­/g, ' ')\n .replace(/'/g, \"'\")\n .replace(/'/gi, \"'\")\n .replace(/"/gi, '\"')\n .replace(/&/gi, '&')\n .replace(/</gi, '<')\n .replace(/>/gi, '>')\n .replace(/é/gi, '\u00e9')\n .replace(/è/gi, '\u00e8')\n .replace(/ê/gi, '\u00ea')\n .replace(/à/gi, '\u00e0')\n .replace(/ç/gi, '\u00e7')\n .replace(/ô/gi, '\u00f4')\n .replace(/ù/gi, '\u00f9')\n .replace(/&#(\\d+);/g, (match, number) => String.fromCharCode(Number(number)))\n}\n\n// Nettoie un contenu HTML pour obtenir un texte lisible.\nfunction cleanHtml(value) {\n // V\u00e9rifie si la valeur est vide.\n if (!value) {\n // Retourne un texte vide.\n return ''\n }\n\n // D\u00e9code d'abord les entit\u00e9s HTML visibles comme ‌.\n const decodedFirst = decodeHtmlEntities(value)\n\n // Supprime les scripts.\n const withoutScripts = decodedFirst.replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n\n // Supprime les styles.\n const withoutStyles = withoutScripts.replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n\n // Supprime les commentaires HTML.\n const withoutComments = withoutStyles.replace(/<!--[\\s\\S]*?-->/g, ' ')\n\n // Remplace certaines balises par des espaces pour \u00e9viter de coller les phrases.\n const withSpaces = withoutComments\n .replace(/<\\/p>/gi, ' ')\n .replace(/<br\\s*\\/?>/gi, ' ')\n .replace(/<\\/div>/gi, ' ')\n .replace(/<\\/tr>/gi, ' ')\n .replace(/<\\/td>/gi, ' ')\n .replace(/<\\/li>/gi, ' ')\n\n // Supprime toutes les balises HTML restantes.\n const withoutTags = withSpaces.replace(/<[^>]*>/g, ' ')\n\n // D\u00e9code encore une fois les entit\u00e9s restantes.\n const decodedSecond = decodeHtmlEntities(withoutTags)\n\n // Supprime les vrais caract\u00e8res invisibles Unicode.\n const withoutInvisible = decodedSecond.replace(/[\\u200B-\\u200D\\uFEFF\\u00AD]/g, ' ')\n\n // Supprime les r\u00e9p\u00e9titions inutiles de la mention image.\n const withoutTooManyImages = withoutInvisible.replace(/\\[image:\\s*[^\\]]+\\]/gi, ' ')\n\n // Remplace plusieurs espaces par un seul espace.\n const cleanText = withoutTooManyImages.replace(/\\s+/g, ' ').trim()\n\n // Retourne le texte propre.\n return cleanText\n}\n\n// Nettoie le nom de l'exp\u00e9diteur.\nfunction cleanSender(value) {\n // Transforme la valeur en texte.\n const sender = toText(value)\n\n // V\u00e9rifie si l'exp\u00e9diteur est vide.\n if (!sender) {\n // Retourne une valeur par d\u00e9faut.\n return 'Exp\u00e9diteur inconnu'\n }\n\n // Supprime les guillemets autour du nom de l'exp\u00e9diteur.\n const cleaned = sender\n .replace(/^\"([^\"]+)\"\\s*</, '$1 <')\n .replace(/\\\\\"/g, '\"')\n .trim()\n\n // Retourne l'exp\u00e9diteur nettoy\u00e9.\n return cleaned || 'Exp\u00e9diteur inconnu'\n}\n\n// R\u00e9cup\u00e8re un header Gmail.\nfunction getHeader(mail, headerName) {\n // Met le nom recherch\u00e9 en minuscules.\n const wantedName = headerName.toLowerCase()\n\n // R\u00e9cup\u00e8re les headers dans payload.headers.\n const payloadHeaders = mail.payload?.headers || []\n\n // V\u00e9rifie si payload.headers est un tableau.\n if (Array.isArray(payloadHeaders)) {\n // Cherche le header demand\u00e9.\n const foundHeader = payloadHeaders.find((header) => header.name?.toLowerCase() === wantedName)\n\n // V\u00e9rifie si le header existe.\n if (foundHeader?.value) {\n // Retourne la valeur du header.\n return foundHeader.value\n }\n }\n\n // R\u00e9cup\u00e8re les headers directs.\n const directHeaders = mail.headers || {}\n\n // V\u00e9rifie si headers est un tableau.\n if (Array.isArray(directHeaders)) {\n // Cherche le header demand\u00e9.\n const foundHeader = directHeaders.find((header) => header.name?.toLowerCase() === wantedName)\n\n // V\u00e9rifie si le header existe.\n if (foundHeader?.value) {\n // Retourne la valeur du header.\n return foundHeader.value\n }\n }\n\n // V\u00e9rifie si headers est un objet.\n if (typeof directHeaders === 'object' && directHeaders !== null) {\n // Cherche le header avec le nom exact.\n const exactValue = directHeaders[headerName]\n\n // V\u00e9rifie si la valeur exacte existe.\n if (exactValue) {\n // Retourne la valeur exacte.\n return toText(exactValue)\n }\n\n // Cherche le header avec le nom en minuscules.\n const lowerValue = directHeaders[wantedName]\n\n // V\u00e9rifie si la valeur en minuscules existe.\n if (lowerValue) {\n // Retourne la valeur en minuscules.\n return toText(lowerValue)\n }\n\n // Cherche une cl\u00e9 qui correspond au header demand\u00e9.\n const matchingKey = Object.keys(directHeaders).find((key) => key.toLowerCase() === wantedName)\n\n // V\u00e9rifie si une cl\u00e9 correspond.\n if (matchingKey) {\n // Retourne la valeur trouv\u00e9e.\n return toText(directHeaders[matchingKey])\n }\n }\n\n // Retourne un texte vide si rien n'est trouv\u00e9.\n return ''\n}\n\n// R\u00e9cup\u00e8re l'exp\u00e9diteur du mail.\nfunction getSender(mail) {\n // Cherche l'exp\u00e9diteur dans les champs directs.\n const directSender = getFirstValue(mail, [\n 'from',\n 'From',\n 'sender',\n 'Sender',\n 'expediteur',\n 'replyTo',\n 'reply_to'\n ])\n\n // V\u00e9rifie si un exp\u00e9diteur direct existe.\n if (directSender) {\n // Retourne l'exp\u00e9diteur nettoy\u00e9.\n return cleanSender(directSender)\n }\n\n // Cherche l'exp\u00e9diteur dans le header From.\n const fromHeader = getHeader(mail, 'From')\n\n // V\u00e9rifie si le header From existe.\n if (fromHeader) {\n // Retourne l'exp\u00e9diteur nettoy\u00e9.\n return cleanSender(fromHeader)\n }\n\n // Cherche l'exp\u00e9diteur dans le header Sender.\n const senderHeader = getHeader(mail, 'Sender')\n\n // V\u00e9rifie si le header Sender existe.\n if (senderHeader) {\n // Retourne l'exp\u00e9diteur nettoy\u00e9.\n return cleanSender(senderHeader)\n }\n\n // Cherche l'exp\u00e9diteur dans le header Reply-To.\n const replyToHeader = getHeader(mail, 'Reply-To')\n\n // V\u00e9rifie si Reply-To existe.\n if (replyToHeader) {\n // Retourne l'exp\u00e9diteur nettoy\u00e9.\n return cleanSender(replyToHeader)\n }\n\n // Retourne une valeur par d\u00e9faut.\n return 'Exp\u00e9diteur inconnu'\n}\n\n// R\u00e9cup\u00e8re l'objet du mail.\nfunction getSubject(mail) {\n // Cherche l'objet dans les champs directs.\n const directSubject = getFirstValue(mail, ['subject', 'Subject', 'objet'])\n\n // V\u00e9rifie si un objet direct existe.\n if (directSubject) {\n // Retourne l'objet direct.\n return cleanHtml(directSubject)\n }\n\n // Cherche l'objet dans les headers.\n const headerSubject = getHeader(mail, 'Subject')\n\n // V\u00e9rifie si un objet existe dans les headers.\n if (headerSubject) {\n // Retourne l'objet trouv\u00e9.\n return cleanHtml(headerSubject)\n }\n\n // Retourne une valeur par d\u00e9faut.\n return 'Sans objet'\n}\n\n// Lit toutes les parties disponibles dans le payload Gmail.\nfunction getBodyFromPayload(payload) {\n // Cr\u00e9e une liste pour les textes simples.\n const plainTexts = []\n\n // Cr\u00e9e une liste pour les contenus HTML.\n const htmlTexts = []\n\n // Cr\u00e9e une fonction interne pour parcourir le payload.\n function readPart(part) {\n // V\u00e9rifie si la partie existe.\n if (!part) {\n // Arr\u00eate la fonction.\n return\n }\n\n // V\u00e9rifie si cette partie contient des donn\u00e9es.\n if (part.body?.data) {\n // D\u00e9code les donn\u00e9es.\n const decoded = decodeBase64Url(part.body.data)\n\n // V\u00e9rifie si le contenu d\u00e9cod\u00e9 existe.\n if (decoded) {\n // V\u00e9rifie si la partie est du texte simple.\n if (part.mimeType === 'text/plain') {\n // Ajoute le texte simple.\n plainTexts.push(decoded)\n } else {\n // Ajoute le contenu HTML ou g\u00e9n\u00e9ral.\n htmlTexts.push(decoded)\n }\n }\n }\n\n // R\u00e9cup\u00e8re les sous-parties.\n const subParts = part.parts || []\n\n // Parcourt toutes les sous-parties.\n for (const subPart of subParts) {\n // Lit chaque sous-partie.\n readPart(subPart)\n }\n }\n\n // Lance la lecture du payload complet.\n readPart(payload)\n\n // Fusionne les textes simples.\n const plainContent = plainTexts.join(' ')\n\n // Fusionne les contenus HTML.\n const htmlContent = htmlTexts.join(' ')\n\n // Nettoie le texte simple.\n const cleanedPlain = cleanHtml(plainContent)\n\n // Nettoie le HTML.\n const cleanedHtml = cleanHtml(htmlContent)\n\n // V\u00e9rifie si le texte simple est plus complet.\n if (cleanedPlain.length >= cleanedHtml.length) {\n // Retourne le texte simple.\n return cleanedPlain\n }\n\n // Retourne le texte HTML nettoy\u00e9.\n return cleanedHtml\n}\n\n// Formate la date du mail.\nfunction formatMailDate(mail) {\n // R\u00e9cup\u00e8re la date directe.\n const directDate = getFirstValue(mail, ['date', 'Date', 'receivedDate'])\n\n // R\u00e9cup\u00e8re la date depuis les headers.\n const headerDate = getHeader(mail, 'Date')\n\n // R\u00e9cup\u00e8re la date interne Gmail.\n const internalDate = mail.internalDate\n\n // V\u00e9rifie si internalDate existe et ressemble \u00e0 un nombre.\n if (internalDate && !isNaN(Number(internalDate))) {\n // Transforme internalDate en date JavaScript.\n const date = new Date(Number(internalDate))\n\n // V\u00e9rifie si la date est valide.\n if (!isNaN(date.getTime())) {\n // Retourne la date en fran\u00e7ais.\n return date.toLocaleString('fr-FR')\n }\n }\n\n // Choisit la meilleure date texte disponible.\n const rawDate = directDate || headerDate\n\n // V\u00e9rifie si une date texte existe.\n if (rawDate) {\n // Transforme la date texte en date JavaScript.\n const date = new Date(rawDate)\n\n // V\u00e9rifie si la date est valide.\n if (!isNaN(date.getTime())) {\n // Retourne la date en fran\u00e7ais.\n return date.toLocaleString('fr-FR')\n }\n }\n\n // Retourne une valeur par d\u00e9faut.\n return 'Date inconnue'\n}\n\n// R\u00e9cup\u00e8re le contenu complet du mail.\nfunction getMailContent(mail) {\n // R\u00e9cup\u00e8re le texte direct.\n const directText = getFirstValue(mail, ['text', 'textPlain', 'plainText', 'bodyText', 'content'])\n\n // R\u00e9cup\u00e8re le HTML direct.\n const directHtml = getFirstValue(mail, ['html', 'textHtml', 'bodyHtml', 'htmlBody'])\n\n // R\u00e9cup\u00e8re le contenu document si n8n l'utilise.\n const documentContent = getFirstValue(mail, ['document'])\n\n // R\u00e9cup\u00e8re le contenu body si n8n l'utilise.\n const bodyContent = getFirstValue(mail, ['body'])\n\n // R\u00e9cup\u00e8re le contenu data si n8n l'utilise.\n const dataContent = getFirstValue(mail, ['data'])\n\n // R\u00e9cup\u00e8re le contenu depuis payload.\n const payloadContent = getBodyFromPayload(mail.payload)\n\n // R\u00e9cup\u00e8re le snippet Gmail.\n const snippet = getFirstValue(mail, ['snippet', 'preview'])\n\n // Pr\u00e9pare toutes les sources possibles.\n const sources = [\n directText,\n directHtml,\n documentContent,\n bodyContent,\n dataContent,\n payloadContent,\n snippet\n ]\n\n // Nettoie toutes les sources disponibles.\n const cleanedSources = sources.map((source) => cleanHtml(source)).filter(Boolean)\n\n // V\u00e9rifie si au moins une source existe.\n if (cleanedSources.length > 0) {\n // Trie les contenus du plus long au plus court.\n cleanedSources.sort((a, b) => b.length - a.length)\n\n // Retourne le contenu le plus long.\n return cleanedSources[0]\n }\n\n // Retourne une valeur par d\u00e9faut.\n return 'Contenu non disponible.'\n}\n\n// Cr\u00e9e la date actuelle.\nconst now = new Date()\n\n// R\u00e9cup\u00e8re l'ann\u00e9e actuelle.\nconst year = now.getFullYear()\n\n// R\u00e9cup\u00e8re le mois actuel avec deux chiffres.\nconst month = String(now.getMonth() + 1).padStart(2, '0')\n\n// R\u00e9cup\u00e8re le jour actuel avec deux chiffres.\nconst day = String(now.getDate()).padStart(2, '0')\n\n// Cr\u00e9e la date du jour au format React.\nconst today = `${year}-${month}-${day}`\n\n// Garde seulement les \u00e9l\u00e9ments qui ressemblent \u00e0 des e-mails.\nconst realItems = items.filter((item) => {\n // R\u00e9cup\u00e8re le mail courant.\n const mail = item.json\n\n // V\u00e9rifie si le mail contient des informations utiles.\n return mail.id || mail.messageId || mail.payload || mail.html || mail.text || mail.document || mail.snippet\n})\n\n// Transforme chaque mail Gmail en objet simple pour React.\nconst mails = realItems.map((item, index) => {\n // R\u00e9cup\u00e8re le mail courant.\n const mail = item.json\n\n // R\u00e9cup\u00e8re l'exp\u00e9diteur.\n const expediteur = getSender(mail)\n\n // R\u00e9cup\u00e8re l'objet.\n const objet = getSubject(mail)\n\n // R\u00e9cup\u00e8re la date de r\u00e9ception.\n const dateReception = formatMailDate(mail)\n\n // R\u00e9cup\u00e8re le contenu complet.\n const contenu = getMailContent(mail)\n\n // Cr\u00e9e un r\u00e9sum\u00e9 court pour la carte.\n const resume = contenu.length > 300 ? `${contenu.slice(0, 300)}...` : contenu\n\n // Retourne le mail dans le format attendu par React.\n return {\n // Identifiant simple pour React.\n id: index + 1,\n\n // Identifiant Gmail.\n messageId: mail.id || mail.messageId || '',\n\n // Identifiant du thread Gmail.\n threadId: mail.threadId || '',\n\n // Exp\u00e9diteur du mail.\n expediteur: expediteur,\n\n // Objet du mail.\n objet: objet,\n\n // Date de r\u00e9ception.\n dateReception: dateReception,\n\n // R\u00e9sum\u00e9 court.\n resume: resume,\n\n // Contenu complet.\n contenu: contenu,\n\n // Statut de r\u00e9ponse.\n statut: 'non r\u00e9pondu'\n }\n})\n\n// Pr\u00e9pare le r\u00e9sum\u00e9 global provisoire.\nconst resumeGlobal = `${mails.length} e-mail(s) re\u00e7u(s) aujourd\u2019hui ont \u00e9t\u00e9 r\u00e9cup\u00e9r\u00e9(s) depuis Gmail.`\n\n// Retourne les donn\u00e9es finales \u00e0 React.\nreturn [\n {\n json: {\n date: today,\n resumeGlobal: resumeGlobal,\n mails: mails\n }\n }\n]"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2112,
208
],
"id": "45c3db80-3ddb-47d6-8235-9dc8675238d8",
"name": "Structurer les donn\u00e9es pour React",
"alwaysOutputData": true
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
4032,
208
],
"id": "6ba9776a-6bf8-439b-90d9-845472233b62",
"name": "Respond to Webhook"
},
{
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re les donn\u00e9es venant du n\u0153ud IF.\nconst data = $input.first().json\n\n// R\u00e9cup\u00e8re la liste des messages.\nconst messages = data.messages || []\n\n// Transforme chaque mail en ID propre pour le n\u0153ud Gmail suivant.\nconst preparedItems = messages.map((message) => {\n // R\u00e9cup\u00e8re l'ID Gmail du message.\n const messageId = String(message.id).trim()\n\n // Retourne un item propre.\n return {\n json: {\n id: messageId,\n messageId: messageId,\n threadId: message.threadId || ''\n }\n }\n})\n\n// Retourne les IDs au n\u0153ud suivant.\nreturn preparedItems"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1664,
208
],
"id": "bf0039a0-cc24-4e9f-9758-4474fe031277",
"name": "Pr\u00e9parer les IDs des mails",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re les donn\u00e9es pr\u00e9par\u00e9es pour React.\nconst data = $input.first().json\n\n// R\u00e9cup\u00e8re la liste des mails.\nconst mails = data.mails || []\n\n// Transforme chaque mail en texte simple pour Groq.\nconst mailsText = mails.map((mail, index) => {\n // Retourne un bloc lisible pour chaque mail.\n return `\nMail ${index + 1}\nExp\u00e9diteur : ${mail.expediteur}\nObjet : ${mail.objet}\nDate : ${mail.dateReception}\nR\u00e9sum\u00e9 court : ${mail.resume}\nContenu : ${mail.contenu}\n`\n}).join('\\n--------------------\\n')\n\n// Pr\u00e9pare la consigne envoy\u00e9e \u00e0 Groq.\nconst prompt = `\nTu dois analyser les e-mails re\u00e7us aujourd'hui et produire un r\u00e9sum\u00e9 global clair.\n\nR\u00e9ponds exactement avec cette structure :\n\nR\u00e9sum\u00e9 g\u00e9n\u00e9ral :\n\u00c9cris ici un court paragraphe qui r\u00e9sume la journ\u00e9e.\n\nPoints importants :\n- Point important 1\n- Point important 2\n- Point important 3\n\nActions \u00e0 faire :\n- Action 1 s'il y en a une\n- Sinon \u00e9cris : Aucune action urgente d\u00e9tect\u00e9e.\n\nContraintes :\n- R\u00e9ponds uniquement en fran\u00e7ais.\n- Ne r\u00e9p\u00e8te pas tous les mails un par un.\n- Ne mets pas de Markdown avec des ast\u00e9risques.\n- Ne mets pas de texte comme **Mail 1**.\n- Fais une r\u00e9ponse claire, courte et facile \u00e0 lire.\n\nVoici les e-mails re\u00e7us aujourd'hui :\n\n${mailsText}\n` \n\n// Retourne les donn\u00e9es originales avec le prompt ajout\u00e9.\nreturn [\n {\n json: {\n date: data.date,\n resumeGlobal: data.resumeGlobal,\n mails: data.mails,\n prompt: prompt\n }\n }\n]"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2336,
208
],
"id": "02c0bbfb-9ee0-40b2-b748-cf9626c6482c",
"name": "Pr\u00e9parer la demande Groq",
"alwaysOutputData": true
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.prompt }}",
"messages": {
"messageValues": []
},
"batching": {}
},
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.9,
"position": [
2560,
96
],
"id": "c9a34f73-38ab-42cc-88be-87173f47e84b",
"name": "G\u00e9n\u00e9rer le r\u00e9sum\u00e9 global",
"alwaysOutputData": true
},
{
"parameters": {
"model": "llama-3.1-8b-instant",
"options": {
"maxTokensToSample": 500,
"temperature": 0.2
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatGroq",
"typeVersion": 1,
"position": [
2560,
304
],
"id": "ccc15eae-2a4b-463a-9c94-01aa18dd83ca",
"name": "Mod\u00e8le Groq",
"credentials": {
"groqApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re la r\u00e9ponse du n\u0153ud Basic LLM Chain.\nconst aiResponse = $input.first().json\n\n// R\u00e9cup\u00e8re les donn\u00e9es originales pr\u00e9par\u00e9es avant Groq.\nconst originalData = $('Pr\u00e9parer la demande Groq').first().json\n\n// Essaie de r\u00e9cup\u00e9rer le r\u00e9sum\u00e9 dans plusieurs champs possibles.\nconst generatedSummary =\n aiResponse.text ||\n aiResponse.output ||\n aiResponse.response ||\n aiResponse.result ||\n aiResponse.content ||\n aiResponse.answer ||\n aiResponse.message ||\n ''\n\n// V\u00e9rifie si Groq a vraiment g\u00e9n\u00e9r\u00e9 un r\u00e9sum\u00e9.\nconst finalSummary = generatedSummary || originalData.resumeGlobal\n\n// Retourne le JSON final attendu par React.\nreturn [\n {\n json: {\n date: originalData.date,\n resumeGlobal: finalSummary,\n mails: originalData.mails\n }\n }\n]"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2912,
208
],
"id": "fe6c5837-abae-42ad-8fd7-2d2f4b81a5ed",
"name": "Ajouter le r\u00e9sum\u00e9 Groq au JSON",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re tous les \u00e9l\u00e9ments re\u00e7us depuis le n\u0153ud Gmail pr\u00e9c\u00e9dent.\nconst items = $input.all()\n\n// Cr\u00e9e une date avec la date actuelle.\nconst now = new Date()\n\n// R\u00e9cup\u00e8re l'ann\u00e9e actuelle.\nconst year = now.getFullYear()\n\n// R\u00e9cup\u00e8re le mois actuel avec deux chiffres.\nconst month = String(now.getMonth() + 1).padStart(2, '0')\n\n// R\u00e9cup\u00e8re le jour actuel avec deux chiffres.\nconst day = String(now.getDate()).padStart(2, '0')\n\n// Cr\u00e9e la date du jour au format attendu par React.\nconst today = `${year}-${month}-${day}`\n\n// Cr\u00e9e une liste vide pour stocker les vrais mails trouv\u00e9s.\nconst messages = []\n\n// Parcourt tous les \u00e9l\u00e9ments re\u00e7us.\nfor (const item of items) {\n // R\u00e9cup\u00e8re les donn\u00e9es JSON de l'\u00e9l\u00e9ment actuel.\n const data = item.json || {}\n\n // V\u00e9rifie si l'\u00e9l\u00e9ment contient directement un vrai identifiant Gmail.\n if (data.id) {\n // Ajoute ce mail dans la liste.\n messages.push(data)\n }\n\n // V\u00e9rifie si l'\u00e9l\u00e9ment contient une liste de messages.\n if (Array.isArray(data.messages)) {\n // Ajoute tous les messages trouv\u00e9s.\n messages.push(...data.messages)\n }\n\n // V\u00e9rifie si l'\u00e9l\u00e9ment contient une liste data.\n if (Array.isArray(data.data)) {\n // Ajoute tous les \u00e9l\u00e9ments trouv\u00e9s.\n messages.push(...data.data)\n }\n\n // V\u00e9rifie si l'\u00e9l\u00e9ment contient une liste items.\n if (Array.isArray(data.items)) {\n // Ajoute tous les \u00e9l\u00e9ments trouv\u00e9s.\n messages.push(...data.items)\n }\n}\n\n// Garde uniquement les messages qui ont un ID Gmail correct.\nconst validMessages = messages.filter((message) => {\n // R\u00e9cup\u00e8re l'identifiant du message.\n const id = message.id\n\n // Refuse si l'identifiant est absent.\n if (!id) {\n return false\n }\n\n // Transforme l'identifiant en texte.\n const cleanId = String(id).trim()\n\n // Refuse les identifiants vides.\n if (!cleanId) {\n return false\n }\n\n // Refuse les valeurs invalides.\n if (cleanId === 'undefined' || cleanId === 'null' || cleanId.includes('{{')) {\n return false\n }\n\n // Accepte le message.\n return true\n})\n\n// Retourne une seule sortie claire pour le n\u0153ud IF.\nreturn [\n {\n json: {\n date: today,\n mailCount: validMessages.length,\n messages: validMessages,\n resumeGlobal: validMessages.length === 0\n ? \"Aucun e-mail re\u00e7u aujourd\u2019hui. Vous n\u2019avez aucune action \u00e0 traiter pour le moment.\"\n : `${validMessages.length} e-mail(s) re\u00e7u(s) aujourd\u2019hui ont \u00e9t\u00e9 r\u00e9cup\u00e9r\u00e9(s) depuis Gmail.`,\n mails: []\n }\n }\n]"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1216,
352
],
"id": "f5ab7366-2885-4094-80b9-6032a5ff5d37",
"name": "V\u00e9rifier s\u2019il y a des mails",
"alwaysOutputData": true
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"id": "78944045-f3e3-43ff-9e1f-17dd35245249",
"leftValue": "={{ $json.mailCount }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
1440,
352
],
"id": "570c9feb-182d-4f19-a19e-e4b64bb6e773",
"name": "Y a-t-il des mails ?",
"alwaysOutputData": false
},
{
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re les donn\u00e9es venant du n\u0153ud IF.\nconst data = $input.first().json\n\n// Retourne une r\u00e9ponse propre pour React.\nreturn [\n {\n json: {\n date: data.date,\n resumeGlobal: data.resumeGlobal,\n mails: []\n }\n }\n]"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1664,
496
],
"id": "38f90ae0-d1c6-4ae3-bf9b-9e74c74eae3d",
"name": "R\u00e9ponse aucun mail",
"alwaysOutputData": true
},
{
"parameters": {
"options": {
"responseCode": 200
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
2912,
496
],
"id": "3ec1d1f1-b96f-4b31-b0ba-bd560a132dcd",
"name": "Respond to Webhook1"
},
{
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re les donn\u00e9es finales venant du n\u0153ud pr\u00e9c\u00e9dent.\nconst finalData = $input.first().json\n\n// Transforme les donn\u00e9es finales en texte JSON bien format\u00e9.\nconst jsonText = JSON.stringify(finalData, null, 2)\n\n// Retourne les donn\u00e9es finales avec une version texte du fichier.\nreturn [\n {\n json: {\n date: finalData.date,\n resumeGlobal: finalData.resumeGlobal,\n mails: finalData.mails,\n jsonText: jsonText\n }\n }\n]"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3136,
208
],
"id": "d0e3d15f-e7db-4d47-b9a6-93d6a4b4d9a8",
"name": "Pr\u00e9parer le fichier JSON",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re les donn\u00e9es finales venant du n\u0153ud pr\u00e9c\u00e9dent.\nconst finalData = $input.first().json\n\n// Transforme les donn\u00e9es finales en texte JSON bien format\u00e9.\nconst jsonText = JSON.stringify(finalData, null, 2)\n\n// Retourne les donn\u00e9es finales avec une version texte du fichier.\nreturn [\n {\n json: {\n date: finalData.date,\n resumeGlobal: finalData.resumeGlobal,\n mails: finalData.mails,\n jsonText: jsonText\n }\n }\n]"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1888,
496
],
"id": "0abe5761-b968-4468-b350-4a5c9a94221e",
"name": "Pr\u00e9parer le fichier JSON1",
"alwaysOutputData": true
},
{
"parameters": {
"operation": "toJson",
"options": {
"encoding": "utf8",
"fileName": "mails-today.json"
}
},
"type": "n8n-nodes-base.convertToFile",
"typeVersion": 1.1,
"position": [
3360,
208
],
"id": "97def6dc-cc76-4f15-b288-45890221ede3",
"name": "Convertir en fichier JSON",
"alwaysOutputData": true
},
{
"parameters": {
"operation": "toJson",
"options": {
"encoding": "utf8",
"fileName": "mails-today.json"
}
},
"type": "n8n-nodes-base.convertToFile",
"typeVersion": 1.1,
"position": [
2112,
496
],
"id": "500a35db-de82-4ee6-88ef-32cf1c22f3f8",
"name": "Convertir en fichier JSON1",
"alwaysOutputData": true
},
{
"parameters": {
"operation": "write",
"fileName": "D:/assistant_mails/data/mails-today.json",
"options": {}
},
"type": "n8n-nodes-base.readWriteFile",
"typeVersion": 1.1,
"position": [
3584,
208
],
"id": "6b70c60f-ef85-4d1a-afb5-2eafa25ba101",
"name": "Sauvegarder mails-today.json",
"alwaysOutputData": false
},
{
"parameters": {
"operation": "write",
"fileName": "D:/assistant_mails/data/mails-today.json",
"options": {
"append": false
}
},
"type": "n8n-nodes-base.readWriteFile",
"typeVersion": 1.1,
"position": [
2336,
496
],
"id": "97e71e7c-ab9f-43e5-9a8e-283739febbd6",
"name": "Sauvegarder mails-today.json1",
"alwaysOutputData": false
},
{
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re les donn\u00e9es pr\u00e9par\u00e9es avant la conversion en fichier.\nconst finalData = $('Pr\u00e9parer le fichier JSON').first().json\n\n// Retourne uniquement les donn\u00e9es attendues par React.\nreturn [\n {\n json: {\n date: finalData.date,\n resumeGlobal: finalData.resumeGlobal,\n mails: finalData.mails\n }\n }\n]"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3808,
208
],
"id": "1f8786de-493c-4b87-9a09-4fb9a57ead94",
"name": "Retourner le JSON final",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "// R\u00e9cup\u00e8re les donn\u00e9es du n\u0153ud qui pr\u00e9pare la r\u00e9ponse quand il n'y a aucun mail.\nconst finalData = $('R\u00e9ponse aucun mail').first().json\n\n// Retourne uniquement les donn\u00e9es attendues par React.\nreturn [\n {\n json: {\n date: finalData.date,\n resumeGlobal: finalData.resumeGlobal,\n mails: finalData.mails\n }\n }\n]"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2624,
496
],
"id": "aa9ba036-f46e-42e5-a0a1-e14f3f0b213f",
"name": "Retourner le JSON final1",
"alwaysOutputData": false
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Pr\u00e9parer la recherche du jour",
"type": "main",
"index": 0
}
]
]
},
"Pr\u00e9parer la recherche du jour": {
"main": [
[
{
"node": "R\u00e9cup\u00e9rer les mails du jour",
"type": "main",
"index": 0
}
]
]
},
"R\u00e9cup\u00e9rer les mails du jour": {
"main": [
[
{
"node": "V\u00e9rifier s\u2019il y a des mails",
"type": "main",
"index": 0
}
]
]
},
"Lire le d\u00e9tail de chaque mail": {
"main": [
[
{
"node": "Structurer les donn\u00e9es pour React",
"type": "main",
"index": 0
}
]
]
},
"Structurer les donn\u00e9es pour React": {
"main": [
[
{
"node": "Pr\u00e9parer la demande Groq",
"type": "main",
"index": 0
}
]
]
},
"Pr\u00e9parer les IDs des mails": {
"main": [
[
{
"node": "Lire le d\u00e9tail de chaque mail",
"type": "main",
"index": 0
}
]
]
},
"Pr\u00e9parer la demande Groq": {
"main": [
[
{
"node": "G\u00e9n\u00e9rer le r\u00e9sum\u00e9 global",
"type": "main",
"index": 0
}
]
]
},
"G\u00e9n\u00e9rer le r\u00e9sum\u00e9 global": {
"main": [
[
{
"node": "Ajouter le r\u00e9sum\u00e9 Groq au JSON",
"type": "main",
"index": 0
}
]
]
},
"Mod\u00e8le Groq": {
"ai_languageModel": [
[
{
"node": "G\u00e9n\u00e9rer le r\u00e9sum\u00e9 global",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Ajouter le r\u00e9sum\u00e9 Groq au JSON": {
"main": [
[
{
"node": "Pr\u00e9parer le fichier JSON",
"type": "main",
"index": 0
}
]
]
},
"V\u00e9rifier s\u2019il y a des mails": {
"main": [
[
{
"node": "Y a-t-il des mails ?",
"type": "main",
"index": 0
}
]
]
},
"Y a-t-il des mails ?": {
"main": [
[
{
"node": "Pr\u00e9parer les IDs des mails",
"type": "main",
"index": 0
}
],
[
{
"node": "R\u00e9ponse aucun mail",
"type": "main",
"index": 0
}
]
]
},
"R\u00e9ponse aucun mail": {
"main": [
[
{
"node": "Pr\u00e9parer le fichier JSON1",
"type": "main",
"index": 0
}
]
]
},
"Pr\u00e9parer le fichier JSON1": {
"main": [
[
{
"node": "Convertir en fichier JSON1",
"type": "main",
"index": 0
}
]
]
},
"Pr\u00e9parer le fichier JSON": {
"main": [
[
{
"node": "Convertir en fichier JSON",
"type": "main",
"index": 0
}
]
]
},
"Convertir en fichier JSON": {
"main": [
[
{
"node": "Sauvegarder mails-today.json",
"type": "main",
"index": 0
}
]
]
},
"Convertir en fichier JSON1": {
"main": [
[
{
"node": "Sauvegarder mails-today.json1",
"type": "main",
"index": 0
}
]
]
},
"Sauvegarder mails-today.json": {
"main": [
[
{
"node": "Retourner le JSON final",
"type": "main",
"index": 0
}
]
]
},
"Sauvegarder mails-today.json1": {
"main": [
[
{
"node": "Retourner le JSON final1",
"type": "main",
"index": 0
}
]
]
},
"Retourner le JSON final1": {
"main": [
[
{
"node": "Respond to Webhook1",
"type": "main",
"index": 0
}
]
]
},
"Retourner le JSON final": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "cf60920b-05d6-4422-8f14-d61fea3fe8fe",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "pjlmNIpG080NN455",
"tags": []
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
gmailOAuth2groqApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
01-api-lire-mails-du-jour. Uses gmail, chainLlm, lmChatGroq, readWriteFile. Webhook trigger; 23 nodes.
Source: https://github.com/siewealix/assistant-mails/blob/5755e920c0839a808637757b20f168d94dffd53b/n8n/01-api-lire-mails-du-jour.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Doctor Clinic Appointment Booking System. Uses googleSheets, googleCalendar, chainLlm, lmChatGroq. Webhook trigger; 15 nodes.
✨🔪 Advanced AI Powered Document Parsing & Text Extraction with Llama Parse. Uses gmail, gmailTrigger, limit, stickyNote. Webhook trigger; 54 nodes.
Doc Processing. Uses chainLlm, lmChatGroq, googleDrive, googleSheets. Scheduled trigger; 39 nodes.
Proj2 Newsletter. Uses googleSheets, httpRequest, chainLlm, lmChatGroq. Event-driven trigger; 28 nodes.
Proj5_Claude. Uses googleSheetsTrigger, chainLlm, lmChatGroq, gmail. Event-driven trigger; 28 nodes.