How to handle transactional email in Webflow Cloud after Logic was discontinued

Comment gérer les e-mails transactionnels dans Webflow Cloud depuis l'arrêt de Logic

Découvrez comment envoyer des e-mails transactionnels depuis n'importe quel site Webflow avec Postmark et un Route Handler Webflow Cloud.

Comment gérer les e-mails transactionnels dans Webflow Cloud depuis l'arrêt de Logic

Table of contents

Depuis l'arrêt de Webflow Logic en juin 2025, envoyer un véritable e-mail transactionnel depuis un formulaire Webflow nécessite un vrai backend. Webflow Cloud fait de ce backend un Route Handler : quelques dizaines de lignes de TypeScript qui appellent l'API de Postmark et envoient deux e-mails au moment où un formulaire est soumis.

L'e-mail transactionnel est l'une des premières choses que les clients me demandent d'ajouter une fois qu'un site Webflow dépasse le stade de la brochure. Les notifications de formulaire intégrées de Webflow sont envoyées au propriétaire du site, sans aucun contrôle sur l'adresse d'expéditeur, le modèle ou le texte. Webflow Logic, qui gérait une partie de cette automatisation, a été abandonné le 27 juin 2025.

L'approche actuelle consiste en un Route Handler d'application Webflow Cloud qui reçoit les données du formulaire, appelle l'API HTTP de Postmark, et envoie des e-mails à l'image de la marque dans les deux sens : une confirmation à la personne qui a soumis le formulaire et une notification à votre équipe.

Dans ce guide, nous couvrons chaque étape : vérifier votre domaine d'expéditeur dans Postmark, configurer les identifiants, écrire un helper d'e-mail réutilisable, construire le Route Handler, et le connecter à un formulaire Webflow avec du JavaScript côté client.

De quoi avez-vous besoin pour envoyer des e-mails transactionnels depuis Webflow Cloud ?

Vous avez besoin d'un compte Postmark, d'un domaine d'expéditeur vérifié, et d'une application Webflow Cloud déjà initialisée avant d'écrire le moindre code.

Assurez-vous que ces prérequis sont remplis avant de commencer :

  • Un compte Postmark : le niveau gratuit envoie 100 e-mails par mois, ce qui suffit pour le développement et une production à faible volume
  • Un domaine que vous possédez, avec accès DNS : Postmark exige l'ajout d'enregistrements DNS (DKIM + Return-Path) pour vérifier votre domaine d'envoi
  • Uneapplication Webflow Cloud : exécutez webflow auth login && webflow cloud init si vous ne l'avez pas encore fait.
  • Un site Webflow avec au moins un formulaire nécessitant une gestion d'e-mail personnalisée

Une chose à savoir avant de commencer : le package npm officiel postmark ne fonctionne pas sur Cloudflare Workers. La documentation de Cloudflare l'indique directement : « Postmark's JavaScript library is currently not supported on Workers. » L'API HTTP, en revanche, fonctionne parfaitement via fetch(). C'est celle-ci que ce guide utilise.

5 étapes pour envoyer des e-mails transactionnels depuis Webflow Cloud avec Postmark

Postmark s'occupe de la livraison. L'application Webflow Cloud s'occupe de la validation, du routage et de la mise en forme de la réponse. Ces étapes construisent l'intégration dans l'ordre suivant : vérification du domaine d'expéditeur, configuration des identifiants, un helper d'e-mail réutilisable, et un Route Handler vers lequel le formulaire Webflow envoie ses données.

1. Vérifiez votre domaine d'expéditeur dans Postmark

Postmark bloque l'envoi depuis des domaines non vérifiés. Cette étape ne peut pas être ignorée, et c'est la raison la plus fréquente de l'échec du premier envoi. Configurez-la avant d'écrire le moindre code.

Connectez-vous à Postmark et allez dans Sender Signatures → Add Domain or Signature → Add Domain. Saisissez votre domaine et cliquez sur Verify Domain.

