Mailforge

Développeurs

API Mailforge

API REST JSON pour envoyer des e-mails transactionnels depuis vos domaines vérifiés et suivre leur livraison. Spécification complète : OpenAPI 3.1 (JSON).

Authentification

Créez une clé dans Mailforge › Clés API et transmettez-la dans l’en-tête Authorization: Bearer mf_…. La clé n’est affichée qu’une fois ; elle est limitée à l’organisation qui l’a créée. Toutes les requêtes passent en HTTPS.

Envoyer un e-mail — POST /api/v1/emails

L’en-tête Idempotency-Key est obligatoire : un rejeu avec la même clé et le même contenu renvoie l’e-mail existant (200), un contenu différent est refusé (409). L’expéditeur doit appartenir à un domaine vérifié. Réponse 202 : l’e-mail est en file.

curl -X POST https://mailforge.forge-labs.fr/api/v1/emails \
  -H "Authorization: Bearer mf_votre_cle" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: commande-123-confirmation" \
  -d '{
    "from": "Boutique <hello@votre-domaine.fr>",
    "to": "client@example.com",
    "subject": "Votre commande est confirmée",
    "html": "<p>Merci pour votre commande.</p>",
    "text": "Merci pour votre commande.",
    "metadata": { "order_id": "123" }
  }'

# 202 Accepted
{ "id": "cm1…", "status": "queued", "queued": true }

Champs : from, to (adresse ou tableau, 50 max), cc, bcc, reply_to, subject (sans retour à la ligne), html et/ou text, metadata (paires texte). Corps limité à 4,5 Mo.

Consulter un e-mail — GET /api/v1/emails/{id}

Renvoie le statut courant et les événements (envoi, livraison, ouverture, clic, rebond, plainte…) d’un e-mail transactionnel de votre organisation. Un identifiant d’une autre organisation répond 404.

curl https://mailforge.forge-labs.fr/api/v1/emails/cm1… -H "Authorization: Bearer mf_votre_cle"

{
  "id": "cm1…", "status": "delivered", "to": ["client@example.com"],
  "subject": "Votre commande est confirmée",
  "sent_at": "2026-10-04T08:00:01.000Z", "delivered_at": "2026-10-04T08:00:03.000Z",
  "open_count": 1, "click_count": 0,
  "events": [{ "type": "delivered", "recipient": "client@example.com", "occurred_at": "2026-10-04T08:00:03.000Z", "machine": false }]
}

Statuts : queued, sent, delivered, soft_bounced, bounced, complained, rejected, failed.

Limites de débit

Chaque clé dispose de 10 requêtes par seconde (fenêtre glissante, partagée par toutes les instances). Chaque réponse indique RateLimit-Limit, RateLimit-Remaining et RateLimit-Reset. Au-delà, Mailforge répond 429 avec Retry-After (secondes) et le code rate_limited. Les quotas journaliers de votre plan s’appliquent en plus (daily_quota_exceeded).

Erreurs

Les erreurs ont toujours la forme { "error": { "message": "…", "code": "…" } }. Le message est lisible (en français) ; basez votre logique sur code.

Codes d’erreur de l’API
HTTPCodeSignification
401unauthorizedClé API absente, invalide, révoquée ou expirée.
403forbiddenLa clé n’a pas le droit requis.
400idempotency_key_requiredEn-tête Idempotency-Key absent ou invalide (8 à 200 caractères ASCII visibles).
400validation_errorCorps JSON invalide ou champ refusé.
400invalid_senderAdresse expéditrice invalide.
400invalid_recipientAdresse destinataire invalide.
400invalid_reply_toAdresse de réponse invalide.
400invalid_subjectObjet vide ou contenant des caractères de contrôle.
404not_foundE-mail introuvable pour l’organisation de la clé.
409idempotency_conflictLa clé d’idempotence a déjà servi pour un contenu différent.
413payload_too_largeCorps de requête supérieur à 4,5 Mo.
422domain_not_verifiedLe domaine de l’expéditeur n’est pas vérifié.
422domain_not_authorizedLe domaine n’est pas autorisé pour le trafic transactionnel.
422recipient_suppressedUn destinataire figure dans une liste d’exclusion.
403traffic_not_enabledLe trafic transactionnel n’est pas activé pour l’organisation.
403quota_disabledQuota journalier nul.
429rate_limitedLimite de débit de la clé dépassée ; respectez Retry-After.
429daily_quota_exceededQuota journalier atteint.
503route_unavailableAucune route d’envoi transactionnelle active.
500queue_errorErreur interne lors de la mise en file.

Webhooks

Déclarez une URL HTTPS publique dans Mailforge › Webhooks et choisissez les événements. Mailforge envoie un POST JSON par événement. Répondez 2xx en moins de 10 secondes ; sinon la livraison est retentée avec un délai croissant (30 s, 2 min, 8 min, 32 min… plafonné à 12 h, 8 tentatives). Les redirections ne sont pas suivies et les adresses privées sont refusées.

  • email.sent — Envoyé au relais
  • email.delivered — Livré
  • email.bounced — Rebond définitif
  • email.complained — Plainte
  • email.opened — Ouvert
  • email.clicked — Cliqué
  • email.failed — Échec définitif
POST /votre-endpoint
Content-Type: application/json
Mailforge-Signature: t=1791100800,v1=4f8e…c21a
Mailforge-Event-Id: evt_7d0c…
Mailforge-Event-Type: email.delivered

{
  "id": "evt_7d0c…",
  "type": "email.delivered",
  "created_at": "2026-10-04T08:00:03.120Z",
  "data": { "email_id": "cm1…", "recipient": "client@example.com", "occurred_at": "2026-10-04T08:00:03.000Z" }
}

Vérifier la signature

v1 est le HMAC-SHA256 (hexadécimal) de la chaîne `${t}.${corps brut}` calculé avec le secret du point de terminaison (whsec_…, affiché une seule fois). Utilisez le corps brut reçu, comparez en temps constant, refusez un t de plus de 5 minutes et ignorez un id déjà traité (une même livraison peut arriver deux fois).

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyMailforgeSignature(secret, header, rawBody, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=", 2)));
  const timestamp = Number(parts.t);
  if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
  const provided = Buffer.from(parts.v1 ?? "", "hex");
  return provided.length === expected.length && timingSafeEqual(provided, expected);
}