Sign inGet Started

Modèles d'e-mail

Un modèle est un objet et un corps d'e-mail que vous enregistrez une fois et envoyez autant de fois que nécessaire. Vous rédigez les parties variables sous forme de substituants {{ variable }}, vous publiez le modèle, puis vous l'envoyez par slug au lieu de coller le même HTML dans chaque appel API. Un modèle appartient à votre espace de travail.

Créez et gérez vos modèles dans Email > Templates, via /v1/email/templates, avec le CLI bird, ou via le serveur MCP. Des méthodes typées sont disponibles dans les SDK TypeScript, Python, PHP et Go sous email.templates. Les schémas complets de requête et de réponse se trouvent dans la référence API. Envoyez un modèle publié via le point de terminaison d'envoi habituel.

Contenu d'un modèle

Chaque modèle possède deux noms, et ils remplissent des rôles différents :

  • slug est le nom par lequel vous envoyez le modèle, par exemple welcome-email. Vous le choisissez à la création du modèle et il ne peut plus être modifié ensuite. Un slug peut contenir des lettres minuscules, des chiffres, des tirets et des traits de soulignement, doit commencer et se terminer par une lettre ou un chiffre, et peut compter jusqu'à 63 caractères. Deux préfixes sont interdits : bird_, réservé à nos modèles intégrés, et emt_, le format utilisé pour les identifiants de modèle. Le tableau de bord appelle ce champ Alias.
  • name est un libellé d'affichage en texte libre. Il prend par défaut la valeur du slug, et vous pouvez le modifier à tout moment. Aucune résolution ne passe par le nom, donc renommer un modèle pour l'affichage ne casse jamais un envoi.

En plus de ceux-ci, un modèle possède un identifiant emt_ permanent, fixé pour toute sa durée de vie. Il possède aussi une category, soit marketing soit transactional, et une source de création : html, du balisage finalisé que vous fournissez, éventuellement personnalisé avec Liquid. La catégorie et la source sont toutes deux fixées à la création du modèle.

Nous fournissons un catalogue de modèles intégrés, dont les slugs commencent tous par bird_. Un modèle intégré n'appartient à aucun espace de travail, ne peut pas être modifié et peut toujours être envoyé tel quel. Copiez-en un dans votre espace de travail pour vous l'approprier : il devient alors un modèle ordinaire que vous pouvez modifier. La copie arrive sous forme de brouillon non publié qui hérite de la catégorie, de la source et des paramètres de langue de l'original ; publiez-le avant de l'envoyer.

Brouillons et versions publiées

Chaque template possède exactement un brouillon, la copie de travail que vous modifiez. Il possède aussi un nombre quelconque de versions publiées, chacune numérotée (1, 2, 3, et ainsi de suite) et jamais modifiée une fois créée. Les modifications s'appliquent au brouillon sur place. La publication prend un instantané du brouillon actuel, le transforme en version numérotée suivante et fait de cette version celle utilisée par les envois. Le brouillon reste modifiable, vous pouvez donc continuer à travailler sur le suivant.

La règle importante pour l'envoi est celle-ci : un envoi utilise toujours la version publiée du template, et un brouillon n'est jamais envoyé seul. Vous pouvez continuer à modifier le brouillon pendant qu'une version stable est envoyée, puis publier quand la modification est prête. Publier une nouvelle version change ce que les envois suivants produisent. Un envoi déjà accepté n'est pas affecté par une publication ultérieure.

L'onglet Versions du template avec une ligne Draft et des lignes publiées v3, v2 et v1 affichant leurs dates de création et de publication

Les versions offrent deux actions supplémentaires. Abandonner les modifications du brouillon pour réinitialiser le brouillon à ce qui est actuellement publié. Ou revenir en arrière pour refaire d'une version publiée antérieure celle utilisée par les envois. Vous ne pouvez revenir qu'à une version publiée, jamais au brouillon lui-même. Le retour en arrière remplace le brouillon par le contenu de cette version : tout ce qui n'a pas été enregistré dans le brouillon est perdu, et les modifications ultérieures partent de la version à laquelle vous êtes revenu. Un retour en arrière ne crée pas de nouvelle version.

