API
API e-mails
Envoyer un e-mail, envoyer un lot de 1 à 100 e-mails et lire le statut d'un envoi, avec les champs, les réponses et les exemples.
Les routes d’envoi créent un e-mail et le mettent dans la file. Il part dans la seconde qui suit, sauf quand Amazon SES demande de ralentir.
Les routes partent de https://api.mail.hgdc.ch (voir Référence de l’API) et demandent toutes Authorization: Bearer am_….
POST /emails
Envoie un e-mail à un ou plusieurs destinataires. Le contenu vient de html, de text, ou d’un modèle.
Paramètres
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
from | string | Oui, sauf avec template | Expéditeur, sous la forme Nom <adresse> ou adresse. Son domaine doit être vérifié dans ton espace, sous-domaines compris. |
to | string ou string[] | Oui | Destinataire ou destinataires. 50 adresses au plus avec cc et bcc. |
subject | string | Oui, sauf avec template | Objet, 998 caractères au plus. |
html | string | Oui, sauf avec text ou template | Contenu HTML, 512 000 caractères au plus. |
text | string | Oui, sauf avec html ou template | Contenu texte, 512 000 caractères au plus. |
template | objet | Non | Un modèle et ses valeurs : { "id": "…", "variables": { … } }. Ne se combine pas avec html ni text. Voir Envoi avec un modèle. |
cc | string ou string[] | Non | Destinataires en copie. |
bcc | string ou string[] | Non | Destinataires en copie cachée. |
reply_to | string ou string[] | Non | Adresse de réponse. |
headers | objet | Non | En-têtes supplémentaires, sous forme de paires texte à texte. |
attachments | tableau | Non | Pièces jointes, 40 Mo au plus par e-mail une fois encodées en base64. Voir Pièces jointes. |
tags | tableau | Non | Au plus 10 paires { "name": "…", "value": "…" }. Chaque nom et valeur ne contient que des lettres, des chiffres, _ ou -, et tient en 256 caractères au plus. Le nom email_id est réservé. |
Pas encore pris en charge : scheduled_at, react et topic_id. Une requête qui les contient reçoit 422, pour qu’un envoi programmé ne disparaisse pas en silence.
En-tête optionnel : Idempotency-Key, pour éviter un doublon en cas de nouvel essai. Voir Envoyer sans doublon.
Exemple
curl -X POST https://api.mail.hgdc.ch/emails \ -H "Authorization: Bearer $AGENTMAIL_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: facture-118" \ -d '{ "from": "Factures <factures@envoi.atelier.ch>", "to": "client@exemple.ch", "subject": "Ta facture d'\''octobre", "html": "<p>Merci pour ta commande.</p>", "tags": [{ "name": "type", "value": "facture" }] }'const response = await fetch("https://api.mail.hgdc.ch/emails", { method: "POST", headers: { Authorization: `Bearer ${process.env.AGENTMAIL_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "facture-118", }, body: JSON.stringify({ from: "Factures <factures@envoi.atelier.ch>", to: "client@exemple.ch", subject: "Ta facture d'octobre", html: "<p>Merci pour ta commande.</p>", tags: [{ name: "type", value: "facture" }], }),})const { id } = await response.json()import { Agentmail } from "./agentmail"
const mail = new Agentmail()const { id } = await mail.emails.send( { from: "Factures <factures@envoi.atelier.ch>", to: "client@exemple.ch", subject: "Ta facture d'octobre", html: "<p>Merci pour ta commande.</p>", tags: [{ name: "type", value: "facture" }], }, { idempotencyKey: "facture-118" },)Réponse
Statut 200. L’id sert à relire l’e-mail avec GET /emails/:id.
{ "id": "kd7b3m9q2x8r4v6n1c0z5h7t"}Pièces jointes
Chaque élément de attachments suit la forme de Resend :
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
filename | string | Oui | Nom du fichier tel que le destinataire le voit. Son extension donne le type quand content_type manque. |
content | string | Oui | Le fichier encodé en base64. |
content_type | string | Non | Type du fichier, par exemple application/pdf ou text/calendar; method=REQUEST pour une invitation d’agenda. |
content_id | string | Non | Pour une image dans le corps de l’e-mail : le HTML l’appelle par <img src="cid:…">. Moins de 128 caractères. |
{ "from": "Factures <factures@ton-domaine.ch>", "to": "client@exemple.ch", "subject": "Votre facture", "html": "<p>Votre facture est jointe.</p>", "attachments": [ { "filename": "facture-118.pdf", "content": "JVBERi0xLjQK…" } ]}- 40 Mo au plus par e-mail, comptés après encodage en base64, et 100 pièces jointes au plus.
- Les fichiers exécutables sont refusés (
.exe,.bat,.js,.msi,.vbs,.ps1…, même liste que Resend) :422avecname: "invalid_attachment". path(un fichier à télécharger depuis une adresse) n’est pas encore pris en charge : envoie le fichier danscontent.- Mail garde le fichier jusqu’à l’envoi, puis l’efface. Le journal ne montre que son nom, son type et sa taille.
Envoi avec un modèle
Au lieu de html et text, passe un modèle et ses valeurs. Le modèle se crée dans l’application, l’API le lit (voir API modèles).
curl -X POST https://api.mail.hgdc.ch/emails \ -H "Authorization: Bearer $AGENTMAIL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "lea@exemple.ch", "template": { "id": "k57w8c2n5d9f1h6j3m0p4q7r", "variables": { "entreprise": "Atelier Nord" } } }'import { Agentmail } from "./agentmail"
const mail = new Agentmail()const { id } = await mail.emails.send({ to: "lea@exemple.ch", template: { id: "k57w8c2n5d9f1h6j3m0p4q7r", variables: { entreprise: "Atelier Nord" }, },})La réponse est la même que pour un envoi direct : { "id": "…" }.
- L’expéditeur et l’objet du modèle servent par défaut. Envoie
fromousubjectpour les remplacer. - Une variable sans valeur ni repli bloque l’envoi : rien ne part, et la réponse est un
422qui nomme la variable, par exempletemplate.variables : valeur manquante pour prenom. - Un modèle sans contenu, ou inconnu de ton espace, renvoie
422. - Si
tocontient une seule adresse et que c’est déjà un contact, ses champs servent de valeurs :prenom,nom,email, puis ses propriétés. Les valeurs devariablesl’emportent sur elles.
Les variables s’écrivent {{prenom}}, avec une valeur de secours après une barre verticale : {{prenom|à toi}}. La liste complète est dans Variables.
Lien de désabonnement
Mets {{lien_desabonnement}} dans le HTML ou le texte d’un e-mail envoyé à une seule adresse dans to. À l’envoi, la variable est remplacée par le lien propre au destinataire, et les en-têtes List-Unsubscribe sont ajoutés. Les boîtes de réception affichent alors un bouton de désabonnement.
Sans cette variable, l’e-mail n’a pas de lien de désabonnement : c’est normal pour un e-mail transactionnel. Un envoi à plusieurs adresses dans to n’en reçoit pas non plus.
Le lien passe par GET /unsubscribe?token=…, qui redirige vers la page de désabonnement de Mail. POST /unsubscribe?token=… sert au désabonnement en un clic, demandé par les boîtes de réception.
Mention de pied de page
Chaque e-mail envoyé par l’API porte la mention « Propulsé par HGDC », en HTML comme en texte. Elle est ajoutée à l’envoi, et ni la clé ni le modèle ne peuvent la retirer.
Liens et suivi
Les liens d’un e-mail envoyé par l’API arrivent tels que tu les as écrits : aucune redirection de suivi, pour qu’un lien d’invitation ou de connexion s’ouvre aussi chez qui bloque le pistage. Les ouvertures sont mesurées, pas les clics : un e-mail de l’API ne passe jamais au statut clicked.
POST /emails/batch
Envoie de 1 à 100 e-mails en une requête.
Le corps est un tableau de 1 à 100 objets, chacun au format de POST /emails, pièces jointes comprises (42,5 Mo au plus pour tout le lot). Le lot est tout ou rien : soit tous les e-mails sont créés, soit aucun.
Si un élément est invalide (422), si le débit ou le quota du jour est atteint (429) ou si un domaine n’est pas vérifié, aucun e-mail du lot n’est créé. Tu peux relancer le même lot sans risque. Avec une Idempotency-Key, chaque élément reçoit sa propre clé (<clé>:<position>) : un lot déjà accepté, rejoué, renvoie les mêmes id sans second envoi.
curl -X POST https://api.mail.hgdc.ch/emails/batch \ -H "Authorization: Bearer $AGENTMAIL_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: factures-octobre" \ -d '[ { "from": "Factures <factures@envoi.atelier.ch>", "to": "client@exemple.ch", "subject": "Ta facture 118", "html": "<p>Merci pour ta commande.</p>" }, { "from": "Factures <factures@envoi.atelier.ch>", "to": "lea@exemple.ch", "subject": "Ta facture 119", "html": "<p>Merci pour ta commande.</p>" } ]'import { Agentmail } from "./agentmail"
const mail = new Agentmail()const { data } = await mail.emails.batch( [ { from: "Factures <factures@envoi.atelier.ch>", to: "client@exemple.ch", subject: "Ta facture 118", html: "<p>Merci pour ta commande.</p>", }, { from: "Factures <factures@envoi.atelier.ch>", to: "lea@exemple.ch", subject: "Ta facture 119", html: "<p>Merci pour ta commande.</p>", }, ], { idempotencyKey: "factures-octobre" },)Réponse : statut 200, avec un id par élément, dans l’ordre du tableau.
{ "data": [ { "id": "kd7b3m9q2x8r4v6n1c0z5h7t" }, { "id": "kf2n8w4c6r1x9m3q7b5z0v2j" } ]}GET /emails/:id
Renvoie un e-mail de ton espace. Une clé ne voit jamais l’e-mail d’un autre espace.
Une clé « envoi seulement » peut aussi appeler cette route, mais seulement pour les e-mails qu’elle a elle-même envoyés. Pour un e-mail envoyé par une autre clé, ou avant que Mail enregistre la clé d’origine (2026-10-17), elle reçoit le même 404 que pour un identifiant inconnu. Une clé à accès complet lit tous les e-mails de l’espace.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
id | string | Oui, dans le chemin | Identifiant renvoyé par POST /emails. |
curl https://api.mail.hgdc.ch/emails/kd7b3m9q2x8r4v6n1c0z5h7t \ -H "Authorization: Bearer $AGENTMAIL_API_KEY"import { Agentmail } from "./agentmail"
const mail = new Agentmail()const email = await mail.emails.get("kd7b3m9q2x8r4v6n1c0z5h7t")Réponse
Statut 200. to est toujours un tableau, même si l’envoi a donné une chaîne.
{ "object": "email", "id": "kd7b3m9q2x8r4v6n1c0z5h7t", "from": "Factures <factures@envoi.atelier.ch>", "to": ["client@exemple.ch"], "subject": "Ta facture d'octobre", "created_at": "2026-10-10T08:00:00.000Z", "last_event": "delivered"}last_event prend l’une des valeurs de Statuts des e-mails. Si l’e-mail n’existe pas dans ton espace, ou si une clé « envoi seulement » ne l’a pas envoyé, la réponse est un 404 avec name: "not_found".
Si ça ne marche pas
422 validation_erroravecfrom: l’adresse n’appartient pas à un domaine vérifié de ton espace, ou le domaine n’est pas encore Prêt.422surscheduled_at: l’envoi programmé n’est pas encore pris en charge. Retire-le.422 invalid_attachment: la pièce jointe n’a pas decontent, utilisepath, ou son extension est refusée.413 payload_too_large: le corps dépasse 42,5 Mo. Une pièce jointe de plus de 2,5 Mo demande aussi l’en-têteContent-Length, quefetchet les SDK envoient d’eux-mêmes.- Le lot est refusé en entier : un seul élément est invalide. Corrige l’élément invalide, puis renvoie le lot complet.
Cette page t'a aidé ?
Merci, c'est noté.