Aller au contenu principal

Webhook email entrant

Transformez les emails de votre propre boîte
en JSON analysé sur votre serveur.

Les services de parsing veulent vos enregistrements MX ou une adresse de transfert. Connectez plutôt la boîte que vous utilisez déjà : chaque nouvel email arrive sur votre URL avec son objet, son HTML, ses pièces jointes et son fil, et vous répondez depuis la même adresse.

Pressé ? Résumez cet article avec une IA

La réponse courte

Comment envoyer les emails entrants vers un webhook ?

Connectez la boîte elle-même. Unlimited Messaging se connecte à votre boîte mail existante en IMAP (Gmail avec un mot de passe d'application, votre propre domaine, tout hébergeur IMAP), surveille sa boîte de réception et envoie chaque nouvel email à votre URL sous forme d'événement JSON signé, message.received, avec objet, texte, HTML, pièces jointes et identifiants de fil. Vos MX restent en place, contrairement à SendGrid Inbound Parse. Les webhooks sont inclus dans les offres payantes Medium et Max.

Les services de parsing se placent devant le courrier. SendGrid vous demande de pointer le MX d'un nom d'hôte dédié vers mx.sendgrid.net, puis envoie du multipart/form-data (ou le MIME brut) à votre URL. Postmark donne à chaque serveur une adresse du type [email protected], vers laquelle vous transférez depuis Gmail ou via l'inbound domain forwarding, et son traitement entrant est réservé à l'offre Pro, à partir de 16,50 $ par mois. Dans les deux cas, l'adresse à laquelle vos clients écrivent change, ou son courrier est relayé.

Unlimited Messaging lit la boîte à laquelle vos clients écrivent déjà. Une passerelle garde une connexion IMAP IDLE sur sa boîte de réception, en lecture seule : un nouvel email est pris dès que le serveur l'annonce. L'email est stocké, ses pièces jointes aussi (20 par email au plus), et votre webhook reçoit un seul corps JSON : externalId est le Message-ID, conversation.id la racine de son fil, replyTo le message auquel il répond.

La réponse boucle la boucle : POST /message avec replyToMessageId envoie depuis la même boîte, par son propre serveur SMTP, avec Re: et les en-têtes In-Reply-To et References, et range une copie dans ses Éléments envoyés. Le fil reste un seul fil dans la messagerie de votre client.

Avant de connecter : configurer Gmail en IMAP avec un mot de passe d'application, et pourquoi envoyer depuis votre propre boîte compte.

Illustration : une grille de petites icônes dont une en évidence, avec le titre « Votre boîte mail, en JSON ».

Les événements

Que contient le webhook d'email entrant ?

L'email partage l'enveloppe de tous les canaux et ajoute trois champs : subject, html et attachments.

  1. message.received

    Un nouvel email est arrivé dans la boîte de réception. content est le corps texte, ou le HTML converti en texte quand l'email n'a pas de partie texte ; html est le corps tel que reçu, non fiable, à nettoyer avant tout affichage.

  2. attachments

    Chaque fichier dans l'ordre, 20 par email au plus : url (la copie stockée), filename, mimeType, size, et contentId pour une image intégrée que le HTML référence en cid:. Le premier est aussi recopié dans mediaUrl.

  3. Fils de discussion

    externalId est le Message-ID RFC 5322 sans chevrons. conversation.id est la racine du fil (premier élément de References, sinon In-Reply-To, sinon l'email lui-même) : tous les emails d'un fil le partagent.

  4. message.sent et message.status

    Vos réponses sont annoncées en message.sent, et un rebond définitif passe le message en UNDELIVERABLE (message.status). L'adresse en échec est alors suspendue 30 jours sur cette boîte pour protéger sa réputation.

  5. account.status_changed

    La boîte s'est déconnectée, par exemple parce que le serveur a refusé les identifiants après un changement de mot de passe. Abonnez-vous-y pour qu'une boîte muette ne passe pas inaperçue.

POST https://example.com/webhooks/umJSON
{
  "type": "message.received",
  "eventId": "whd_9a4f2e61-7c3b-4d85-b0e9-5f1a8c2d7e34",
  "occurredAt": "2026-11-09T14:05:12.000Z",
  "data": {
    "message": {
      "id": "msg_3f8a1c2e-9d4b-4e7a-8b6c-2d1e0f9a8b7c",
      "externalId": "[email protected]",
      "direction": "IN",
      "fromMe": false,
      "content": "Hi, please find the signed quote attached. Claire",
      "sentAt": "2026-11-09T14:05:12.000Z",
      "receivedAt": "2026-11-09T14:05:12.000Z",
      "sender": "[email protected]",
      "senderId": "[email protected]",
      "senderName": null,
      "participant": null,
      "replyTo": { "externalId": "[email protected]", "participant": null },
      "mediaUrl": "https://api.unlimitedmessaging.app/message/media/stored/4c1d...",
      "mediaType": "document",
      "mimeType": "application/pdf",
      "mediaFilename": "quote-1042-signed.pdf",
      "transcript": null,
      "subject": "Re: Quote #1042",
      "html": "<p>Hi, please find the signed quote attached.</p><p>Claire</p>",
      "attachments": [
        {
          "url": "https://api.unlimitedmessaging.app/message/media/stored/4c1d...",
          "filename": "quote-1042-signed.pdf",
          "mimeType": "application/pdf",
          "size": 184320,
          "contentId": null
        }
      ]
    },
    "channel": {
      "type": "EMAIL",
      "account": { "id": "acc_8b2e4f6a-1c3d-4e5f-a7b9-0c1d2e3f4a5b", "name": "[email protected]" },
      "conversation": {
        "id": "[email protected]",
        "isGroup": false,
        "address": "[email protected]"
      }
    }
  }
}
Un vrai corps message.received pour un email, construit d'après le schéma publié (URLs de médias raccourcies). sender est l'adresse From ; senderName n'est pas renseigné pour l'email.

Mise en place

Comment analyser les emails entrants en Next.js ?

Connectez la boîte, enregistrez un endpoint, vérifiez chaque POST. Ni parser MIME ni bibliothèque IMAP dans votre code.

  1. 01

    Connectez votre boîte

    Dans l'application web, ajoutez un compte email avec l'adresse et son mot de passe (un mot de passe d'application pour Gmail). Les réglages IMAP et SMTP sont détectés à partir de l'adresse.

  2. 02

    Enregistrez l'endpoint

    POST /webhooks avec votre URL et vos eventTypes, ou ajoutez-le sur la page Webhooks du tableau de bord. Le secret (whsec_...) n'est affiché qu'une fois.

  3. 03

    Vérifiez, enregistrez, répondez

    Contrôlez l'en-tête webhook-signature, faites un upsert sur eventId, récupérez les pièces jointes par leur url quand vous en avez besoin, répondez avec replyToMessageId.

POST /webhookscurl
curl https://api.unlimitedmessaging.app/webhooks \
  -H "Authorization: Bearer $UM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/um",
    "eventTypes": ["message.received", "account.status_changed"],
    "description": "Inbound email"
  }'

# The response carries "secret": "whsec_..." once. Store it as UM_WEBHOOK_SECRET.
Ajoutez accountIds pour limiter l'endpoint à une boîte, par exemple support@ mais pas facturation@. Sans lui, tous les comptes sont couverts.
app/api/webhooks/um/route.tsNext.js route handler
// app/api/webhooks/um/route.ts (Next.js App Router)
import crypto from 'node:crypto';
import { UnlimitedMessagingApiClient } from '@unlimited-messaging/sdk';

const um = new UnlimitedMessagingApiClient({ token: process.env.UM_API_KEY! });
const key = Buffer.from(process.env.UM_WEBHOOK_SECRET!.replace(/^whsec_/, ''), 'base64');

// Standard Webhooks: HMAC-SHA256 of "id.timestamp.rawBody", base64.
function verify(raw: string, id: string, ts: string, header: string) {
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = crypto.createHmac('sha256', key).update(`${id}.${ts}.${raw}`).digest('base64');
  return header.split(' ').some((entry) => {
    const sig = entry.startsWith('v1,') ? entry.slice(3) : '';
    return sig.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  });
}

export async function POST(req: Request) {
  const raw = await req.text(); // the raw body, before any JSON parsing
  const h = req.headers;
  if (!verify(raw, h.get('webhook-id') ?? '', h.get('webhook-timestamp') ?? '',
    h.get('webhook-signature') ?? '')) {
    return new Response('bad signature', { status: 401 });
  }

  const event = JSON.parse(raw);
  if (event.type === 'message.received' && event.data.channel.type === 'EMAIL') {
    const m = event.data.message;
    const thread = event.data.channel.conversation.id; // same id for the whole thread
    const files = m.attachments ?? []; // [{ url, filename, mimeType, size, contentId }]
    await saveTicket({ eventId: event.eventId, thread, from: m.sender, subject: m.subject,
      text: m.content, files }); // upsert on eventId: delivery is at least once

    // Answer in the same thread, from the same mailbox.
    await um.messages.messageSend({ replyToMessageId: m.id, text: 'Thanks, received. We reply within a day.' });
  }
  return new Response(null, { status: 200 }); // within 10 s; queue slow work
}
req.text() conserve le corps brut sur lequel la signature est calculée (Standard Webhooks, HMAC-SHA256 sur id.timestamp.rawBody). saveTicket est à vous. La réponse part avec Re: et les en-têtes de fil, depuis la même boîte.

Vibe coding

Vous codez avec Cursor, Claude Code, Lovable ou Bolt ? Collez ceci

Votre éditeur IA reçoit la route, la vérification de signature et les champs email utiles, sans inventer de parser MIME.

Prompt à copier
Add an Unlimited Messaging webhook to this app so incoming emails from my own mailbox become support tickets.

1. Create a POST route (Next.js: app/api/webhooks/um/route.ts) that reads the RAW body with req.text().
2. Verify the Standard Webhooks signature: key = base64-decode(UM_WEBHOOK_SECRET without "whsec_"); expected = base64(HMAC-SHA256(key, `${webhook-id}.${webhook-timestamp}.${rawBody}`)); accept if any space-separated entry of the webhook-signature header equals `v1,${expected}` (constant-time compare); reject timestamps more than 5 minutes off; return 401 otherwise.
3. On type "message.received" with data.channel.type "EMAIL": upsert a ticket keyed on body.eventId with data.channel.conversation.id (thread id), data.message.sender, data.message.subject, data.message.content (text) and data.message.attachments ([{ url, filename, mimeType, size, contentId }]). Treat data.message.html as untrusted: sanitise before rendering.
4. To answer, install @unlimited-messaging/sdk, read UM_API_KEY from env, and call `client.messages.messageSend({ replyToMessageId: data.message.id, text })`: it replies in the same thread from the same mailbox.
5. Return 200 within 10 seconds; queue slow work.
6. Put UM_API_KEY and UM_WEBHOOK_SECRET in .env and .env.example.

Docs: https://docs.unlimitedmessaging.app (Webhooks). Register with POST https://api.unlimitedmessaging.app/webhooks and eventTypes ["message.received"].

Ce que l'on construit

Que construire sur les emails entrants ?

Tout ce qui, aujourd'hui, consiste à recopier un email dans un autre outil.

Un helpdesk sur support@

Un ticket par conversation.id, chaque réponse ajoutée, des réponses envoyées depuis support@ dans le même fil. Aucune adresse de ticketing à publier.

Tri et réponse automatique par IA

Donnez subject et content à un LLM, étiquetez et aiguillez l'email, ou envoyez une première réponse avec replyToMessageId avant qu'un humain prenne le relais.

  1. Factures et documents entrants

    Récupérez le PDF via attachments[].url et envoyez-le à votre comptabilité ou à votre OCR.

  2. Captation de leads

    Transformez les notifications de formulaire ou les emails directs en leads CRM, par adresse d'expéditeur.

  3. Une seule boîte avec WhatsApp et SMS

    Le même handler traite les événements WhatsApp et SMS : seul data.channel.type change.

  4. Flux no-code

    Pointez le webhook vers n8n, Make ou Zapier et aiguillez selon l'objet ou l'expéditeur, sans serveur.

Le tableau

Email entrant vers webhook : SendGrid, Postmark et Unlimited Messaging

Les services de parsing reçoivent le courrier d'une nouvelle adresse ou d'un nouveau nom d'hôte. Unlimited Messaging lit la boîte que vous avez déjà.

Email entrant vers webhook : SendGrid, Postmark et Unlimited Messaging
SendGrid Inbound ParseMX sur un nom d'hôte dédiéPostmark InboundAdresse ou domaine entrantUnlimited MessagingVotre boîte en IMAP, offres payantes
Adresse qui reçoitToute adresse d'un nom d'hôte dédié au parsing[email protected], ou un transfert vers elleVotre adresse existante
Changement DNSMX vers mx.sendgrid.netAucun avec un transfert ; DNS pour l'inbound domain forwardingAucun
Format du corpsmultipart/form-data, ou le message MIME brutJSON ; authentification HTTP basic dans l'URL comme protectionJSON, signé selon Standard Webhooks
Identifiants de filDans les en-têtes à analyserDans les en-têtes analysésconversation.id (racine du fil) et replyTo, prêts à l'emploi
Répondre depuis la même adresseVia l'envoi SendGrid, à configurer à partVia l'envoi Postmark, à configurer à partreplyToMessageId, par le SMTP de votre boîte, copie dans Envoyés
PrixInclus dans une offre SendGridEntrant sur l'offre Pro, à partir de 16,50 $ par moisWebhooks avec Medium ou Max ; aucun frais par email
WhatsApp et SMS dans le même webhookNonNonOui, même enveloppe

Vérifié le 9 novembre 2026 sur le guide de configuration de l'Inbound Parse de SendGrid, le guide des serveurs entrants et la page tarifs de Postmark (liens dans les sources).

Les contreparties

Quelles sont les limites d'une passerelle boîte mail vers webhook ?

Lire votre propre boîte supprime le travail DNS. Elle en apporte les règles.

  1. Les webhooks sont payants

    Avec l'offre Free, les emails reçus s'affichent dans la boîte de réception de l'application web, regroupés par fil. Les pousser vers votre serveur demande Medium (19 € par mois, ou 17 € par mois en annuel) ou Max.

  2. Boîte de réception uniquement

    La passerelle surveille INBOX. Un email que votre hébergeur classe en spam, ou qu'un filtre déplace dans un autre dossier, n'est jamais vu.

  3. Pas encore d'Outlook ni de Microsoft 365

    Outlook, Hotmail et Microsoft 365 exigent OAuth, prévu mais pas encore disponible. Gmail fonctionne avec un mot de passe d'application, qui demande la validation en deux étapes sur le compte Google.

  4. 20 pièces jointes par email au plus

    Les fichiers au-delà du 20e ne sont pas stockés. Le HTML est transmis tel que reçu : nettoyez-le avant de l'afficher où que ce soit.

  5. Au moins une fois, sans ordre garanti

    Le même événement peut arriver deux fois, et les événements n'arrivent pas dans l'ordre des faits : faites un upsert sur eventId et triez sur sentAt.

FAQ

Questions sur les webhooks d'email entrant

Ilian, ingénieur logiciel senior

Écrit par

· Senior Software Engineer

Ingénieur logiciel senior, il fonde et dirige plusieurs sociétés tech. Il conçoit des produits autour des API de messagerie, de l’automatisation et de l’intégration de l’IA, et partage ici ce qu’il apprend en construisant.

Voir le profil LinkedInMis à jour le

Votre boîte de réception, en JSON propre.

Créez un compte, connectez votre boîte mail, et activez les webhooks avec Medium ou Max. L'envoi d'emails reste gratuit sur toutes les offres.