Envoi programmé
Définissez scheduled_at pour retenir un message jusqu'à une heure précise. Lorsque cette heure arrive, le message entre dans le cycle de livraison normal et produit les mêmes événements qu'un envoi immédiat. Votre application n'a pas besoin de gérer son propre planificateur.
Programmer un envoi
Ajoutez un horodatage scheduled_at à un envoi POST /v1/email/messages normal. Rien d'autre ne change dans le payload.
const msg = await bird.email.send({
from: "news@yourdomain.com",
to: ["delivered@messagebird.dev"],
subject: "Your weekly digest",
html: "<p>Here is what happened this week...</p>",
category: "marketing",
scheduled_at: "2027-01-15T09:00:00Z",
});
console.log(msg.id, msg.status); // "em_…", "accepted"msg = client.email.send(
from_="news@yourdomain.com",
to=["delivered@messagebird.dev"],
subject="Your weekly digest",
html="<p>Here is what happened this week...</p>",
category="marketing",
scheduled_at="2027-01-15T09:00:00Z",
)
print(msg.id, msg.status)package main
import (
"context"
"fmt"
"log"
"os"
"time"
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: "news@yourdomain.com",
To: []string{"delivered@messagebird.dev"},
Subject: "Your weekly digest",
HTML: "<p>Here is what happened this week...</p>",
Category: bird.CategoryMarketing,
ScheduledAt: time.Date(2026, 7, 30, 9, 0, 0, 0, time.UTC),
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$message = $bird->email->send(
from: 'news@yourdomain.com',
to: ['delivered@messagebird.dev'],
subject: 'Your weekly digest',
html: '<p>Here is what happened this week...</p>',
category: 'marketing',
scheduledAt: new \DateTimeImmutable('2027-01-15T09:00:00Z'),
);
echo $message->getId(), ' ', $message->getStatus();bird email send \
--from news@yourdomain.com \
--to delivered@messagebird.dev \
--subject 'Your weekly digest' \
--html '<p>Here is what happened this week...</p>' \
--category marketing \
--scheduled-at 2027-01-15T09:00:00Zcurl -X POST https://us1.platform.bird.com/v1/email/messages \
-H "Authorization: Bearer bk_us1_..." \
-H "Content-Type: application/json" \
-d '{
"from": "news@yourdomain.com",
"to": ["delivered@messagebird.dev"],
"subject": "Your weekly digest",
"html": "<p>Here is what happened this week...</p>",
"category": "marketing",
"scheduled_at": "2027-01-15T09:00:00Z"
}'L'appel renvoie immédiatement 202 Accepted avec l'ID de message préfixé par em_ et status: accepted. C'est le même objet message qu'un envoi immédiat renvoie, plus scheduled_at renvoyé en UTC, ce qui vous permet de confirmer l'heure d'envoi sans lecture supplémentaire. L'acceptation est synchrone ; la livraison est différée. Omettez scheduled_at de la requête et le message part tout de suite ; sa réponse ne contient alors pas de clé scheduled_at.
Sur les endpoints de lecture, le message affiche status: scheduled avec son scheduled_at jusqu'à l'arrivée de l'heure d'envoi :
Exemple de code
{
"id": "em_01ky7q24hafjgvzfg02v3m177p",
"status": "scheduled",
"scheduled_at": "2027-01-15T09:00:00Z",
"category": "marketing"
}Lorsque l'heure arrive, nous libérons le message et son statut progresse à travers les états habituels (accepted, puis processed, puis delivered, etc.). scheduled_at reste défini après coup, ce qui vous permet de toujours voir pour quand un message était programmé.
La programmation consomme une unité de l'enveloppe d'e-mails programmés de votre organisation pour la période de facturation. Dépasser cette enveloppe est rejeté avec un 422 (E10003).
Un envoi programmé utilise du contenu inline
scheduled_at et template sont mutuellement exclusifs, et un envoi qui définit les deux est rejeté avec un 422. C'est le contrat : un envoi programmé possède son propre subject et son corps, tandis qu'un envoi par template part immédiatement. Pour programmer du contenu de template, effectuez d'abord le rendu de son sujet et de son corps. Le tableau de bord et bird CLI prévisualisent le sujet, le HTML et le texte exacts qu'un envoi par template livrerait. Programmez ces valeurs rendues comme contenu inline.
Un élément de batch accepte scheduled_at aux mêmes conditions, ce qui permet à un batch de mélanger messages programmés et immédiats. Chaque élément programmé consomme sa propre unité de l'enveloppe, et le batch entier est rejeté si l'heure d'un élément est hors de la plage autorisée. Chaque élément programmé porte son propre scheduled_at dans la réponse du batch, et un élément envoyé immédiatement n'a pas de clé scheduled_at ; la référence batch montre les deux dans une même réponse.
Un payload d'envoi immédiat peut tout de même être trop volumineux pour être programmé. Si son corps, sa liste de destinataires ou ses métadonnées dépassent la limite de programmation, API renvoie 422. Réduisez ces champs ou envoyez le message immédiatement.
Choisir l'heure d'envoi
scheduled_at est un horodatage absolu RFC 3339. Deux règles le gouvernent :
- L'heure doit se situer entre 30 secondes et 30 jours dans le futur. Moins de 30 secondes ou plus de 30 jours est rejeté avec un 422. Le seuil minimal empêche un envoi programmé d'entrer en concurrence avec un envoi immédiat. Trente jours est l'horizon le plus lointain pour lequel nous retenons un message.
- Fournissez un instant exact. Incluez un suffixe UTC Z (2027-01-15T09:00:00Z) ou un décalage explicite (2026-07-30T09:00:00-04:00, le même instant que 13:00:00Z). Nous comparons l'instant à l'heure courante et n'interprétons jamais une heure locale brute ni n'appliquons le fuseau horaire du destinataire. Pour envoyer à 9 h dans l'heure locale de chaque destinataire, calculez vous-même ces instants et programmez un envoi par fuseau horaire.
Les expressions relatives comme "in 2 hours" ne sont pas acceptées. Envoyez un horodatage résolu.
Lister les messages programmés
Filtrez la liste des messages par statut pour voir ceux qui n'ont pas encore été envoyés :
for await (const message of bird.email.list({ status: "scheduled" })) {
console.log(message.id, message.scheduled_at);
}for message in client.email.list(status="scheduled"):
print(message.id, message.scheduled_at)for msg, err := range client.Email.List(context.Background(), bird.EmailListParams{Status: bird.EmailStatusScheduled}) {
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id)
}foreach ($bird->email->list(['status' => 'scheduled']) as $message) {
echo $message->getId(), "\n";
}bird email list --status scheduledcurl "https://us1.platform.bird.com/v1/email/messages?status=scheduled" \
-H "Authorization: Bearer bk_us1_..."status=canceled liste ceux que vous avez annulés avant leur envoi. Une fois qu'un message programmé est déclenché, il entre dans le pipeline et apparaît avec les statuts de livraison, comme tout autre envoi. Le journal des e-mails du tableau de bord propose les mêmes filtres Scheduled et Canceled.
Annuler un envoi programmé
Annulez un message à tout moment avant qu'il ne commence à partir avec POST /v1/email/messages/{message_id}/cancel :
await bird.email.cancel("em_abc123");client.email.cancel("em_abc123")if err := client.Email.Cancel(context.Background(), "em_abc123"); err != nil {
log.Fatal(err)
}$bird->email->cancel('em_01krdgeqcxet5s7t44vh8rt9mg');bird email cancel <message-id> --yescurl -X POST "https://{region}.platform.bird.com/v1/email/messages/{message_id}/cancel" \
-H "Authorization: Bearer $TOKEN"Une annulation réussie renvoie 204 No Content. Le statut du message devient canceled, il n'est jamais envoyé, et un webhook email.canceled se déclenche. Quatre points à retenir :
-
Seul un message encore programmé peut être annulé. Un message dont l'envoi a déjà commencé, qui a déjà été envoyé ou déjà annulé renvoie 409 :Exemple de code
{ "error": { "type": "conflict_error", "code": "E10005", "name": "EmailNotCancelable", "message": "This message cannot be canceled.", "remediation": "Only a scheduled message that has not started sending can be canceled. Once sending begins there is no way to pull it back." } }À l'approche de l'heure d'envoi, une annulation peut aussi perdre la course face à l'envoi lui-même et renvoyer 409 pour la même raison. -
Un envoi volumineux peut prendre quelques secondes avant de devenir annulable. Un envoi programmé avec des pièces jointes ou un corps volumineux est encore en cours de stockage après le 202, donc :
- Une annulation pendant cette fenêtre renvoie 409 et le message reste programmé.
- Relisez le message.
- S'il affiche toujours status: scheduled, réessayez l'annulation.
-
L'annulation ne restitue pas l'enveloppe d'e-mails programmés. L'unité consommée au moment de la programmation reste consommée, ce qui empêche une boucle programmer-puis-annuler de contourner l'enveloppe. Votre enveloppe d'envoi normale n'est pas affectée, car elle n'est débitée que lorsqu'un message part effectivement.
-
L'annulation peut être réessayée en toute sécurité avec un Idempotency-Key, comme toute autre écriture.
Pour déplacer un envoi programmé à une autre heure, annulez-le et soumettez un nouvel envoi avec le nouveau scheduled_at. Vous obtenez un nouvel ID em_.
Ce qui se passe à l'heure d'envoi
La programmation ne change que le moment où un message est libéré. Sa construction et sa gouvernance restent identiques. Les pièces jointes, la catégorie, les tags et métadonnées se comportent exactement comme lors d'un envoi immédiat et sont renvoyés de la même façon sur les événements webhook. Quatre vérifications se répartissent entre les deux moments :
- La validation du payload et du domaine s'effectue en amont. Un envoi programmé malformé échoue lors de l'appel API avec un 422, ce qui vous informe tout de suite plutôt qu'à 9 h.
- Le domaine de l'expéditeur est revérifié à l'heure d'envoi. Si votre domaine from n'est plus vérifié lorsque l'heure programmée arrive, le message n'est pas envoyé. Ses destinataires sont renvoyés en tant que rejected avec une raison, au lieu de recevoir un e-mail d'un domaine non vérifié. Maintenez le domaine vérifié pendant toute la fenêtre.
- Votre enveloppe d'envoi est débitée à l'heure d'envoi. Le quota d'envoi normal est consommé lorsque le message part. La programmation laisse le quota inchangé. Si l'enveloppe est épuisée à l'heure d'envoi, les destinataires sont rejetés.
- La suppression est évaluée à l'heure d'envoi, par rapport à votre liste de suppression telle qu'elle est à ce moment-là. Ainsi, une personne qui se désinscrit entre la programmation et l'envoi est bien prise en compte.
Erreurs
| Statut | Code | Quand |
|---|---|---|
| 422 | E10003 | L'enveloppe d'e-mails programmés de votre organisation pour la période de facturation est épuisée |
| 422 | scheduled_at est à moins de 30 secondes ou à plus de 30 jours | |
| 422 | scheduled_at a été combiné avec template | |
| 422 | Le payload est trop volumineux pour être mis en attente ; réduisez le corps, les destinataires ou les métadonnées, ou envoyez maintenant | |
| 409 | E10005 | Le message ne peut plus être annulé : son envoi a déjà commencé, il a été envoyé ou a été annulé |
| 404 | Aucun message avec cet ID dans cet espace de travail |
Webhooks
Deux événements sont propres à la programmation, en plus des événements de livraison habituels :
- email.scheduled se déclenche lorsqu'un message est accepté avec un scheduled_at futur, et indique cette heure.
- email.canceled se déclenche lorsqu'un message programmé est annulé avant son envoi.
Lorsque le message part, la chaîne email.accepted normale suit sans changement.
Étapes suivantes
- Envoyer un e-mail : le payload d'envoi complet et le modèle asynchrone 202
- Suppressions : à qui nous ne livrons pas, et pourquoi, évalué à l'heure d'envoi
- Événements et webhooks : les événements qu'un message programmé produit une fois déclenché
- Idempotence : réessayer en toute sécurité les appels de programmation et d'annulation
- Référence API : le contrat complet de l'endpoint d'annulation
- Programmer l'envoi d'un email pour plus tard : une vidéo montrant un envoi programmé et comment l'annuler
Ressources associées
Poursuivez avec la documentation, les guides et les exemples sur ce sujet. Les ressources sont en anglais.
Explorer la fonctionnalitéScheduled emailSuivre le parcours d'apprentissageBuild your first integration
Obtenir un guide d'implémentation