Pour choisir une image existante, vous avez besoin d'un accès en lecture à la bibliothèque de médias de l'espace de travail. Pour téléverser, coller ou déposer une nouvelle image, vous avez besoin d'un accès en écriture. Si Insert image est désactivé ou si vous ne pouvez ni parcourir ni téléverser, demandez à un administrateur de l'espace de travail la permission correspondante pour la bibliothèque de médias. La permission de modification des templates seule n'accorde pas l'accès à la bibliothèque de médias.

Dans le tableau de bord, choisissez Visual > Insert image pour rechercher dans votre bibliothèque de médias ou téléverser une image PNG, JPEG, GIF ou WebP jusqu'à 5 Mo. Les fichiers WebP statiques sont convertis en PNG ou JPEG. Sélectionnez l'image pour définir sa Image description, sa largeur d'affichage, son alignement et son lien. Marquez une image comme Decorative image uniquement quand elle n'apporte aucune information ; une image avec lien a besoin d'une description qui explique sa destination. Chaque langue conserve ses propres descriptions d'image et sa mise en page.

Utilisez Replace image pour changer une image sélectionnée tout en conservant sa description, son lien, sa largeur et son alignement. Vous pouvez aussi coller ou déposer un fichier image dans l'éditeur visuel. Attendez la fin du téléversement, ou annulez-le, avant d'enregistrer ou d'envoyer un test. Vérifiez l'aperçu, puis ouvrez More actions > Test email pour envoyer le contenu actuel à vous-même. Code reste disponible pour modifier le HTML.

Supprimer une image de la bibliothèque de médias ne la retire pas des e-mails déjà envoyés. Remplacer une image utilise une nouvelle URL, les messages antérieurs continuent donc d'afficher l'original.

L'enregistrement est protégé par un numéro de révision. Envoyez le revision que vous avez lu en dernier pour la langue que vous enregistrez. Si quelqu'un d'autre a modifié cette langue entre-temps, l'enregistrement est refusé comme conflit au lieu d'écraser son travail. Omettez revision pour enregistrer sans condition. La publication et le retour en arrière utilisent le revision propre au brouillon de la même manière.

Contenu en plusieurs langues

Un template contient du contenu dans 25 langues au maximum, chacune avec son propre objet et son propre corps, identifiée par un code BCP-47 tel que en ou pt-BR. Une langue est la langue par défaut du template. Publier un template publie toutes les langues qu'il contient en même temps. Vous ne pouvez pas publier une seule langue séparément : elles doivent toutes être terminées au préalable. Chaque langue a besoin d'un objet et d'un corps, et la langue par défaut du template doit faire partie des langues que vous avez remplies. Si l'une de ces conditions manque, rien n'est publié, et l'erreur vous indique ce qui manque dans chaque langue pour que vous puissiez tout corriger en une seule passe. Vous n'êtes pas obligé de terminer toutes les langues dès le départ : publiez celles qui sont prêtes et ajoutez le reste plus tard.

Une langue a besoin d'un corps HTML. Vous pouvez omettre son text : la publication crée alors automatiquement une alternative en texte brut à partir du HTML, vous obtenez donc les deux parties sans rédiger la seconde vous-même.

Chaque langue peut aussi contenir un texte d'aperçu, parfois appelé preheader : la ligne qu'une boîte de réception affiche après l'objet dans sa liste de messages. Il est facultatif, limité à 255 caractères, et accepte les mêmes substitutions {{ variable }} que l'objet. Si vous l'omettez, la boîte de réception reprend la première ligne du corps à la place, ce qui est rarement la ligne que vous choisiriez. La publication refuse un texte d'aperçu sur une langue dont le corps n'a pas de partie HTML, car un client de messagerie ne lit la ligne d'aperçu que dans du balisage HTML masqué, et refuse {{ bird.unsubscribe_url }} à l'intérieur pour la même raison que l'objet ne peut pas en contenir : ni l'un ni l'autre n'est un endroit où un lien peut apparaître.

Deux paramètres couvrent un envoi qui ne nomme pas une langue pour laquelle le template a du contenu, et ils protègent contre des erreurs différentes :