Page Sender Signatures de Postmark avec le bouton Add Domain or Signature mis en évidence, un domaine existant listé avec un lien DNS Settings, et le compte affichant le mode Test

Postmark affiche deux enregistrements DNS à ajouter :

  • Un enregistrement DKIM (type TXT)
  • Un enregistrement Return-Path (type CNAME)

Ajoutez les deux enregistrements chez votre fournisseur DNS. Dans Cloudflare, allez dans DNS → Records → Add record et copiez chaque valeur exactement. Une fois que les enregistrements se propagent (généralement moins de 10 minutes sur Cloudflare), retournez sur Postmark et cliquez sur les boutons Verify.

Une fois le domaine vérifié, récupérez votre token API serveur. Allez dans Servers → My First Server → API Tokens et copiez le token serveur. Cette valeur est incluse dans l'en-tête d'authentification X-Postmark-Server-Token pour chaque requête API.

Point de contrôle : depuis le tableau de bord Postmark, utilisez le bouton Test pour envoyer un e-mail de test. S'il est livré, le domaine est vérifié et le token fonctionne.

2. Ajoutez les identifiants Postmark à votre environnement Webflow Cloud

Votre application Cloud a besoin de deux variables d'environnement.

Ajoutez-les à .env.local pour le développement local :

# .env.local
POSTMARK_SERVER_TOKEN=your_server_token_here
POSTMARK_FROM_EMAIL=hello@yourdomain.com
OWNER_EMAIL=you@yourdomain.com

Dans les paramètres de votre site Webflow, ouvrez Webflow Cloud, sélectionnez votre environnement, et ajoutez les mêmes variables sous Environment Variables.

Pour les tests locaux sans envoyer de vrais e-mails, Postmark fournit un token de test spécial : POSTMARK_API_TEST. Remplacez localement votre vrai token par cette valeur pour valider le format de la requête sans déclencher de livraison réelle.

Point de contrôle : exécutez next dev en local et confirmez que process.env.POSTMARK_SERVER_TOKEN se résout bien à votre token.

3. Créez le helper d'e-mail Postmark

Créez lib/postmark.ts dans votre application Cloud. Ce module gère l'appel HTTP vers l'endpoint /email de Postmark et met la requête dans le format attendu par l'API.

Voici ce qu'il faut faire :

// lib/postmark.ts

const POSTMARK_API = 'https://api.postmarkapp.com'

interface EmailParams {
  to: string
  from: string
  subject: string
  htmlBody: string
  textBody?: string
  replyTo?: string
  tag?: string
  messageStream?: string
}

interface PostmarkResponse {
  ErrorCode: number
  Message: string
  MessageID: string
  SubmittedAt: string
  To: string
}

export async function sendEmail(params: EmailParams): Promise<PostmarkResponse> {
  const token = process.env.POSTMARK_SERVER_TOKEN
  if (!token) throw new Error('POSTMARK_SERVER_TOKEN is not set')

  const res = await fetch(`${POSTMARK_API}/email`, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      'X-Postmark-Server-Token': token,
    },
    body: JSON.stringify({
      From: params.from,
      To: params.to,
      Subject: params.subject,
      HtmlBody: params.htmlBody,
      TextBody: params.textBody ?? params.htmlBody.replace(/<[^>]+>/g, ''),
      ReplyTo: params.replyTo,
      Tag: params.tag,
      TrackOpens: true,
      MessageStream: params.messageStream ?? 'outbound',
    }),
  })

  if (!res.ok) {
    const error = (await res.json()) as PostmarkResponse
    throw new Error(`Postmark error ${error.ErrorCode}: ${error.Message}`)
  }

  return res.json() as Promise<PostmarkResponse>
}

Je garde l'appel Postmark dans un helper dédié plutôt que directement dans le Route Handler. Quand la même application envoie plusieurs types d'e-mails (confirmation de contact, reçu de commande, réinitialisation de mot de passe), chaque Route Handler importe sendEmail() avec ses propres paramètres. Aucune logique fetch dupliquée entre les fichiers.

