Automatisation Grist - n8n pour envoyer des mails : cas pratique

Nous avons créé un prototype de document Grist pour une petite mairie, pour la gestion du bois d’affouage. Grist permet de gérer les commandes et la facturation. Nous allons automatiser 2 processus grâce à n8n :

  • envoi automatique de confirmation de commande, dès que les utilisateur·ices ont soumis le formulaire de commande
  • au clic sur un bouton, envoi des factures personnalisées à chaque habitant·e

Pour utiliser n8n, vous pouvez héberger votre propre instance (l’auto-hébergement de la version communautaire est gratuit), utiliser le SaaS, ou vous rapprocher d’un prestataire. Si vous êtes une petite collectivité ou agent·e de l’ANCT, contactez donnees@anct.gouv.fr

Envoi de confirmation de commande

Le document Grist contient une page « Commandes » avec un formulaire de commande, qui alimente une table « Commandes ».

Le formulaire est envoyé aux habitant·es. Il leur est notamment demandé leur email :

Dans le document, nous allons ajouter une sous-page « Mail de confirmation de commande », avec une table « Mail_automatique_confirmation_commande » dans laquelle nous définissons l’objet, l’expéditeur et le contenu du mail à envoyer.

Nous pourrions définir ces données directement dans n8n, mais le but est de paramétrer le maximum de données dans Grist, pour faciliter la gestion et les modifications éventuelles.

Création et test du point d’ancrage

Nous allons maintenant créer le « point d’ancrage », qui permettra de déclencher le workflow n8n dès l’ajout d’une nouvelle ligne à la table « Commandes » - donc dès que le formulaire est soumis. Les étapes sont les suivantes :

  • Dans n8n, dans un nouveau workflow, ajouter un noeud de type « Webhook », choisir HTTP Method = POST et copier l’URL de test (qui s’affiche après POST)
    (vous pouvez modifier le chemin de l’url si nécessaire dans le champ Path)

  • Dans Grist, Paramètres > Points d'ancrage web, créez un nouveau webhook :
    Nom : Envoi confirmation commandes
    Types d'événements : add (pour « ajout de ligne à la table »)
    Table : la table à observer, ici « Commandes »
    Filtrer les changements dans ces colonnes (facultatif) : spécifiez le nom de la colonne à observer (inutile ici)
    URL : collez l’URL de test de N8N
    Activé : ON

  • Dans n8n, cliquez sur Listen for test event
  • Dans Grist, ouvrez le formulaire, remplissez-le et validez
  • Dans N8N, voyez que l’événement a bien été reçu, et observez le résultat de la requête dans le panneau de droite. Vous pouvez choisir une vue « Schéma », « Table » ou « JSON » :

Nous utiliserons uniquement le champ mail, qui nous servira pour envoyer l’email au bon destinataire.

Récupération du contenu du mail à envoyer
Lors du déclenchement du point d’ancrage, on ne récupère que les données de la table qui a été mise à jour. Il n’est pas possible de récupérer les données d’autres tables.
Or, nous avons besoin de récupérer le contenu, l’objet et l’expéditeur de notre mail, que nous avons défini précédemment dans la table « Mail_automatique_confirmation_commande ».

Nous allons donc dans n8n utiliser un noeud « Grist » pour aller lire cette table.

  • Dans votre workflow n8n, ajoutez un noeud « Grist » et connectez-le au noeud précédent

  • Configurez le noeud :

Credential to connect with : configurez votre authentification Grist : clé API (lien dans Paramètres > Console API) ; « Self-hosted » et l’url de votre instance incluant l’espace d’équipe

DocumentID : à récupérer dans l’url de votre document, de la forme fgsUvXYaVqqL

TableID : id de votre table, ici Mail_automatique_confirmation_commande

Envoi des mails
Nous avons désormais tout ce qu’il nous faut pour envoyer les mails : le mail de la personne ayant soumis le formulaire, ainsi que les infos à envoyer. Nous allons utiliser un outil de mailing de masse, Brevo, pour envoyer facilement nos mails.

  • Dans votre workflow n8n, ajoutez un noeud « Brevo »

Credential to connect with : configurez votre authentification
Resource : Email
Operation : Send
Subject: faites glisser/déposer le champ du panneau de gauche - ou écrivez directement {{ $json.Objet }}
Text content : {{ $json.Corps }}
Sender: {{ $json.Expediteur }}
Recipient : {{ $('Webhook').item.json.body[0].Mail }}, sachant que $('Webhook') va permettre de chercher la donnée issue du noeud « Webhook »

  • CliquezExecute step, vérifiez que vous avez bien reçu l’e-mail. Tests validés !

Passer le workflow « en production »
Pour activer le workflow pour qu’il soit en écoute « permanente » de l’ajout de données dans la table Commandes :

  • Dans n8n, ouvrez le noeud « Webhook », basculez sur Production URL et copiez l’url.
  • Activez le workflow grâce au petit bouton switch au-dessus du canevas (obligatoire, sinon votre requête ne sera pas reçue !) et sauvegardez.
  • Dans Grist, dans la configuration du Webhook, dans le champ URL, remplacez l’url de test par l’url de production. En cas d’erreur visible dans le champ Statut, désactivez et réactivez le bouton Activé.
  • Soumettez à nouveau une réponse au formulaire, et vérifiez que vous avez bien reçu l’e-mail dans votre client mail :partying_face:

Envoi des factures personnalisées

Le document Grist contient une page « Facturation », qui contient une formule de génération html d’une facture, personnalisée pour chaque habitant·e.

On ajoute une table « Texte_pour_mail_facturation » qui contient les informations statiques du mail à envoyer :

  • expéditeur
  • Objet
  • Contenu fixe du mail

Cette table a également une colonne « Année », qui nous permettra de filtrer les factures à envoyer uniquement sur l’année en cours .

On ajoute une autre table « Envoi_factures_n8n » qui contient :

  • une colonne « Envoyer » qui affiche le nombre d’envoi de factures effectué (normalement, il y en aura 1 par an)
  • la formule d’un bouton d’action, qui, si cliqué, incrémente la valeur de la colonne « Envoyer » de la table :

La formule du bouton est la suivante :



{
  "button":"Envoyer",
  "description":":)",
  "actions":[[
    "UpdateRecord", 
    "Envoi_factures_n8n", 
    1, 
    {
    "Envoyer": Envoi_factures_n8n.lookupOne(id=1).Envoyer + 1,
    "Statut": "✅"
  }
  ]]
}
  • une colonne « Statut », qui est mise à jour par défaut avec un :white_check_mark: lors du clic sur le bouton d’envoi, et qui sera mise à jour avec un :x: si l’une des requêtes de l’API de Brevo échoue.

Dans n8n, nous mettons en place le workflow suivant :

Le premier noeud est un webhook dont nous récupérons l’url de test (cf section précédente) pour créer notre point d’ancrage dans Grist.

Ce dernier va déclencher une requête à chaque mise à jour de la colonne « Envoyer » (donc à chaque clic sur le bouton d’action « Envoyer »).

Pour tester le webhook, il faut cliquer dans n8n sur « Listen for test event », puis, dans Grist cliquer sur notre bouton d’action pour mettre à jour la colonne « Envoyer ».

On récupère alors dans notre noeud webhook les données de notre table « Envoi_factures_n8n » :

Le noeud suivant est un noeud Grist qui va récupérer le contenu statique du mail de notre table « Texte_pour_mail_facturation ».

Le troisième nœud est un noeud Grist qu permet de lire notre table des Factures, en filtrant sur l’année obtenue via le nœud précédent :

si on souhaite envoyer uniquement les factures qui ont été livrées, on pourrait rajouter un filtre dans le nœud pour filtrer sur la colonne « Livrée ? »

Enfin, un noeud Brevo permet d’envoyer les mails en masse, en bouclant sur le nombre de factures obtenues via le nœud Grist des factures, et en récupérant les les données du mail dans le premier nœud Grist.

Dans n8n, pour cibler les données du nœud qui précède (le nœud Grist Factures), on utilise $json.Nom_du_champ. Pour cibler les données du nœud Grist d’email, on utilise $('Grist récupérer mail').first().json, avec first car nos données sont dans la première ligne de la table, et 'Grist récupérer mail' qui est le nom de notre nœud.

Le contenu du mail, indiqué dans le champ « HTML Content » du nœud Brevo, est une concaténation entre la partie mail statique, et le html obtenu grâce à notre formule de publipostage.

{{$('Grist récupérer mail').first().json.Contenu + "\n\n\n" + $json.Formule_pour_PDF }}

On envoie ainsi à chaque mail sa propre facture !

Pour boucler la boucle, on paramètre le noeud Brevo pour retourner une erreur si une des requêtes d’envoi n’aboutit pas :

Cela nous permet de mettre à jour la colonne « Statut » de notre table « Envoi_factures_n8n » avec un :x: suivi du message d’erreur.

On ajoute pour cela un noeud Grist qui se déclenchera en cas d’échec d’envoi via Brevo :

On peut le paramétrer pour qu’il ne s’exécute qu’une fois (sinon, il va itérer autant de fois que le nombre de requêtes en échec, ce qui est inutile pour nous puisqu’on aura déjà un :x: affiché dans notre colonne).

Le fait qu’il n’y ait pas d’erreur suite au noeud Brevo ne signifie pas que tous les mails ont bien été reçus par les destinataires, mais seulement que Brevo a bien effectué toutes les requêtes d’envoi de mails transactionnels. Il faudra ensuite regarder dans l’interface de Brevo s’il y a eu des emails non reçus (« soft bounces » ou « hard bounces »).

Passer le workflow « en production »
Pour activer le workflow pour qu’il soit en écoute « permanente » du clic sur le bouton d’envoi :

  • Dans n8n, ouvrez le noeud « Webhook », basculez sur Production URL et copiez l’url.
  • Activez le workflow grâce au petit bouton switch au-dessus du canevas (obligatoire, sinon votre requête ne sera pas reçue !) et sauvegardez.
  • Dans Grist, dans la configuration du Webhook, dans le champ URL, remplacez l’url de test par l’url de production. En cas d’erreur visible dans le champ Statut, désactivez et réactivez le bouton Activé.
  • Cliquez sur le bouton « Envoi », et vérifiez dans n8n, onglet « Exécutions », que le workflow s’est bien déclenché, et dans Brevo que vos emails ont bien été envoyés :partying_face:

Liens utiles :

Infos utiles

Si vous utilisez Grist sur une instance de l’ANCT ou de la DINUM, les webhooks vers des url non connues sont bloquées pour raisons de sécurité - si vous auto-hébergez votre propre instance de n8n, il vous faudra contacter l’équipe (sur Tchap ou par mail donnees@anct.gouv.fr ou contact@grist.numerique.gouv.fr) en amont pour demander une validation de votre sous-domaine. Si vous hébergez sur un domaine .gouv.fr, il sera de facto autorisé.
Sur l’instance de l’ANCT, pour vous permettre de réaliser vos tests, nous avons autorisé les webhooks vers les instances cloud de n8n (n8n.cloud) et Activepieces (cloud.activepieces.com).

3 « J'aime »

Et les workflows n8n associés :slight_smile:

(à enregistrer au format .json, puis à importer directement dans n8n)

Confirmation mail