ParamètreCe qu'il contrôle
on_missing_languageCe qui se passe quand un envoi demande une langue que le template ne possède pas. fallback, la valeur par défaut, sert la correspondance la plus proche. Il essaie d'abord une forme plus large de la même langue, de sorte qu'un pt stocké peut servir une requête pour pt-BR. Il se rabat ensuite sur la langue par défaut du template. fail rejette l'envoi à la place, pour du contenu où envoyer la mauvaise langue est pire que de ne rien envoyer du tout.
language_source_requiredSi un envoi doit nommer une langue ou non. Ce réglage est désactivé par défaut, de sorte qu'un envoi qui n'en nomme aucune reçoit la langue par défaut. Activez-le, et cet envoi est rejeté à la place. Un broadcast nomme une langue pour toute son audience, donc un template avec ce réglage activé nécessite que cette langue soit choisie avant que le broadcast puisse envoyer.

Vous pouvez régler ces deux paramètres indépendamment. Seul, fail ne s'applique que lorsqu'un envoi nomme une langue que nous n'avons pas, donc un envoi qui n'en nomme aucune passe quand même. Activez les deux paramètres ensemble lorsque vous voulez que chaque envoi nomme une langue explicitement.

Personnaliser avec des variables

Écrivez des substitutions {{ variable }} dans l'objet, le texte d'aperçu et le corps. Nous les détectons automatiquement, combinées à travers toutes les langues, donc vous n'avez jamais à les déclarer séparément. Le préfixe de la substitution distingue les deux types. Un chemin commençant par bird. lit nos données, soit un enregistrement de contact, soit le lien de désinscription. Tout le reste est un paramètre auquel vous donnez une valeur lors de l'envoi.

Le nom d'un paramètre est un mot unique, comme {{ animal }}. Un nom avec des points tente d'accéder à une structure qu'un paramètre ne possède pas, donc sa publication est rejetée : écrivez la valeur comme son propre paramètre, ou lisez les données de contact avec bird.contact.<attribute> à la place.

Pour un envoi unique ou un lot, la valeur d'un paramètre provient de l'objet template.parameters de l'envoi, indexée par son nom. Un seul jeu de valeurs couvre tous les destinataires de cet envoi. bird est le seul nom que vous ne pouvez pas y utiliser : une clé template.parameters nommée bird est rejetée avec une 422.

Un broadcast n'a pas d'objet parameters, donc son contenu ne peut utiliser que des placeholders bird.. bird.contact.<attribute> est rempli à partir des propriétés de contact de chaque destinataire, ce qui personnalise le contenu par destinataire. Chaque propriété de contact est accessible par sa propre clé, ainsi que les trois champs intégrés : first_name, last_name et email.

Exemple de code
Hi {{ bird.contact.first_name }},

Chaque paramètre du template a besoin d'une valeur au moment de l'envoi. Sinon, API renvoie une 422 qui nomme le paramètre manquant. Fournissez des valeurs pour les paramètres dans toutes les langues, car la langue sélectionnée peut dépendre des paramètres de repli. Une propriété de contact manquante est rendue comme une valeur vide ; ajoutez donc un repli pour le contenu visible par le client : {{ bird.contact.first_name | default: "there" }}.

Un broadcast est plus strict sur les noms qu'il accepte, car les propriétés de contact sont tout ce dont il dispose pour remplir les placeholders. Ses placeholders bird.contact.* ne peuvent nommer qu'un champ intégré ou une propriété de contact enregistrée dans l'espace de travail. Tout autre placeholder, y compris un paramètre, est un placeholder que le broadcast n'a aucun moyen de remplir. L'envoi est refusé, et l'erreur nomme le placeholder.

Archiver une propriété la retire du sélecteur de propriétés dans l'éditeur. Les nouvelles versions de template qui la lisent ne peuvent pas être publiées, et l'erreur nomme la propriété. Les templates déjà publiés continuent d'envoyer. Désarchivez la propriété pour l'utiliser à nouveau dans de nouvelles versions.

Les placeholders utilisent Liquid, donc les filtres et le contrôle de flux fonctionnent aux côtés de la substitution simple. Un conditionnel {% if %} et une boucle {% for %} sur une valeur de tableau sont tous deux valides. Quelques constructions sont rejetées lors de la publication, et l'erreur nomme exactement ce qu'il faut modifier :

  • Les inclusions partielles, avec {% include %} ou {% render %}.
  • Les tags increment, decrement et ifchanged.
  • Les filtres money, format_date, format_time, json, inspect et type.
  • Les comparaisons avec empty ou blank. Utilisez .size == 0 à la place.
  • Les blocs imbriqués bien plus profondément que le balisage d'un e-mail réel ne l'exige.

