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 ».
Capture d’écran 2025-07-16 à 12.05.39

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

Capture d’écran 2025-07-16 à 11.48.16

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.

Capture d’écran 2025-07-16 à 11.53.38

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)
    Capture d’écran 2025-07-16 à 12.03.17

  • 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

Capture d’écran 2025-07-16 à 12.18.14

  • 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 » :
    Capture d’écran 2025-07-16 à 12.24.25

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

Capture d’écran 2025-07-16 à 12.30.30

  • 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

Capture d’écran 2025-07-16 à 12.33.35

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

TableID : id de votre table, ici Mail_automatique_confirmation_commande

  • Cliquez sur « Execute step » et voyez le résultat de la requête de lecture de table :
    Capture d’écran 2025-07-16 à 12.30.47

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 »

Capture d’écran 2025-07-16 à 12.36.46

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 »

Capture d’écran 2025-07-16 à 12.39.41

  • 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:

Capture d’écran 2025-07-16 à 12.42.02

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 .
Capture d’écran 2025-07-22 à 17.23.24

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 :cross_mark: si l’une des requêtes de l’API de Brevo échoue.

Capture d’écran 2025-07-23 à 17.35.11

Dans n8n, nous mettons en place le workflow suivant :
Capture d’écran 2025-07-23 à 17.25.23

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 »).

Capture d’écran 2025-07-22 à 17.31.53

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 » :
Capture d’écran 2025-07-23 à 16.50.22

Le noeud suivant est un noeud Grist qui va récupérer le contenu statique du mail de notre table « Texte_pour_mail_facturation ».
Capture d’écran 2025-07-23 à 16.54.36

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 :
Capture d’écran 2025-07-23 à 16.55.30

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 ? »
Capture d’écran 2025-07-23 à 16.57.30

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.

Capture d’écran 2025-07-23 à 16.59.49

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 !

Capture d’écran 2025-07-22 à 17.38.24

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 :cross_mark: suivi du message d’erreur.

On ajoute pour cela un noeud Grist qui se déclenchera en cas d’échec d’envoi via Brevo :
Capture d’écran 2025-07-23 à 17.27.26

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 :cross_mark: affiché dans notre colonne).
Capture d’écran 2025-07-23 à 17.27.38

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 :

https://forum.grist.libre.sh/t/generer-des-pdf-personnalises-publipostage-widget-markdown/1154?u=audezu

https://forum.grist.libre.sh/t/envoyer-des-emails-avec-n8n/454

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).

4 « 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"
    }
  ]
}

Merci pour ces infos sur comment faire de l’automatisation pour Grist grace à n8n (outil que je connaissais pas). Pour mes travaux de recherche et de gestion de projet, j’utilise depuis de nombreuses années Airtable. Voyant les fonctionnalités de Grist je me dis que je pourrais migrer ma base vers grist mais j’ai beaucoup d’éléments qui s’autocomplètent avec des règles de « si ajout d’un élément dans cette table » alors « création d’un élément dans une autre table »; est ce que ça aussi peut être géré via n8n (juste pour savoir si je peux creuser de ce coté pour préparer la migration) :slight_smile: merci,

Bonjour,

Ouiii bye Airtable :smirk:

Vous avez le lookupOrAddDerived pour Copier automatiquement des données d’une table vers une autre , ou pour des cas plus complexes en effet n8n.

Top ! Parfait je vais creuser cette fonction de Grist et les fonctionnalités de n8n alors ! Merci pour cette réponse rapide :slight_smile:

Bonjour,
Ceci pourrait vous intéresser :wink: