Aller à la documentation
API

Envoyer un e-mail

POST /emails : un message, maintenant ou plus tard.

POSTapi.openemail.uk/emails

Exécute le véritable appel sur votre espace de travail, avec votre propre clé.

La requête

from est obligatoire. Contrairement à la fenêtre de rédaction, il n'y a pas d'expéditeur de repli, car ce repli est l'adresse par défaut de l'espace de travail et elle change invisiblement au gré des adresses qui arrivent et qui partent.

ChampObligatoireRemarques
fromouiUne adresse seule ou Name <addr>. Doit être une adresse au nom de laquelle la clé peut envoyer.
toouiJusqu'à 50 destinataires au total entre to, cc et bcc.
cc, bccnonLes destinataires en bcc ne sont jamais nommés dans les octets que reçoivent les autres.
subjectnonVide par défaut.
html, textl'un des deuxLes deux à la fois conviennent. C'est le HTML que voient les destinataires.
templatel'un des deux{ id, version?, props?, slots? }. Un corps enregistré, par id ou par slug. Refusé en même temps que html, text ou draftId. Voir Envoyer avec un modèle.
replyTononUne seule adresse.
headersnonX-*, List-*, Reply-To, Precedence, Auto-Submitted, Importance, Priority, Feedback-Id.
attachmentsnon{ filename, content, contentType } en base64, 5 Mo au total, ou { fileId } désignant un fichier déjà présent dans l'espace de travail. 20 fichiers.
attachmentDeliverynonmime, link ou auto. auto met les fichiers en lien dès qu'ils dépassent 2 Mo sur un domaine doté d'un domaine de fichiers actif. Par défaut, le réglage de la boîte mail.
threadIdnonRépondre dans un fil existant.
draftIdnonEnvoyer un brouillon existant.
scheduledAtnonInstant ISO ou durée. Voir La planification.
cancellableForSecondsnonUne fenêtre d'annulation de 0 à 900 secondes sur un envoi immédiat. Refusé en même temps que scheduledAt, qui reste annulable jusqu'à son départ. Voir La planification.
signaturenonfalse laisse ce message sans signature. Sinon, il porte la signature de l'adresse depuis laquelle il est envoyé, c'est-à-dire la sienne propre ou, à défaut, celle définie pour Toutes les adresses.
tagsnonJusqu'à 10 étiquettes de votre choix. Renvoyées telles quelles, jamais interprétées.
trackingnon{ opens?, clicks? }. L'un ou l'autre remplace le réglage pour ce message ; omettez un champ et cette moitié retombe sur le réglage de l'adresse depuis laquelle le message est envoyé, ou à défaut sur celui de Toutes les adresses, et elle est active à moins que l'un de ces réglages ne l'ait désactivée.
translatenon{ to, from?, subject?, includeOriginal? }. Envoie le message dans la langue du destinataire. Résolu au moment où la requête est acceptée, refusé en même temps que draftId.

