Envoyer un e-mail
POST /v1/email/messages envoie un e-mail. Fournissez un expéditeur, des destinataires et un contenu dans un payload JSON. Le API renvoie 202 Accepted avec un ID de message, puis distribue l'e-mail de manière asynchrone. Consultez la référence API pour les schémas complets.
Un envoi minimal
Le plus petit payload valide comprend un from, au moins un destinataire to, un subject et un corps (html, text, ou les deux). L'adresse from doit appartenir à un domaine que vous avez vérifié dans cet espace de travail, ou au domaine d'onboarding.
const msg = await bird.email.send({
from: { email: "onboarding@messagebird.dev", name: "Bird" },
to: ["delivered@messagebird.dev"],
subject: "Hello from Bird",
html: "<p>My first Bird email.</p>",
});
console.log(msg.id, msg.status); // "em_…", "accepted"msg = client.email.send(
from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
to=["delivered@messagebird.dev"],
subject="Hello from Bird",
html="<p>My first Bird email.</p>",
)
print(msg.id, msg.status)package main
import (
"context"
"fmt"
"log"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
msg, err := client.Email.Send(context.Background(), bird.EmailSendParams{
From: "onboarding@messagebird.dev",
To: []string{"delivered@messagebird.dev"},
Subject: "Hello from Bird",
HTML: "<p>My first Bird email.</p>",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$message = $bird->email->send(
from: 'Bird <onboarding@messagebird.dev>',
to: ['delivered@messagebird.dev'],
subject: 'Hello from Bird',
html: '<p>My first Bird email.</p>',
);
echo $message->getId(), ' ', $message->getStatus();bird email send \
--from hello@yourdomain.com \
--html '<p>It works.</p>' \
--subject 'Hello from Bird' \
--to delivered@messagebird.devcurl -X POST https://us1.platform.bird.com/v1/email/messages \
-H "Authorization: Bearer bk_us1_..." \
-H "Content-Type: application/json" \
-d '{
"from": "hello@yourdomain.com",
"to": ["delivered@messagebird.dev"],
"subject": "Hello from Bird",
"html": "<p>It works.</p>"
}'Utilisez votre hôte régional (https://us1.platform.bird.com ou https://eu1.platform.bird.com) avec une clé bk_{region}_... correspondante.
L'exemple d'envoi utilise delivered@messagebird.dev, une adresse sandbox qui accepte toujours le courrier. Le API rejette les domaines fictifs avec une 422 : example.com, example.net, example.org, example.edu, test.com, et tout ce qui se trouve sous les TLD réservés .test, .example, .invalid ou .localhost. Un envoi vers ces domaines ne peut que rebondir, ce qui dégrade votre réputation d'expéditeur.
Envoyer avant d'avoir vérifié un domaine
Pendant l'onboarding, vous pouvez envoyer depuis notre domaine d'onboarding partagé, onboarding@messagebird.dev. Ces envois contournent la vérification de domaine, mais n'atteignent que les membres vérifiés de votre propre espace de travail et les adresses sandbox, dans la limite d'un plafond quotidien de destinataires. Le quickstart détaille les règles et les limites exactes.
Construire le payload
Destinataires
to, cc et bcc acceptent chacun jusqu'à 50 adresses, et to en exige au moins une. Chaque entrée est une chaîne d'adresse e-mail simple, une chaîne de boîte aux lettres RFC 5322 (Jane <jane@acme.com>) ou un objet avec un nom d'affichage facultatif.
Les destinataires figurant sur la liste de suppression de l'espace de travail ne font pas échouer la requête. Elle renvoie toujours un 202, et chaque destinataire supprimé apparaît sur les endpoints de lecture avec le statut status: rejected et la raison recipient_suppressed, y compris lorsque tous les destinataires de l'envoi sont concernés.
Contenu
subject est requis pour les envois inline, jusqu'à 998 caractères. Fournissez html, text, ou les deux, chacun jusqu'à 524 288 caractères. Envoyez les deux quand c'est possible : un client incapable d'afficher le HTML se rabat sur la partie texte.
Pour personnaliser le contenu inline, placez des tokens {{ variable }} dans le sujet ou le corps et transmettez leurs valeurs dans parameters, jusqu'à 16 Ko sérialisés. Un seul jeu de valeurs couvre tous les destinataires de l'envoi, et un token sans clé correspondante s'affiche vide. Pour du contenu que vous réutilisez, envoyez plutôt un template.
Incluez parameters, même en tant qu'objet vide ({}), pour traiter le sujet et le corps comme du Liquid. Omettez-le pour envoyer les tokens tels que {{ animal }} tels quels. Chaque nom de paramètre est un mot unique, par exemple first_name ; les noms avec points et le nom réservé bird sont rejetés. Une syntaxe Liquid invalide ou des tags et filtres non pris en charge renvoient 422.
Les valeurs insérées dans du HTML sont échappées afin qu'elles ne puissent pas modifier le balisage environnant. Pour un lien complet ou une URL d'image, utilisez {{ link }} sans url_encode. Pour une valeur dans un paramètre d'URL, encodez-la explicitement, par exemple https://example.com/search?q={{ query | url_encode }}.
Reply-to et en-têtes personnalisés
reply_to accepte de 1 à 25 adresses, aux mêmes formats que les destinataires. Chaque réponse d'un destinataire est envoyée à toutes ces adresses, donc une ou deux est la norme.
headers est un objet chaîne-vers-chaîne pour vos propres en-têtes, par exemple {"X-Campaign": "spring-2026"}, limité à 25 en-têtes avec des valeurs jusqu'à 998 caractères. Trois types d'en-tête sont renvoyés comme 422 :
- En-têtes d'adressage et de plateforme. Définissez l'adressage du message via les champs dédiés (from, to, cc, bcc, reply_to, subject). Ces noms, ainsi que les en-têtes que nous générons pour vous (Content-Type, Content-Transfer-Encoding, DKIM-Signature, Received, Return-Path), ne peuvent pas être définis ici.
- List-Unsubscribe et List-Unsubscribe-Post sur un envoi marketing. Nous définissons nous-mêmes un en-tête de désinscription en un clic conforme sur ces envois. Sur un envoi transactional, nous laissons les vôtres tels que vous les avez définis.
- Toute valeur contenant un retour chariot ou un saut de ligne.
Suivi
track_opens et track_clicks ont tous deux la valeur par défaut true. Définissez l'un ou l'autre à false pour ne pas injecter le pixel d'ouverture ou ne pas réécrire les liens sur cet envoi. Suivi et métriques détaille ce que chacun modifie dans le message.
Catégorie et pool d'IP
category classifie le contenu et définit la politique de suppression : marketing bloque la distribution pour toute raison de suppression et tout opt-out, et transactional distribue malgré une suppression pour plainte ou un opt-out marketing uniquement (un opt-out enregistré pour tous les messages le bloque aussi). La valeur par défaut est la catégorie du template sur un envoi par template et marketing sinon, donc définissez transactional explicitement pour les reçus, les réinitialisations de mot de passe et tout autre courrier opérationnel. Catégories détaille le choix. Le courrier soumis via SMTP prend sa catégorie depuis la configuration SMTP de la clé.
ip_pool_id choisit le pool d'envoi : un ID de pool (ipp_...), ou ipp_shared pour router explicitement par le pool partagé. Omettez-le pour utiliser le pool par défaut de votre organisation. Un pool inconnu, ou un pool sans IP dédiées disponibles pour l'envoi, est rejeté avec une 422.
Référence des champs
| Champ | Type | Requis | Limites et notes |
|---|---|---|---|
| from | address | oui | Doit appartenir à un domaine vérifié ou au domaine d'onboarding |
| to | address[] | oui | 1 à 50 |
| cc, bcc | address[] | non | Jusqu'à 50 chacun |
| subject | string | envois inline | Jusqu'à 998 caractères ; à omettre pour les envois par template |
| html, text | string | au moins un | Jusqu'à 524 288 caractères chacun ; à omettre pour les envois par template |
| reply_to | address[] | non | 1 à 25 ; les réponses arrivent à toutes les adresses listées |
| headers | object (string → string) | non | Jusqu'à 25 ; noms réservés rejetés (voir en-têtes personnalisés) |
| parameters | object | non | Valeurs pour {{ tokens }} dans le contenu inline ; jusqu'à 16 Ko sérialisés ; partagées entre les destinataires |
| tags | {name, value}[] | non | Jusqu'à 20 ; nom ≤ 32 car., valeur ≤ 64 car. ; [A-Za-z0-9_-] uniquement ; noms uniques par envoi |
| metadata | object | non | JSON arbitraire, jusqu'à 2 Ko sérialisés |
| track_opens | boolean | non | Par défaut true |
| track_clicks | boolean | non | Par défaut true |
| category | string | non | marketing ou transactional ; par défaut celle du template pour un envoi par template, sinon marketing |
| ip_pool_id | string | non | ipp_... ou ipp_shared ; à omettre pour le pool par défaut de votre organisation |
| template | object | non | Envoie un template publié par id ou slug, avec parameters pour ses variables et un language facultatif |
| attachments | object[] | non | Jusqu'à 20 ; voir pièces jointes |
| scheduled_at | RFC 3339 timestamp | non | Planifier du contenu inline ou un template ; voir envoi planifié |
Envoyer avec un template
Au lieu de contenu inline, envoyez un template publié : définissez template comme un objet le désignant par id (emt_...) ou par slug, exactement l'un des deux, avec les valeurs de ses variables dans template.parameters. Omettez subject, html et text, car le template les contient déjà.
const msg = await bird.email.send({
from: { email: "onboarding@messagebird.dev", name: "Bird" },
to: ["delivered@messagebird.dev"],
category: "transactional",
template: {
slug: "welcome-email",
parameters: { first_name: "Jane" },
},
});
console.log(msg.id, msg.status);msg = client.email.send(
from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
to=["delivered@messagebird.dev"],
category="transactional",
template="welcome-email",
parameters={"first_name": "Jane"},
)
print(msg.id, msg.status)msg, err := client.Email.Send(context.Background(), bird.EmailSendParams{
From: "onboarding@messagebird.dev",
To: []string{"delivered@messagebird.dev"},
Category: "transactional",
Template: "welcome-email",
Parameters: map[string]any{"first_name": "Jane"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)$message = $bird->email->send(
from: 'Bird <onboarding@messagebird.dev>',
to: ['delivered@messagebird.dev'],
category: 'transactional',
template: (new EmailMessageSendRequestTemplate())
->setSlug('welcome-email')
->setParameters(['first_name' => 'Jane']),
);
echo $message->getId(), ' ', $message->getStatus();bird email send \
--category transactional \
--from hello@yourdomain.com \
--parameters '{"first_name":"Jane"}' \
--template welcome-email \
--to delivered@messagebird.devcurl -X POST https://us1.platform.bird.com/v1/email/messages \
-H "Authorization: Bearer bk_us1_..." \
-H "Content-Type: application/json" \
-d '{
"from": "hello@yourdomain.com",
"to": ["delivered@messagebird.dev"],
"category": "transactional",
"template": {
"slug": "welcome-email",
"parameters": { "first_name": "Jane" }
}
}'Le contenu d'un template est du Liquid, donc en plus de la simple substitution {{ variable }}, il peut utiliser des filtres, des conditionnelles {% if %} et des boucles {% for %}. Personnaliser avec des variables liste les quelques constructions qu'une publication rejette. template.parameters est l'endroit où vous placez les valeurs des paramètres propres au template, indexées par nom. Omettez-en un et l'envoi est rejeté avec une 422 le nommant. Tout le reste de l'envoi se comporte comme en mode inline, y compris les destinataires, tags, metadata, le suivi et les pièces jointes. Ce qui est propre à un envoi par template :
- Inline ou par template, jamais les deux. Envoyer template en même temps que subject, html ou text est rejeté avec une 422. Le API rejette aussi les valeurs de variables dans le champ de premier niveau parameters ; pour un envoi par template, elles vont dans template.parameters.
- bird est le seul nom réservé. Un chemin de placeholder commençant par bird. désigne nos propres données, comme le lien de désinscription ou la fiche contact du destinataire, donc une clé template.parameters ne peut pas s'appeler bird. Toute autre clé est libre, et chacune est un mot simple : {"order_number": "A-1043"} remplit {{ order_number }}.
- Un template peut être envoyé maintenant ou plus tard. Ajoutez scheduled_at pour planifier l'envoi. Nous figeons la version publiée, la langue sélectionnée et les valeurs des paramètres à l'acceptation. Si vous supprimez le template avant l'heure d'envoi, le message est rejeté avec generation_failure.
- Un envoi utilise la version publiée du template. Les brouillons ne sont jamais envoyés. Un template inconnu est rejeté avec une 404, et un template sans version publiée avec une 422.
- language sélectionne l'une des langues du template. Omettez-le pour envoyer la langue par défaut du template. Demandez une langue que le template n'a pas, et son propre paramètre on_missing_language décide si la correspondance la plus proche est envoyée à la place ou si l'envoi est rejeté. Un template qui définit language_source_required rejette un envoi qui ne nomme aucune langue.
- La catégorie du template est une valeur par défaut, et la vôtre la remplace. Omettez category et l'envoi hérite de celle du template, de sorte qu'un template transactionnel n'a pas besoin qu'elle soit répétée à chaque appel.
Templates d'e-mail couvre la création, la publication et les constructions qu'un template peut contenir.
Tags vs métadonnées
Les deux attachent vos propres données à un envoi, et ils diffèrent par la façon dont vous les interrogez ensuite :
- Les tags sont des paires {name, value} structurées : jusqu'à 20 par envoi, nom jusqu'à 32 caractères, valeur jusqu'à 64, lettres ASCII, chiffres, underscore et tiret uniquement, et noms uniques au sein de l'envoi. Les tags sont des dimensions de filtrage : vous pouvez filtrer la liste de messages par tag et découper les analyses et les agrégations du tableau de bord par tag. Utilisez-les pour des libellés à faible cardinalité comme campaign, experiment_variant ou source.
- metadata est un objet JSON arbitraire, jusqu'à 2 Ko sérialisés. Nous le stockons, le renvoyons sur les lectures API et le reproduisons sur chaque événement webhook, ce qui convient au contexte que vous voulez récupérer : identifiants internes, clés étrangères, payloads structurés.
Chaque événement webhook inclut les deux avec les identifiants de corrélation (email_id, recipient_id), ce qui vous permet de rapprocher vos propres enregistrements sans seconde requête. Les noms de tags et les clés de métadonnées de premier niveau commençant par __bird sont rejetés. Vous n'avez pas besoin d'encoder l'appareil, la géographie, le fournisseur de boîte aux lettres, le type de rebond ou le domaine du destinataire dans ces champs, car nous capturons chacun d'eux en tant que dimension d'analyse.
Exemple de code
{
"tags": [{ "name": "campaign", "value": "onboarding" }],
"metadata": { "user_id": "usr_12345", "order_id": "ord_98765" }
}Pièces jointes
attachments accepte jusqu'à 20 fichiers par message, sous forme d'octets encodés en base64 inline. Nous rejetons un envoi dont la taille estimée du message généré dépasse 20 Mo, mesurée après l'encodage base64 : gardez donc le contenu brut des pièces jointes à 15 Mo ou moins pour conserver une marge. Pièces jointes détaille le contrat de champs, les images inline, les types de fichier bloqués et la façon de retélécharger une pièce jointe.
Ce que signifie un 202
Un envoi réussi renvoie 202 Accepted avec un identifiant de message préfixé par em_ et status: accepted :
Exemple de code
{
"id": "em_01ky7ma8y2es1s2akzk53tmjn0",
"status": "accepted",
"category": "marketing",
"from": { "email": "hello@yourdomain.com" },
"to": [{ "email": "delivered@messagebird.dev" }],
"subject": "Hello from Bird",
"accepted_count": 1,
"processed_count": 0,
"delivered_count": 0,
"deferred_count": 0,
"bounced_count": 0,
"complained_count": 0,
"rejected_count": 0,
"open_count": 0,
"click_count": 0,
"track_opens": true,
"track_clicks": true,
"created_at": "2026-07-23T13:58:20.866Z"
}Le 202 signifie que nous avons accepté l'envoi de manière durable. Les erreurs que vous pouvez corriger reviennent directement sur la requête sous forme de 422 : un domaine d'expéditeur non vérifié, ou un champ invalide. Les résultats par destinataire (distribué, rejeté, différé, signalé) arrivent ensuite via les webhooks et les endpoints de lecture du message.
Deux conséquences en découlent :
- Les lectures renvoient l'état sans le corps du message. GET /v1/email/messages/{message_id} renvoie l'état du message et des destinataires, jamais le corps html ou text. Lorsque le stockage du contenu est activé pour l'espace de travail, les corps stockés restent disponibles pendant 30 jours maximum depuis GET /v1/email/messages/{message_id}/content.
- Une lecture peut brièvement être en retard sur l'envoi. Un 404 sur les endpoints de lecture juste après un 202 signifie que le message n'est pas encore visible : réessayez après un court instant.
Réessayer en toute sécurité
Envoyez un en-tête Idempotency-Key avec une valeur unique par envoi logique. Si une requête a réussi mais que vous n'avez jamais reçu la réponse, rejouez-la avec la même clé. Le API renvoie le résultat d'origine au lieu d'envoyer un second e-mail et inclut un en-tête Idempotency-Replay. Idempotence décrit le format de clé et la durée de rétention.
Envoi par lot
Pour réduire les requêtes API, POST /v1/email/batches accepte jusqu'à 100 messages indépendants et les valide comme une seule unité. Appeler l'endpoint d'envoi unitaire en boucle est également possible. Chaque élément du lot utilise le payload décrit sur cette page, scheduled_at compris, de sorte qu'un même lot peut mêler messages immédiats et planifiés.
Facturation
Les envois d'e-mails sont comptabilisés par destinataire sur le quota mensuel de votre plan : un message à trois destinataires consomme trois envois. Facturation et utilisation décrit le modèle de comptage et la lecture en temps réel de la consommation.
Étapes suivantes
- Modèles d'e-mail : créer et publier les modèles que vous envoyez ici
- Catégories : comment marketing et transactional modifient le comportement de suppression
- Suppressions : à qui nous ne distribuons pas, et pourquoi
- Envoi planifié : distribuer à une date future avec scheduled_at
- Bac à sable de test : destinataires sandbox et envoi avant vérification
- Référence API : les schémas complets de requête et de réponse
Ressources associées
Poursuivez avec la documentation, les guides et les exemples sur ce sujet. Les ressources sont en anglais.
Regarder le guideOrder confirmation emailsExplorer la fonctionnalitéOrder confirmation emailsSuivre le parcours d'apprentissageBuild your first integration
Essayez la pratique et obtenez un guide d'implémentation