Générer et envoyer par mail un pdf récapitulatif pour l’usager suite à la saisie d’un formulaire

Bonjour,

J’aurais besoin de pouvoir générer un pdf à destination de l’utilisateur lorsque celui-ci aura fini de remplir un formulaire, qui reprendrait une partie des réponses mises en forme afin de pouvoir l’imprimer puis de le faire signer par un tiers.

Comment cela pourrait-il s’organiser (dans le cadre contraint de l’instance interministérielle française) ?

Merci d’avance

Bonjour Maxime,
vous pourriez utiliser le publipostage sur la table alimentée par le formulaire?

1 « J'aime »

Je me suis en effet orienté vers cette solution.

Il me reste à tenter d’automatiser le tout avec 8n8, ce qui va prendre plus de temps. ^^

Merci d’avoir pris le temps de répondre.

Oui je pensais aussi à n8n, le « truc » c’est que c’est compliqué de générer le PDF pour l’envoyer par mail dans n8n, en tout cas je n’ai pas trouvé de solution gratuite. Alors pour l’envoi de factures de bois d’affouage on envoie la facture en html dans le corps du mail, ce qui finalement fonctionne bien et est plus écologique. Mais dans votre cas, s’il faut imprimer, je ne sais pas si ce sera pratique !

Idem j’ai cherché pour generer le pdf depuis n8n mais ce n’était pas satisfaisant. Par exemple pour générer un bulletin d’adhésion après avoir rempli le formulaire. Alors j’ai opté pour cette solution :
Je généré un lien unique (grâce a un uuid) pour chaque entrée grist, qui est un appel vers un webhook n8n
C’est ce lien que j’envoie à l’utilisateur pour qu’il voit et/ou télécharge son document
Dans n8n dans mon webhook je récupère les infos de la ligne correspondate grâce au uuid et je génère une page html avec un bouton pour telecharger la page grâce à html2canvas, donc le lien affiche le document et un bouton télécharger
Comme ça je ne stock pas tous les documents mais l’utilisateur les télécharge si besoin seulement. Et dans ma table j’ai le lien qui permet de voir/télécharger le document

2 « J'aime »

Et l’utilisateur peut recuper son document à la fin du formulaire ou il reçoit un courrier lui indiquant qu’il peut le faire ?

Le formulaire grist permet de rediriger vers une URL spécifique, mais je ne crois pas qu’on puisse avoir l’id de la ligne ajoutée
On pourrait contourner le probleme en redirigeant vers une page qui affiche la dernière entrée. Pour une utilisation standard je pense que ca suffit

Ça se passe où cette redirection ?

Trouvé ! (panneau de création à droite > formulaire > soumission > redirection)

Super bonne idée ! Merci du partage. Tu pourrais nous faire un petit pas à pas stp :pray: je sais pas du tout comment fonctionne html2canvas

1 « J'aime »

Oui je ferai çà dès que je trouve un temps, avec un document démo

2 « J'aime »

Salut, si t’as deux minutes, tu penses à nous ?:pray: :innocent:

Salut, j’ai fait une démo d’un formulaire d’adhésion.

  • un formulaire grist qui envoie vers la carte à télécharger
  • une table d’adhérent qui contient le lien unique de la carte d’adhérent
  • un email de confirmation qui contient la carte et le lien de téléchargement

Je suis en train de transformer le code n8n en template pour pouvoir le partager également, voici un aperçu

Ca fonctionne très bien.
J’ai ajouté la fonctionnalité de la carte qui s’affiche automatiquement en fin de formulaire.
Cependant je pense que cette redirection automatique est une pratique à manier prudemment dans un cas réel. Le formulaire grist ne permet de rediriger que vers une adresse générique. On utilise cette adresse générique pour afficher le dernier adhérent enregistré. Hors une carte d’adhérent peut faire fuiter des données sensibles.

1 « J'aime »

le workflow n8n

1 « J'aime »

Quand je clique sur « Télécharger la carte », il ne se passe rien :confused:

ah mince,
depuis une récente version de n8n (du 14 juillet 2025), il encapsule les réponses de webhook dans un iframe, ce qui crée des problemes de cors quand on utilise html2canvas …

je cherche un moyen de régler ce problème qui n’existait pas jusqu’à récemment

ca m’a pris pas mal de temps, mais j’ai trouvé une autre solution.
Ca n’utilise plus html2canvas. Car comme je le disais ci-dessus, cette méthode fonctionnait parfaitement avant la dernière mise à jour, mais ne fonctionne plus maintenante.
Je passe donc par Puppeteer. Ca me permet de faire facilement un screenshot de nimporte quelle page web. Et l’avantage est que j’ai un pdf maintenant. Alors que j’avais un png avec html2canvas.
Et aussi, Puppeteer tourne coté serveur, je peux donc maintenant manipuler le fichier pdf directement dans n8n, et l’envoyer par mail directement par exemple. Je pourrais l’envoyer dans grist aussi (ce n’est pas fait actuellement, mais ca serait facile à ajouter)

voilà le document Grist démo Démo bulletin adhésion

Le lien vers le formulaire utilisateur grist qui renvoit un pdf : Grist Form

le workflow n8n n8dex - Share and Discover n8n Workflows

L’utilisation de puppeteer ouvre pas mal de possibilités.
Par contre, son installation n’est pas facile … Pour ma part, étant sur une instance auto-hébergée, j’ai installé en suivant GitHub - drudge/n8n-nodes-puppeteer: n8n node for browser automation using Puppeteer

1 « J'aime »

J’ai une solution assez simple qui arrive par un noeud code sur n8n en amont d’un noeud Gotenberg. C’est très prometteur, pour ce que je cherche à faire.

Je poste ça dès que ça fonctionne parfaitement.

1 « J'aime »

Ma solution très bricolo qui ne demande qu’à être améliorée et simplifiée :

1 et 2. Pour configurer le Webhook et le noeud Grist, je vous renvoie au post d’ @audezu : Workflows basiques avec n8n

3. Un premier nœud Code renvoie le dernier enregistrement dans la table Grist.
Deux remarques :

  • Normalement, un bon paramétrage du nœud Grist suffirait pour cette opération mais je n’y suis pas parvenu.
  • cette partie de code pourrait être intégrée au nœud suivant mais j’ai préféré les séparer pour la lisibilité (et parce que j’espère arriver à régler le point ci-dessus)
  • Une troisième par souci d’honnêteté : c’est DeepSeek qui a généré ce code comme celui du deuxième nœud code.
const allRecords = $input.all();
console.log(`📊 Reçu ${allRecords.length} enregistrements`);

if (allRecords.length === 0) return [];

// Affiche les timestamps pour vérifier
allRecords.forEach((record, index) => {
  console.log(`Record ${index}: ${record.json.rec_time}`);
});

const lastRecord = allRecords.reduce((newest, current) => {
  return new Date(current.json.rec_time) > new Date(newest.json.rec_time) 
    ? current 
    : newest;
});

console.log(`✅ Dernier enregistrement: ${lastRecord.json.rec_time}`);
return [lastRecord];

4. Le deuxième nœud code génère un fichier pdf :

  • récupère les valeurs utiles du noeud Grist
  • convertit les dates UNIX/Timestamp en dd/mm/yyyyy
  • génère un squelette html et le remplit avec les valeurs
  • génère un fichier html à partir du modèle rempli ci-avant.
//récupère les valeurs utiles du noeud Grist
const { 
    eleve_nom, 
    eleve_prenom,
    eleve_ddn,
    resp1_qualite,
    resp1_nom,
    resp1_prenom,
    resp1_adresse,
    resp1_cp,
    resp1_commune,
  
} = items[0].json;

// date UNIX/Timestamp => lisible par les humains

const date = new Date(eleve_ddn * 1000);
const jour = String(date.getDate()).padStart(2, '0');
const mois = String(date.getMonth() + 1).padStart(2, '0');
const annee = String(date.getFullYear());
const dateFormatee = `${jour}/${mois}/${annee}`;

// squelette html nourri aux valeurs de grist

const htmlContent = `
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>My HTML document</title>
  <style>
    </style>
</head>
<body>
 
${eleve_prenom} ${eleve_nom} est un sacr&eacute; zozo !

</body>
</html>
`;

//enregistre le contenu html dans un fichier
return [{
  json: {},
  binary: {
    data: {
      data: Buffer.from(htmlContent).toString('base64'),
      mimeType: 'text/html',
      fileName: 'index.html'
    }
  }
}];

5. le nœud HTTP Request / Gotenborg transforme le fichier html en pdf avec la configuration suivante (désolé pour les captures, je l’éditerai en texte dès que j’aurai plus de temps) :


6. le nœud Send mail.
Pas trop d’enjeu, si ce n’est de mettre le même nom pour la pièce-jointe (ici « example.pdf »).
A noter qu’on peut récupérer des valeurs du noeud Grist, par exemple le destinataire sou la forme :

{{ $('Grist').item.json.mail_du_destinataire }}

Merci pour vos éventuels retours pour alléger, améliorer…

2 « J'aime »