Aller au contenu

Tape un mot pour chercher dans les pages.

↑↓ Choisir↵ OuvrirÉchap Fermer

API

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

ChampTypeObligatoireDescription
fromstringOui, sauf avec templateExpéditeur, sous la forme Nom <adresse> ou adresse. Son domaine doit être vérifié dans ton espace, sous-domaines compris.
tostring ou string[]OuiDestinataire ou destinataires. 50 adresses au plus avec cc et bcc.
subjectstringOui, sauf avec templateObjet, 998 caractères au plus.
htmlstringOui, sauf avec text ou templateContenu HTML, 512 000 caractères au plus.
textstringOui, sauf avec html ou templateContenu texte, 512 000 caractères au plus.
templateobjetNonUn modèle et ses valeurs : { "id": "…", "variables": { … } }. Ne se combine pas avec html ni text. Voir Envoi avec un modèle.
ccstring ou string[]NonDestinataires en copie.
bccstring ou string[]NonDestinataires en copie cachée.
reply_tostring ou string[]NonAdresse de réponse.
headersobjetNonEn-têtes supplémentaires, sous forme de paires texte à texte.
attachmentstableauNonPièces jointes, 40 Mo au plus par e-mail une fois encodées en base64. Voir Pièces jointes.
tagstableauNonAu 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 :

ChampTypeObligatoireDescription
filenamestringOuiNom du fichier tel que le destinataire le voit. Son extension donne le type quand content_type manque.
contentstringOuiLe fichier encodé en base64.
content_typestringNonType du fichier, par exemple application/pdf ou text/calendar; method=REQUEST pour une invitation d’agenda.
content_idstringNonPour 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) : 422 avec name: "invalid_attachment".
  • path (un fichier à télécharger depuis une adresse) n’est pas encore pris en charge : envoie le fichier dans content.
  • 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 from ou subject pour les remplacer.
  • Une variable sans valeur ni repli bloque l’envoi : rien ne part, et la réponse est un 422 qui nomme la variable, par exemple template.variables : valeur manquante pour prenom.
  • Un modèle sans contenu, ou inconnu de ton espace, renvoie 422.
  • Si to contient 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 de variables l’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.

ChampTypeObligatoireDescription
idstringOui, dans le cheminIdentifiant 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_error avec from : l’adresse n’appartient pas à un domaine vérifié de ton espace, ou le domaine n’est pas encore Prêt.
  • 422 sur scheduled_at : l’envoi programmé n’est pas encore pris en charge. Retire-le.
  • 422 invalid_attachment : la pièce jointe n’a pas de content, utilise path, 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ête Content-Length, que fetch et 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.