Les champs inconnus sont rejetés plutôt qu'ignorés : un nom mal orthographié donne donc un 422 tout de suite, au lieu d'une surprise plus tard. Les en-têtes qui mettraient en échec l'autorisation de l'expéditeur (From, Sender, Bcc, Message-ID, Return-Path et d'autres) sont refusés avec reserved_header.

La réponse

200 quand le message est déjà parti, 202 quand quelque chose doit encore lui arriver. Un appelant qui se décide sur le code de statut a raison dans les deux cas.

200 OK
{  "object": "email",  "id": "msg_c5f21cc6bfec4e848caf905b",  "status": "sent",  "mode": "live",  "from": "[email protected]",  "subject": "Your September invoice",  "messageId": "<2598…@acme.com>",  "transport": "ses",  "sentAt": "2026-08-29T08:19:08.000Z",  "source": "api",  "replayed": false}

id est l'identifiant durable que vous conservez, et celui sur lequel revient un événement de livraison, puisqu'un webhook de rebond le nomme emailId. messageId est le Message-ID RFC 5322 et vaut null tant que le MIME n'existe pas. Ne faites pas de corrélation dessus : le service d'envoi réécrit cet en-tête au départ, si bien que la valeur indiquée ici n'apparaît dans aucun rapport de rebond ou de livraison et qu'une correspondance sur celle-ci ne se déclenche jamais.

Dans la langue du destinataire

translate rédige le message dans la langue de quelqu'un d'autre avant son départ. Le corps, ainsi que l'objet sauf si vous le désactivez, est traduit au moment où la requête est ACCEPTÉE, ce qui est la règle que suit aussi template et qui est structurante pour les mêmes raisons : un message planifié emporte les mots qui ont été approuvés plutôt que ce qu'un modèle produira un mardi, et une traduction qui n'a pas pu être produite refuse l'envoi avant qu'aucune ligne n'existe. Rien n'est distribué dans une langue que son expéditeur n'a pas choisie.

translate

tostringobligatoire
La langue dans laquelle rédiger : un code BCP-47 (`de`), un nom anglais ("German") ou le nom que la langue se donne ("Deutsch"), de 2 à 60 caractères. Les trois formes sont normalisées vers le code de la table avant toute autre chose, si bien qu'elles constituent une seule et même requête, ce qui compte parce que l'empreinte Idempotency-Key est calculée sur la requête analysée. Les alias sont eux aussi résolus : `zh-TW` devient `zh-Hant`. Une valeur qui ne résout vers rien donne un 422 sur `translate.to`.
fromstring
La langue dans laquelle vous l'avez écrit, sous l'une des trois mêmes formes. Purement une optimisation. Si vous l'omettez, le corps est lu et la langue déduite, ce qui coûte un court appel de modèle. À préciser sur un chemin à fort volume, et à préciser quand le corps est surtout fait de noms, de nombres et de liens : la détection s'abstient plutôt que de deviner, et une source indéterminée ne vous coûte que la langue nommée dans la légende au-dessus de votre original. À ne pas confondre avec le `from` de premier niveau, qui est une adresse.
subjectboolean
Traduire aussi la ligne d'objet. Vaut true par défaut ; false envoie l'objet exactement tel que vous l'avez écrit.
includeOriginalboolean
Place ce que vous avez réellement écrit sous la traduction, derrière un séparateur et légendé dans la langue du destinataire. Vaut true par défaut, et mieux vaut le laisser activé. C'est la seule chose qui permet à la personne qui lit de vérifier une phrase qui tombe mal, plutôt que d'avoir à faire confiance à un modèle dont ni l'un ni l'autre ne voit la sortie.
curl
curl -X POST "$OE/emails" -H "$AUTH" -H "Content-Type: application/json" \  -d '{    "from": "[email protected]",    "to": ["[email protected]"],    "subject": "Your September invoice",    "html": "<p>Invoice attached. Payment is due on the 14th.</p>",    "translate": { "to": "de" }  }'
200 OK
{  "object": "email",  "id": "msg_c5f21cc6bfec4e848caf905b",  "status": "sent",  "from": "[email protected]",  "subject": "Ihre Rechnung für September",  "translation": {    "language": "de",    "languageName": "German",    "detectedSourceLanguage": "en",    "subject": true,    "includeOriginal": true  }}

translation est additif et n'apparaît que sur un message qui a été traduit : sur cette réponse et sur GET /emails/{id}, jamais sur une ligne de liste, car une liste ne va pas chercher la requête stockée et son silence à cet endroit ne signifie rien dans un sens ni dans l'autre. Il transporte des codes plutôt que des lignes de langue complètes : c'est un enregistrement de ce qui a été fait, et c'est dans GET /languages que vit l'endonyme. Le subject de la réponse est l'objet traduit, si bien qu'une console ne liste jamais un message sous une chaîne que le destinataire n'a jamais vue.

  • Fonctionne avec template, et c'est là le cas utile : c'est la sortie RENDUE qui est traduite, si bien qu'un seul corps stocké sert toutes les langues dans lesquelles vos clients lisent. Un modèle qui rend un document entier est d'abord décomposé : seul ce qui se trouve à l'intérieur de <body> parvient au modèle, et le doctype, les blocs <style> et les règles @font-face sont replacés autour de la réponse. C'est aussi pourquoi la limite de 30 000 caractères mesure la prose et non le document : un message de deux lignes enveloppé dans une feuille de style de marque reste un message de deux lignes.
  • La seule partie d'un modèle laissée non traduite est son <title>, qu'aucun client de messagerie n'affiche. Un <Preview> react-email est rendu dans le corps et est traduit avec le reste.
  • Refusé avec draftId : un 422 sur translate, indiquant « A draft is sent as it was written; translate a body or send a draft, not both ». Un brouillon a été écrit par une personne et est envoyé tel qu'elle l'a laissé.
  • Délibérément exclu de l'empreinte d'idempotence. Ce qui est haché, c'est la requête que vous avez envoyée, translate compris ; ce que le modèle a produit ne l'est pas. Réessayer un envoi resté sans réponse avec le même Idempotency-Key rejoue donc l'original. Le message qui existe déjà est renvoyé, sans second envoi ni seconde traduction. Hacher la formulation à la place donnerait à un réessai honnête une empreinte différente à chaque fois, et c'est ainsi que le même message part deux fois.
  • Un message traduit qui est en file d'attente ou planifié est figé contre toute modification de formulation. Déplacez-le ou annulez-le ; modifier ce qu'il dit implique de l'annuler et de le renvoyer, devant quelqu'un capable de lire les nouveaux mots.
  • Une langue cible qui s'écrit de droite à gauche est produite de droite à gauche : la traduction est enveloppée dans dir="rtl", votre original en dessous étant orienté pour son propre compte. L'attribut survit au nettoyeur sortant, qui autorise dir précisément pour cette raison, si bien que le message sur le réseau porte la direction qu'affichait l'aperçu.
CodeStatutQuand
`invalid_parameter`422translate.to ou translate.from ne nomme aucune langue que nous puissions situer. Le message indique quelles trois formes sont acceptées et renvoie vers GET /languages.
`unknown_language`422La même erreur, détectée une étape plus tard, par le service plutôt que par le schéma. Un filet de sécurité, sur translate.to.
`translation_too_long`422Plus de 30 000 caractères d'un côté ou de l'autre de l'appel de modèle. Un refus plutôt qu'une troncature : la moitié d'un message traduit ne porte aucune couture indiquant où il s'est arrêté, et la personne qui le lit agit sur la moitié qu'on lui a donnée.
`translation_not_configured`409L'espace de travail n'a pas de clé IA et l'IA de la plateforme est désactivée. Un 409 plutôt qu'un 503, parce que le réessai échoue à l'identique. Rien n'a été envoyé. Envoyez sans translate si vous vouliez l'envoyer tel quel.
`translation_failed`503Le fournisseur n'a pas répondu, ou a répondu sans rien d'exploitable. Rien n'a été envoyé ; le message n'est jamais posté non traduit en repli. Celle-ci est de notre côté et mérite un réessai.
`unknown_parameter`422Une clé non reconnue à l'intérieur de translate, qui est un objet strict comme le reste de la requête.

Un envoi depuis du code n'a personne pour relire la traduction d'abord. POST /emails/translate est le même aller-retour arrêté une étape plus tôt, pour montrer à une personne ce qu'elle s'apprête à envoyer. Envoyez ensuite ce qu'elle a approuvé sous la forme d'un html/subject ordinaire, sans aucun translate sur la requête.