Référence API
POST /api/v1/send constitue l'intégralité de l'API d'envoi transactionnel — un seul endpoint versionné et stable. À la différence du reste des routes de MailInApp (qui n'ont jamais que notre propre frontend comme appelant et peuvent changer librement), celle-ci est un contrat dont dépend du code tiers, donc elle est versionnée dès le premier jour.
Authentification
Authorization: Bearer mia_live_...
Une clé manquante ou invalide renvoie 401. Les clés se gèrent sous Developers dans le tableau de bord — voir le démarrage rapide. Une clé révoquée cesse de fonctionner immédiatement.
Requête
Content-Type: application/json. Deux formes de requête partagent l'endpoint — celle qui s'applique est déterminée par la présence ou non de projectId.
Libre
| Champ | Type | Requis | Remarques |
| --- | --- | --- | --- |
| to | string | oui | Une seule adresse de destinataire. |
| subject | string | oui | Tronqué à 200 caractères. |
| html | string | oui | Envoyé tel quel — pas de rendu, pas de balises de fusion, pas de pipeline studio. |
| text | string | oui | Partie texte brut. |
Modèle
| Champ | Type | Requis | Remarques |
| --- | --- | --- | --- |
| to | string | oui | Une seule adresse de destinataire. |
| projectId | string | oui | Doit être un projet que vous possédez — l'URL du studio est /studio/<projectId>. |
| mergeData | object | non | Objet plat { clé: valeur }, jusqu'à 100 champs. Se substitue à une ligne de source de données — se résout dans les balises de fusion {{field}} du projet. Les objets/tableaux imbriqués sont abandonnés ; null/undefined deviennent des chaînes vides ; tout le reste est converti en chaîne. |
| subject | string | non | Par défaut, le nom du projet si omis. Les balises de fusion dans l'objet se résolvent depuis mergeData. Tronqué à 200 caractères. |
Un corps qui ne correspond à aucune des deux formes (par ex. to manquant, ou html/text et projectId manquants ensemble) renvoie 400.
Champs communs
| Champ | Type | Requis | Remarques |
| --- | --- | --- | --- |
| type | "transactional" | "marketing" | non | Par défaut "transactional". Voir ci-dessous. |
| from | object | non | Surcharge par appel de l'adresse/nom d'expéditeur — { "email": string, "name"?: string }. Voir ci-dessous. |
| senderId | string | non | Surcharge par appel en choisissant l'une des identités d'expéditeur enregistrées de votre compte, au lieu d'écrire from en clair. Doit appartenir à votre compte, sinon 400. from prend le dessus si les deux sont fournis. |
| replyTo | string | non | Adresse Reply-To par appel. Voir ci-dessous. |
En-têtes
| En-tête | Requis | Remarques |
| --- | --- | --- |
| Authorization | oui | Bearer <apiKey>. |
| Idempotency-Key | non | Voir idempotence. |
Réponse
{ "id": "abc123", "status": "sent" }
status est l'un des suivants :
| Statut | Signification |
| --- | --- |
| sent | Remis avec succès à votre méthode d'envoi (relais SMTP ou envoi natif). |
| suppressed | Le destinataire figure sur votre liste de suppression — voir transactionnel vs marketing. Aucun email n'a été envoyé ; ce n'est pas une erreur. |
| failed | La tentative d'envoi a échoué (par ex. votre relais SMTP n'est pas configuré, ou a rejeté le message). Un champ error porte une raison lisible par un humain. |
Codes d'erreur
| Statut | Signification |
| --- | --- |
| 400 | JSON malformé, une requête qui ne correspond ni à la forme libre ni à la forme modèle, un type invalide, un from invalide ou non autorisé (voir Adresse d'expéditeur), un replyTo invalide, ou (mode modèle) un projectId qui n'existe pas ou ne vous appartient pas. Également renvoyé lorsque le compte n'a aucune méthode d'envoi fonctionnelle configurée. |
| 401 | En-tête Authorization manquant ou invalide, ou la clé a été révoquée. |
| 404 | (Mode modèle) le projet n'existe pas ou n'appartient pas à votre compte — délibérément la même réponse que « n'existe pas » pour éviter de révéler quels identifiants de projet sont valides pour d'autres comptes. |
| 429 | Limite de débit dépassée — voir limites de débit. |
Un 200 avec status: "failed" (plutôt qu'un statut non-2xx) est renvoyé lorsque la requête elle-même était valide mais que la tentative d'envoi réelle a échoué en aval. Si vous devez distinguer « nous avons rejeté votre requête » de « nous avons essayé et ça n'est pas parti », vérifiez status dans le corps de la réponse, pas seulement le code de statut HTTP.
Transactionnel vs marketing
Le champ type contrôle quelle liste de suppression est vérifiée, à l'image de la façon dont les ESP séparent les flux transactionnels et marketing :
type: "transactional"(par défaut) — contourne la suppression par désabonnement. Une réinitialisation de mot de passe ou un reçu de commande ne devrait pas être bloqué simplement parce que le destinataire s'est désabonné de votre newsletter. Il ne contourne jamais la suppression par rebond — une adresse morte reste morte, quelle que soit l'intention.type: "marketing"— se comporte exactement comme un envoi de campagne depuis le tableau de bord : bloqué à la fois par la suppression par désabonnement et par la suppression par rebond.
Dans les deux cas, status: "suppressed" est renvoyé plutôt qu'une erreur en cas de blocage.
Adresse d'expéditeur
Par défaut, chaque envoi utilise l'identité d'expéditeur configurée de votre compte — la carte « From address » de Settings → Domains si vous utilisez l'envoi natif (SES), ou l'adresse d'expéditeur configurée de votre relais SMTP autrement. Passez from pour la surcharger pour un appel :
{
"to": "[email protected]",
"subject": "Your one-time code",
"html": "<p>Your code is 123456</p>",
"text": "Your code is 123456",
"from": { "email": "[email protected]", "name": "FitConsent Sales Team" }
}
from.email est requis dès que from est présent — il n'y a pas de surcharge du seul nom, donc une valeur ici remplace toujours entièrement à la fois l'adresse et le nom d'affichage pour cet appel. from.name est facultatif ; omettez-le pour envoyer avec la seule adresse nue.
Si votre compte envoie via un domaine vérifié (envoi natif/SES), from.email doit être une adresse sur l'un de vos propres domaines d'envoi vérifiés (par ex. [email protected], pas [email protected]) — un compte peut vérifier plusieurs domaines, donc n'importe lequel d'entre eux est autorisé, mais jamais celui de quelqu'un d'autre. L'envoi natif fonctionne sur un unique compte AWS de la plateforme partagé entre tous les clients MailInApp, c'est donc cette restriction qui empêche un compte d'envoyer du courrier qui semble provenir du domaine vérifié d'un autre compte. Pour l'envoi SMTP, il n'y a pas de telle restriction : l'envoi part via votre propre relais/vos propres identifiants, donc c'est déjà fiable de la même façon que les propres règles de vérification d'expéditeur de votre relais le sont déjà.
senderId (voir le tableau des champs communs ci-dessus) est généralement le choix le plus simple lorsque vous avez déjà configuré une identité d'expéditeur dans le tableau de bord. Il se résout vers le propre {email, name, reply-to} de cette identité sans que vous ayez à les répéter à chaque appel.
Un from invalide ou non autorisé renvoie 400 avant qu'aucun envoi ne soit tenté.
Adresse de réponse (Reply-To)
Par défaut, les réponses partent vers le Reply-To par défaut configuré de votre compte, défini sur la page Sending ou Domains sous Settings selon votre méthode d'envoi, ou nulle part si vous n'en avez pas défini. Passez replyTo pour le surcharger pour un appel — utile lorsque le from visible est une adresse sans réponse mais que vous voulez tout de même qu'un humain voie les réponses :
{
"to": "[email protected]",
"subject": "Your order shipped",
"html": "<p>Your order is on its way.</p>",
"text": "Your order is on its way.",
"from": { "email": "[email protected]", "name": "FitConsent" },
"replyTo": "[email protected]"
}
À la différence de from.email, replyTo n'a aucune restriction de domaine sur l'envoi natif (SES) — il n'affecte jamais l'identité d'envoi ni la réputation de délivrabilité, c'est juste un en-tête que le client de messagerie du destinataire respecte lorsqu'il répond. Un replyTo invalide renvoie 400 avant qu'aucun envoi ne soit tenté.
Idempotence
Passez un en-tête Idempotency-Key sur tout appel susceptible d'être retenté — une redélivrance de webhook de paiement, un consommateur de file retraitant un message. Un appel retenté avec la même clé pour le même compte renvoie le {id, status} de l'appel d'origine sans envoyer un second email, même si le premier appel est encore en cours.
Les clés sont propres à chaque compte et n'ont pas d'expiration ; réutiliser une clé déjà utilisée pour une charge utile différente est de votre responsabilité d'éviter (cela renverra toujours le résultat du premier appel, sans envoyer la nouvelle charge utile). Si vous ne passez pas de clé, chaque appel envoie.
Limites de débit
Trois plafonds indépendants s'appliquent :
- Par clé API : un plafond de requêtes par minute. Le dépasser renvoie
429spécifiquement pour cette clé — les autres clés du même compte ne sont pas affectées. - Par compte, quota de l'API d'envoi : votre forfait inclut un nombre d'appels à l'API d'envoi par mois calendaire (500/mois sur Free). Le dépasser renvoie
429jusqu'à ce que le quota se réinitialise le 1er du mois. - Par compte, volume d'envoi : le plafond cumulé mensuel de volume d'envoi de votre forfait, partagé entre tous les chemins d'envoi (envois depuis le tableau de bord, envois programmés, emails de cycle de vie, et cette API). C'est le même plafond qui protège le reste des envois de votre compte — l'API d'envoi n'a pas de budget séparé.
Spécificités du mode modèle
Un envoi en mode modèle rend le projet lié exactement comme un envoi normal à un destinataire : les trois niveaux du moteur de repli, les blocs interactifs, et un lien de vue en direct personnel et signé, construit à partir de mergeData plutôt que d'une ligne de contact stockée. Tout ce qui suit se comporte comme un envoi piloté par le studio :
- Les votes de sondage, notations, soumissions de formulaire et ouvertures sont enregistrés et apparaissent dans la vue Réponses du projet, attribués à cet appel API spécifique plutôt qu'à une ligne de contact.
- Le webhook de votre projet se déclenche à chaque événement d'interaction, comme pour tout autre destinataire.
- Les appels récents (statut, destinataire, horodatage) sont listés sur la page Developers du tableau de bord à titre de piste d'audit.
La seule différence par rapport à un envoi depuis le tableau de bord : il n'y a pas de ligne de source de données stockée derrière l'interaction, donc les jointures de votre côté doivent se baser sur l'identifiant de destinataire de la réponse plutôt que sur un index de ligne.