Le template d'un broadcast ne peut pas du tout utiliser de boucle {% for %}, car un broadcast ne remplit qu'une seule valeur par propriété de contact et n'a rien à itérer. Si votre contenu a besoin d'une boucle, envoyez-le via API de messages à la place.

Chaque template utilise Liquid, y compris un template qui ne contient que des placeholders {{ variable }}. Avant la publication, nous validons l'objet, le texte d'aperçu, le HTML et le contenu en texte brut en tant que Liquid. Nous ajoutons aussi le filtre escape à chaque sortie HTML qui ne se termine pas déjà par escape ou escape_once, afin qu'une valeur contenant & ou < ne puisse pas altérer le balisage environnant. La sortie réservée au lien de désinscription reste inchangée pour que l'envoi puisse la remplacer. L'objet et le corps en texte brut sont laissés tels quels. Comme la publication ajoute ces filtres, le HTML que vous lisez depuis une version publiée peut ne pas être identique octet par octet à ce que vous avez soumis.

Placez une URL complète directement dans un href, par exemple <a href="{{ sign_in_url }}">Sign in</a>. N'ajoutez pas url_encode à la valeur entière. Ce filtre encode en pourcentage https://, /, ? et &, ce qui empêche le résultat de fonctionner comme un lien absolu. Nous ajoutons l'échappement HTML tout en préservant la structure de l'URL. Quand un paramètre fournit un seul composant d'URL, encodez ce composant explicitement : <a href="https://example.com/search?q={{ query | url_encode }}">Search</a>.

Prévisualiser avant de publier

Rendez un template avec des valeurs d'exemple et récupérez l'objet ainsi que les corps HTML et texte brut qu'un envoi livrerait. La prévisualisation utilise notre moteur Liquid local et rend le brouillon par défaut, ce qui vous permet de vérifier une modification avant sa mise en production. Elle peut aussi rendre une version publiée à la place. Elle fonctionne pour vos propres templates et pour nos templates intégrés, et rien n'est envoyé.

Vous pouvez aussi lui fournir le contenu vous-même au lieu de lui faire lire le brouillon. Passez un objet et des corps : ils sont rendus exactement comme un brouillon le serait, ce qui permet à un éditeur d'afficher une modification au fil de la saisie sans rien enregistrer au préalable.

La personnalisation est remplie pour vous, de sorte que le résultat se lit comme un texte finalisé au lieu de placeholders {{ }}. Indiquez un contact et chaque bird.contact.<attribute> est résolu à partir des propriétés propres à ce contact, ce qui vous permet de vérifier votre formulation par rapport à un enregistrement réel avant que quiconque ne le reçoive. Les valeurs proviennent de la même projection qu'un broadcast utilise pour remplir ses placeholders, de sorte que la prévisualisation répond avec ce qu'un envoi répondrait.

Omettez contact et des valeurs de substitution sont utilisées à la place : Bird et Test pour le prénom et le nom, bird.test@example.com pour l'e-mail, et la valeur de repli enregistrée de chaque autre propriété. Une propriété référencée sans valeur de repli est rendue sous la forme de sa clé entre crochets, comme [loyalty_tier], ce qui vous indique à la fois que la valeur est un placeholder et quelle propriété attend encore une valeur de repli.

Un contact est lu tel qu'il se présente à l'instant. Cela fait de la prévisualisation le bon outil pour vérifier un contenu que vous êtes sur le point d'envoyer, et le mauvais pour demander ce qu'un envoi antérieur contenait. Pour lire ce qu'un envoi a effectivement livré, ouvrez ce message dans le journal des e-mails, qui le rend à partir des valeurs que cet envoi transportait.

Ajoutez language pour rendre une langue spécifique, ou omettez-le pour la langue par défaut du template. La réponse vous indique quelle langue a été rendue, ce qui compte quand celle que vous avez demandée n'est pas disponible et que le on_missing_language du template a servi une correspondance proche.