Le repli TextBody supprime automatiquement les balises HTML pour générer une version en texte brut. En production, écrivez un vrai corps en texte brut : les clients de messagerie qui n'affichent que du texte (y compris de nombreux filtres anti-spam d'entreprise) offrent une meilleure expérience qu'avec du HTML simplement dépouillé.

Point de contrôle : depuis un script de test rapide, appelez sendEmail() avec POSTMARK_SERVER_TOKEN réglé sur POSTMARK_API_TEST. Vous devriez récupérer { ErrorCode: 0, Message: "Test job accepted" }.

4. Créez le Route Handler du formulaire de contact

Créez app/api/contact/route.ts. Ce Route Handler reçoit une soumission de formulaire en JSON, valide les données, envoie deux e-mails via Postmark (une confirmation à la personne qui a soumis le formulaire et une notification au propriétaire du site), et renvoie une réponse JSON.

Voici le contenu :

// app/api/contact/route.ts
export const runtime = 'edge'

import { sendEmail } from '@/lib/postmark'

const SENDER = process.env.POSTMARK_FROM_EMAIL ?? 'hello@yourdomain.com'
const OWNER_EMAIL = process.env.OWNER_EMAIL ?? 'you@yourdomain.com'

interface ContactPayload {
  name: string
  email: string
  message: string
}

function isValidEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}

export async function POST(request: Request) {
  let body: ContactPayload

  try {
    body = (await request.json()) as ContactPayload
  } catch {
    return Response.json({ error: 'Invalid JSON' }, { status: 400 })
  }

  const { name, email, message } = body

  if (!name?.trim() || !email?.trim() || !message?.trim()) {
    return Response.json(
      { error: 'Name, email, and message are required' },
      { status: 422 }
    )
  }

  if (!isValidEmail(email)) {
    return Response.json({ error: 'Invalid email address' }, { status: 422 })
  }

  try {
    // Confirmation to the submitter
    const confirmation = await sendEmail({
      to: email,
      from: SENDER,
      replyTo: OWNER_EMAIL,
      subject: `Thanks for reaching out, ${name}`,
      tag: 'contact-confirmation',
      htmlBody: `
        <p>Hi ${name},</p>
        <p>Thanks for your message. I'll get back to you within one business day.</p>
        <p>Here's what you sent:</p>
        <blockquote>${message}</blockquote>
      `,
    })

    // Notification to the site owner
    await sendEmail({
      to: OWNER_EMAIL,
      from: SENDER,
      replyTo: email,
      subject: `New contact from ${name}`,
      tag: 'contact-notification',
      htmlBody: `
        <p><strong>Name:</strong> ${name}</p>
        <p><strong>Email:</strong> <a href="mailto:${email}">${email}</a></p>
        <p><strong>Message:</strong></p>
        <p>${message}</p>
      `,
    })

    return Response.json({
      success: true,
      messageId: confirmation.MessageID,
    })
  } catch (err) {
    console.error('Email send failed:', err)
    return Response.json({ error: 'Failed to send email' }, { status: 502 })
  }
}

Le handler valide avant d'appeler Postmark. Un nom vide ou une adresse e-mail malformée n'atteignent jamais l'API, ce qui signifie aucun appel API gaspillé et aucune réponse 422 confuse de Postmark à analyser.

Je règle ReplyTo sur l'e-mail du propriétaire pour la confirmation, et sur l'e-mail de la personne qui a soumis le formulaire pour la notification. Ainsi, quand le propriétaire du site clique sur répondre dans son client de messagerie, il joint directement la personne qui a soumis le formulaire.

Point de contrôle : vous pouvez tester l'endpoint avec cURL :

curl -X POST http://localhost:3000/app/api/contact \
  -H "Content-Type: application/json" \
  -d '{"name":"Test User","email":"test@example.com","message":"Hello"}'

Avec POSTMARK_SERVER_TOKEN=POSTMARK_API_TEST, vous devriez recevoir { "success": true, "messageId": "..." }. Avec votre vrai token, vérifiez le journal d'activité de Postmark pour confirmer que les deux e-mails apparaissent.