{
  "name": "D&T - Template Affouage - confirmation de commande",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "588a2757-1557-4f1b-b31d-ea185ed03cb6",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -256,
        -16
      ],
      "id": "cb985d97-22ae-4f00-9cac-6b1d3494ed62",
      "name": "Webhook",
      "webhookId": "588a2757-1557-4f1b-b31d-ea185ed03cb6"
    },
    {
      "parameters": {
        "docId": "fgsUvXYaVqqL",
        "tableId": "Mail_automatique_confirmation_commande",
        "additionalOptions": {}
      },
      "type": "n8n-nodes-base.grist",
      "typeVersion": 1,
      "position": [
        -48,
        -16
      ],
      "id": "37e408a2-5096-4835-9d84-0dee4b4898d2",
      "name": "Grist",
      "credentials": {
        "gristApi": {
          "id": “DDDD”,
          "name": "Grist tuto/templates"
        }
      }
    },
    {
      "parameters": {
        "content": "\nRécupère le contenu du mail",
        "height": 360,
        "width": 200
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -80,
        -144
      ],
      "id": "42ef90bf-62c1-41fd-836f-a625239cd5ac",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "\nReçoit les infos du nouveau formulaire soumis\n",
        "height": 360,
        "width": 220
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -320,
        -144
      ],
      "id": "0f8ab11d-6646-4386-9bdd-f82ba4c268d4",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "subject": "={{ $json.Objet }}",
        "textContent": "={{ $json.Corps }}",
        "sender": "={{ $json.Expediteur }}",
        "receipients": "={{ $('Webhook').item.json.body[0].Mail }}",
        "additionalFields": {},
        "requestOptions": {}
      },
      "type": "n8n-nodes-base.sendInBlue",
      "typeVersion": 1,
      "position": [
        192,
        -16
      ],
      "id": "0607b917-2ebf-40e9-b2ab-f921f1e7fabb",
      "name": "Brevo",
      "credentials": {
        "sendInBlueApi": {
          "id": “DDDDD”,
          "name": "Brevo incubateur des territoires"
        }
      }
    }
  ],
  "pinData": {},
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Grist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Grist": {
      "main": [
        [
          {
            "node": "Brevo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "4c5e0076-742c-4cd4-b6e0-88c94f611904",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "64b538dadf8cb304bf93401f92316a2a374b37d2138ad80b277949ff52fd80c4"
  },
  "id": "FpHwQUv5RuD8D59h",
  "tags": [
    {
      "createdAt": "2025-07-29T16:24:23.584Z",
      "updatedAt": "2025-07-29T16:24:23.584Z",
      "id": "uONy0rWvWOS7uhV3",
      "name": "D&T"
    },
    {
      "createdAt": "2025-07-29T16:10:35.114Z",
      "updatedAt": "2025-07-29T16:25:35.201Z",
      "id": "1WMRMzOcHPVQce1C",
      "name": "Mailing"
    }
  ]
}

Envoi des factures :

{
  "name": "D&T -Template Affouage - factures",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "600d7616-d630-4b96-b2a5-f39f4cb18ab3",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -176,
        0
      ],
      "id": "20b45ed3-7ac6-4331-a60a-bf8dc8c38f75",
      "name": "Webhook",
      "webhookId": "600d7616-d630-4b96-b2a5-f39f4cb18ab3"
    },
    {
      "parameters": {
        "sendHTML": true,
        "subject": "={{$('Grist get static mail').first().json.Objet}}",
        "htmlContent": "={{$('Grist get static mail').first().json.Contenu + \"\\n\\n\\n\" + $json.PDF_Formula }} ",
        "sender": "={{$('Grist get static mail').first().json.Expediteur}}",
        "receipients": "={{ $json.Mail }}",
        "additionalFields": {},
        "requestOptions": {}
      },
      "type": "n8n-nodes-base.sendInBlue",
      "typeVersion": 1,
      "position": [
        480,
        0
      ],
      "id": "95c573f4-3c89-4e05-9d7e-460cb0b0f7a4",
      "name": "Brevo",
      "alwaysOutputData": true,
      "credentials": {
        "sendInBlueApi": {
          "id": “XXXX”,
          "name": "Brevo incubateur des territoires"
        }
      },
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "operation": "update",
        "docId": "fgsUvXYaVqqLrHyZMQdQJk",
        "tableId": "Envoi_factures_n8n",
        "rowId": "1",
        "fieldsToSend": {
          "properties": [
            {
              "fieldId": "Statut",
              "fieldValue": "=❌ {{ $json.messageId }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.grist",
      "typeVersion": 1,
      "position": [
        720,
        48
      ],
      "id": "a16c62d5-7f68-4eca-9998-f7594a855ad2",
      "name": "Grist statut erreur",
      "executeOnce": true,
      "credentials": {
        "gristApi": {
          "id": “XXXXXX”,
          "name": "Grist tuto/templates"
        }
      }
    },
    {
      "parameters": {
        "docId": "prWr8VxGGgRj",
        "tableId": "Texte_pour_mail_facturation",
        "limit": "={{ 50 }}",
        "additionalOptions": {}
      },
      "type": "n8n-nodes-base.grist",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "id": "a3245cbb-d3f3-4e58-abc7-c450b5d9b2e6",
      "name": "Grist get static mail",
      "credentials": {
        "gristApi": {
          "id": “DDDDD”,
          "name": "Grist tuto/templates"
        }
      }
    },
    {
      "parameters": {
        "docId": "prWr8VxGGgRj",
        "tableId": "Billing_and_Payment",
        "additionalOptions": {}
      },
      "type": "n8n-nodes-base.grist",
      "typeVersion": 1,
      "position": [
        208,
        0
      ],
      "id": "bbb0c7da-555f-4272-9622-1f1451361246",
      "name": "Grist get invoices",
      "credentials": {
        "gristApi": {
          "id": “DDDDD”,
          "name": "Grist tuto/templates"
        }
      }
    }
  ],
  "pinData": {},
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Grist get static mail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Brevo": {
      "main": [
        [],
        [
          {
            "node": "Grist statut erreur",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Grist get static mail": {
      "main": [
        [
          {
            "node": "Grist get invoices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Grist get invoices": {
      "main": [
        [
          {
            "node": "Brevo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "57e08efe-0b37-4bed-b0ed-f4d319ee54c8",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "64b538dadf8cb304bf93401f92316a2a374b37d2138ad80b277949ff52fd80c4"
  },
  "id": "O79fvsICKl2lQsob",
  "tags": [
    {
      "createdAt": "2025-07-29T16:24:23.584Z",
      "updatedAt": "2025-07-29T16:24:23.584Z",
      "id": "uONy0rWvWOS7uhV3",
      "name": "D&T"
    },
    {
      "createdAt": "2025-07-29T16:10:35.114Z",
      "updatedAt": "2025-07-29T16:25:35.201Z",
      "id": "1WMRMzOcHPVQce1C",
      "name": "Mailing"
    }
  ]
}