Si le brouillon contient une personnalisation qui serait rejetée lors de la publication, la prévisualisation renvoie la même erreur, ce qui en fait aussi un moyen de détecter les problèmes en amont.

Dans le constructeur de templates du tableau de bord, Preview with contact data en bas du rail gauche affiche l'e-mail rendu à côté de ce que vous êtes en train de modifier, dans l'éditeur visuel comme dans l'éditeur de code. Le sélecteur en dessous choisit les données de quel contact remplissent les placeholders, et Sample data correspond aux valeurs de substitution décrites ci-dessus.

Envoyer avec un template

Définissez le champ template de l'envoi sur un objet qui nomme le modèle, soit par id (emt_...), soit par slug, en utilisant exactement l'un des deux. Placez les valeurs de ses variables dans template.parameters. Ajoutez language pour choisir une langue précise, ou omettez-le pour envoyer dans la langue par défaut du modèle, sauf si le modèle exige que chaque envoi en nomme une. Omettez entièrement subject, html et text, car le modèle les fournit déjà.

Exemple de code
{
  "from": "hello@yourdomain.com",
  "to": ["delivered@messagebird.dev"],
  "category": "transactional",
  "template": {
    "slug": "welcome-email",
    "parameters": { "first_name": "Jane" }
  }
}

Un comportement à anticiper : la catégorie du modèle est une valeur par défaut, et le category de l'envoi la remplace. Omettez category, et l'envoi hérite de la catégorie du modèle : un modèle opérationnel est donc envoyé comme transactionnel sans que vous ayez à le répéter à chaque appel. Définissez category, et votre valeur l'emporte. Le reste du contrat côté envoi se trouve dans envoyer avec un modèle.

Créer en dehors du tableau de bord

L'ensemble du cycle de vie est disponible en dehors du tableau de bord : via API, les SDK sous email.templates, le bird CLI, et les outils email_templates_* MCP. Dans les SDK, les opérations de version et de langue se trouvent sous email.templates.versions et email.templates.versions.languages. L'étape de publication s'appelle submit dans ce contexte, et elle transforme le brouillon en prochaine version publiée.

Vous pouvez conserver vos modèles dans votre propre dépôt et les synchroniser depuis une étape de build. Un brouillon et une version publiée renvoient un HTML différent : choisissez la lecture qui correspond aux besoins de votre outil.

  • Un brouillon est renvoyé exactement tel que vous l'avez enregistré. Le HTML que vous enregistrez dans une langue de brouillon est restitué octet par octet depuis la lecture de cette langue (GET /v1/email/templates/{id}/versions/{draft_version_id}/languages/{language}) et depuis la lecture de version, y compris le doctype, les blocs <style>, les commentaires, les espaces, les fins de ligne et le texte non ASCII. Un enregistrement ne renvoie aucun contenu : relisez donc la langue pour confirmer ce qui a été stocké.
  • Une version publiée est renvoyée prête à l'envoi. La publication réécrit le HTML de trois façons : chaque sortie {{ }} qui ne se termine pas déjà par escape ou escape_once reçoit le filtre escape, sauf {{ bird.unsubscribe_url }}, qui reste tel quel ; le texte d'aperçu est intégré dans un bloc masqué juste après la balise ouvrante <body>, ou tout au début si le HTML n'en comporte pas ; et un text vide est rempli avec une alternative en texte brut dérivée du HTML. La lecture de cette version renvoie le contenu réécrit, qui ne correspondra donc pas à votre fichier source.

Pour exporter la source que vous avez rédigée, lisez le brouillon. La lecture d'un modèle ou le listage des modèles renvoie les métadonnées et l'état par langue, mais pas le HTML.

Les exemples ci-dessous utilisent {{ first_name }}, qui est un paramètre : l'envoi fournit sa valeur dans template.parameters, comme dans "parameters": {"first_name": "Jane"}. Cela convient à un envoi vers une adresse e-mail. Un placeholder {{ bird.contact.first_name }} lit plutôt la fiche contact du destinataire, qu'un broadcast remplit à partir des propriétés de contact de chaque destinataire. Un envoi vers une adresse e-mail n'a pas de fiche contact : il refuse donc un modèle qui lit bird.contact.* avec une 422 (EmailTemplateContactUnavailable).