5. Connectez le formulaire Webflow

Ajoutez ceci dans un bloc de code personnalisé (Before </body>) sur toute page comportant un formulaire de contact. Le formulaire nécessite trois champs de saisie avec les attributs data-contact-name, data-contact-email et data-contact-message, ainsi qu'un élément de statut avec data-contact-status.

Ajoutez un attribut data-contact-form à l'élément de formulaire lui-même :

(function () {
  const form = document.querySelector('[data-contact-form]')
  const status = document.querySelector('[data-contact-status]')

  if (!form) return

  form.addEventListener('submit', async function (e) {
    e.preventDefault()

    const name = form.querySelector('[data-contact-name]')?.value.trim()
    const email = form.querySelector('[data-contact-email]')?.value.trim()
    const message = form.querySelector('[data-contact-message]')?.value.trim()

    if (!name || !email || !message) {
      if (status) status.textContent = 'Please fill in all fields.'
      return
    }

    const submitBtn = form.querySelector('[type="submit"]')
    if (submitBtn) submitBtn.disabled = true
    if (status) status.textContent = 'Sending...'

    try {
      const res = await fetch('/app/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name, email, message }),
      })

      const data = await res.json()

      if (data.success) {
        if (status) status.textContent = 'Message sent. Check your inbox for a confirmation.'
        form.reset()
      } else {
        if (status) status.textContent = data.error ?? 'Something went wrong. Please try again.'
      }
    } catch {
      if (status) status.textContent = 'Network error. Please try again.'
    } finally {
      if (submitBtn) submitBtn.disabled = false
    }
  })
})()

Je désactive le bouton de soumission pendant que la requête est en cours. Sans cela, une double soumission enverrait deux e-mails de confirmation à la même adresse. L'élément data-contact-status donne un retour aux utilisateurs sans déclencher le rechargement de page que provoquerait la gestion de formulaire par défaut de Webflow.

Point de contrôle : publiez votre site Webflow, remplissez le formulaire et soumettez-le. Le message de statut devrait se mettre à jour, et vous devriez recevoir à la fois l'e-mail de confirmation et la notification au propriétaire.

Qu'est-ce qui fait échouer les envois d'e-mails Postmark sur Webflow Cloud ?

La plupart des échecs se répartissent en trois catégories : le SDK npm importé alors qu'il ne devrait pas l'être, des signatures d'expéditeur non vérifiées, et des adresses destinataires inactives.

Voici à quoi chacun ressemble et comment y remédier.

Le package npm postmark lève une erreur à l'importation

Si vous voyez « Cannot find module 'events' » ou « TypeError: Class extends value undefined » au chargement de votre Route Handler, le package npm postmark est installé. Retirez-le avec npm uninstall postmark et utilisez le helper basé sur fetch() de l'étape 3.

La documentation Cloudflare Workers indique explicitement que la bibliothèque JavaScript de Postmark n'est pas prise en charge sur Workers. L'API HTTP couvre chaque opération de ce guide.

Postmark renvoie ErrorCode 400 ou 401

ErrorCode 400 signifie qu'aucune Sender Signature n'existe dans Postmark pour l'adresse From ; elle n'a pas encore été créée. ErrorCode 401 signifie que la Sender Signature existe, mais que l'e-mail de confirmation n'a pas encore été cliqué.

Les deux se résolvent dans le tableau de bord Postmark : ajoutez soit l'adresse From comme nouvelle Sender Signature, soit complétez la vérification du domaine sous Sender Signatures. Un seul domaine vérifié couvre n'importe quelle adresse sur ce domaine, donc hello@yourdomain.com et no-reply@yourdomain.com fonctionnent tous les deux une fois le domaine vérifié.

Postmark renvoie ErrorCode 406

ErrorCode 406 signifie que l'adresse destinataire est inactive. Postmark marque les destinataires comme inactifs après un rejet définitif (hard bounce) ou une plainte pour spam. Je gère le 406 dans le Route Handler en renvoyant une erreur visible par l'utilisateur : « Cette adresse e-mail a précédemment été rejetée. Merci d'utiliser une autre adresse. »

