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.
✨🔪 Advanced AI Powered Document Parsing & Text Extraction with Llama Parse. Uses gmail, gmailTrigger, limit, stickyNote. Webhook trigger; 54 nodes.
This workflow is a high-precision financial intelligence engine that monitors macroeconomic news to identify high-impact trading opportunities. It retrieves real-time data via SerpAPI, processes it th
WordPress Contact Form (CF7) Responses and Classification. Uses lmChatGoogleGemini, textClassifier, gmail, chainLlm. Webhook trigger; 24 nodes.
This workflow optimizes the management of inquiries received through a contact form (Contact Form 7 - CF7 Plugin) on a WordPress site, automating the process of classification, response drafting, and
Googledocs. Uses extractFromFile, outputParserItemList, splitInBatches, stickyNote. Webhook trigger; 23 nodes.