Créez le modèle avec sa première langue. La réponse contient l'id du modèle et le draft_version_id, utilisés par les opérations de version et de langue. L'exemple définit aussi preview_text, le texte d'aperçu qu'une boîte de réception affiche après l'objet dans sa liste de messages. La publication l'intègre dans le HTML sous forme de bloc masqué, ce qui est l'une des raisons pour lesquelles une version publiée renvoie un HTML différent du brouillon :

const template = await bird.email.templates.create({
  slug: "welcome-email",
  category: "transactional",
  source: "html",
  languages: {
    en: {
      subject: "Welcome, {{ first_name }}",
      preview_text: "Your account is ready",
      html: "<!DOCTYPE html>\n<html>\n<body>\n<p>Hi {{ first_name }}, thanks for signing up.</p>\n</body>\n</html>\n",
    },
  },
});
console.log(template.id, template.draft_version_id);

Pour modifier une langue ultérieurement, enregistrez-la à nouveau avec bird email templates versions languages set ou un PUT vers le même chemin de langue. Le corps est plat : subject, qui est obligatoire, plus preview_text, html et text. Un champ que vous omettez est effacé. Ajoutez le revision que vous avez lu en dernier pour refuser l'enregistrement si quelqu'un d'autre a modifié cette langue entre-temps.

La mise en page de l'éditeur visuel du tableau de bord fait partie des champs effacés de cette manière, car API, les SDK et CLI ne peuvent pas l'envoyer. Enregistrer une langue créée dans l'éditeur visuel conserve son HTML mais supprime la mise en page : le tableau de bord rouvre donc cette langue dans l'éditeur de code.

Les étapes suivantes utilisent TEMPLATE_ID et DRAFT_VERSION_ID pour les id et draft_version_id renvoyés par l'appel de création. Lisez la langue du brouillon pour récupérer le HTML exactement tel qu'il est stocké :

const language = await bird.email.templates.versions.languages.get(
  "TEMPLATE_ID",
  "DRAFT_VERSION_ID",
  "en",
);
console.log(language.content?.html);

jq -j écrit la chaîne sans ajouter de saut de ligne final, de sorte que le fichier contient exactement les mêmes octets que ceux stockés par API.

Soumettez le brouillon avec validate_only d'abord. Cette opération exécute les mêmes vérifications qu'une vraie soumission sur chaque langue et signale les problèmes trouvés dans une seule réponse, sans rien publier. Soumettez ensuite sans validate_only pour publier la version suivante, que les envois utiliseront :

const templateId = "TEMPLATE_ID";
const draftVersionId = "DRAFT_VERSION_ID";
const check = await bird.email.templates.versions.submit(templateId, draftVersionId, {
  validate_only: true,
});
for (const problem of check.errors) {
  console.log(problem.language, problem.field, problem.message);
}
if (check.valid) {
  const result = await bird.email.templates.versions.submit(templateId, draftVersionId);
  console.log(result.version?.version_number);
}

Le corps de la soumission prend validate_only et expected_revision. Passez le revision du template comme expected_revision pour refuser la publication si le brouillon a changé depuis votre dernière lecture.

Listez les templates de votre espace de travail pour trouver le slug qu'un envoi désigne :

for await (const tpl of bird.email.templates.list({ scope: "workspace" })) {
  console.log(tpl.slug, tpl.name);
}

Envoyez le template publié par slug, avec une valeur dans template.parameters pour chaque paramètre qu'il utilise, ici first_name :

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);

Étapes suivantes

  • Envoyer un e-mail : le payload d'envoi complet, et comment les envois avec template s'y intègrent
  • Catégories : choisir entre marketing et transactional pour chaque envoi
  • bird email templates : gérer les templates depuis le terminal
  • Référence API : schémas complets de requête et de réponse pour les dix-huit opérations de template
  • SDKs : les méthodes typées email.templates en TypeScript, Python, PHP et Go
  • Serveur MCP : permettre à un agent de créer et publier des templates
  • Comment créer un template d'email : une vidéo qui en crée un dans le tableau de bord, puis en fait créer un autre par un agent

Poursuivez avec la documentation, les guides et les exemples sur ce sujet.