Renvoyer ne fonctionnera pas. Les destinataires inactifs nécessitent une réactivation manuelle via le journal d'activité de Postmark.

Que construire une fois l'e-mail transactionnel opérationnel sur Webflow Cloud

Si vous préférez envoyer via SendGrid, la même structure de Route Handler s'applique, et notre guide de formulaire de contact SendGrid la couvre, y compris la validation côté serveur et la protection anti-spam par honeypot que ce tutoriel n'aborde pas.

Une fois l'e-mail opérationnel, l'ajout naturel suivant est les templates Postmark. Plutôt que de construire le HTML des e-mails à l'intérieur du Route Handler, l'API Templates de Postmark vous permet de gérer le texte des e-mails dans le tableau de bord Postmark et de le rendre avec des variables au moment de l'envoi : un appel sendTemplate() qui poste vers /email/withTemplate avec { TemplateAlias: 'contact-confirmation', TemplateModel: { name, message } }. Le HTML reste hors de la base de code et peut être modifié sans déploiement.

Découvrez Webflow + Postmark pour une liste complète des options d'intégration, ou consultez la documentation Webflow Cloud si vous configurez votre application Cloud pour la première fois.

Questions fréquentes

Puis-je utiliser les templates d'e-mail intégrés de Postmark ?

Oui. L'API Templates de Postmark accepte un TemplateAlias (l'identifiant du modèle dans le tableau de bord) et un TemplateModel (un objet JSON de variables à substituer). Remplacez sendEmail() par une fonction sendTemplate() qui poste vers /email/withTemplate avec { TemplateAlias: 'your-alias', TemplateModel: { name, message } }. Les templates sont créés et modifiés dans le tableau de bord Postmark, sans qu'un déploiement de code soit nécessaire pour mettre à jour le texte.

Comment déclencher des e-mails depuis la soumission de formulaire intégrée de Webflow ?

Les formulaires intégrés de Webflow déclenchent un webhook form_submission quand un visiteur soumet un formulaire. Configurez un webhook dans Webflow Settings → Integrations → Webhooks avec le déclencheur form_submission pointant vers l'URL de votre Route Handler. Le Route Handler reçoit les données des champs du formulaire en JSON et appelle sendEmail() exactement comme ci-dessus. La seule différence est la forme de la charge utile entrante : le webhook de Webflow livre un objet fieldData imbriqué plutôt que des champs JSON plats.

Comment envoyer à plusieurs destinataires ?

Postmark accepte des adresses séparées par des virgules dans les champs To, Cc et Bcc, avec une limite combinée de 50 destinataires par message. Pour des envois à plus de 50 destinataires, utilisez l'endpoint batch de Postmark à /email/batch, qui accepte jusqu'à 500 messages par appel API.

Comment tester sans envoyer de vrais e-mails ?

Réglez POSTMARK_SERVER_TOKEN=POSTMARK_API_TEST dans .env.local. Les requêtes vont vers l'API de Postmark, votre format JSON est validé, et vous récupérez un MessageID. Aucun e-mail n'est livré. C'est la bonne façon d'écrire des tests d'intégration sur le flux d'e-mail sans envoyer vers de vraies boîtes de réception.


Last Updated
Category

Related articles


verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logoreddit logothe new york times logoideo logoupwork logodiscord logo
verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logoreddit logothe new york times logoideo logoupwork logodiscord logo

Lancez-vous gratuitement

Essayez Webflow aussi longtemps que vous le souhaitez avec notre forfait Starter gratuit. Souscrivez à un forfait Site payant pour publier, héberger et débloquer des fonctionnalités supplémentaires.

Get started — it’s free
Voir la démo

Essayez Webflow aussi longtemps que vous le souhaitez avec notre forfait Starter gratuit. Souscrivez à un forfait Site payant pour publier, héberger et débloquer des fonctionnalités supplémentaires.