E-mail verzenden
POST /v1/email/messages verzendt één e-mail. Geef een afzender, ontvangers en inhoud mee in een JSON-payload. De API retourneert 202 Accepted met een bericht-ID en bezorgt de e-mail daarna asynchroon. Zie de API-referentie voor de volledige schema's.
Een minimale verzending
De kleinste geldige payload is een from, minstens één to-ontvanger, een subject en een body (html, text of beide). Het from-adres moet op een domein staan dat je in deze werkruimte hebt geverifieerd, of op het onboardingdomein.
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>"
}'Gebruik je regionale host (https://us1.platform.bird.com of https://eu1.platform.bird.com) met een bijbehorende bk_{region}_...-sleutel.
Het verzendvoorbeeld gebruikt delivered@messagebird.dev, een sandbox-adres dat altijd mail accepteert. De API weigert placeholderdomeinen met een 422: example.com, example.net, example.org, example.edu, test.com en alles onder de gereserveerde .test-, .example-, .invalid- of .localhost-TLD's. Een verzending naar deze domeinen kan alleen bouncen, wat je afzenderreputatie schaadt.
Verzenden voordat je een domein verifieert
Tijdens het onboarden kun je verzenden vanaf ons gedeelde onboardingdomein, onboarding@messagebird.dev. Die verzendingen slaan de domeincontrole over, maar bereiken alleen geverifieerde leden van je eigen werkruimte en sandbox-adressen, met een dagelijks ontvangerlimiet. De quickstart bevat de exacte regels en limieten.
De payload opbouwen
Ontvangers
to, cc en bcc accepteren elk maximaal 50 adressen, en to vereist er minstens één. Elke vermelding is een gewone e-mailstring, een RFC 5322-mailboxstring (Jane <jane@acme.com>) of een object met een optionele weergavenaam.
Ontvangers op de suppressielijst van de werkruimte laten het verzoek niet mislukken. Het retourneert nog steeds een 202, en elke onderdrukte ontvanger verschijnt op de leesendpoints als status: rejected met de reden recipient_suppressed, ook wanneer dat elke ontvanger van de verzending betreft.
Inhoud
subject is verplicht bij inline verzendingen, maximaal 998 tekens. Geef html, text of beide mee, elk maximaal 524.288 tekens. Stuur waar mogelijk beide: een client die geen HTML kan renderen valt terug op het tekstgedeelte.
Om inline-inhoud te personaliseren, plaats je {{ variable }}-tokens in het onderwerp of de body en geef je hun waarden mee in parameters, maximaal 16 KB geserialiseerd. Eén set waarden geldt voor elke ontvanger van de verzending, en een token zonder bijbehorende sleutel wordt leeg gerenderd. Gebruik voor herbruikbare inhoud in plaats daarvan een template.
Neem parameters op, zelfs als leeg object ({}), om het onderwerp en de body als Liquid te verwerken. Laat het weg om tokens zoals {{ animal }} letterlijk te verzenden. Elke parameternaam is één woord, zoals first_name; namen met punten en de gereserveerde naam bird worden geweigerd. Ongeldige Liquid-syntax en niet-ondersteunde tags of filters retourneren 422.
Waarden die in HTML worden ingevoegd, worden ge-escaped zodat ze de omringende markup niet kunnen wijzigen. Gebruik voor een volledige link- of afbeeldings-URL {{ link }} zonder url_encode. Encodeer voor een waarde in een URL-query die waarde expliciet, bijvoorbeeld https://example.com/search?q={{ query | url_encode }}.
Reply-to en aangepaste headers
reply_to accepteert 1 tot 25 adressen, in dezelfde formaten als ontvangers. Elk antwoord van een ontvanger gaat naar allemaal, dus één of twee is gebruikelijk.
headers is een string-naar-string-object voor je eigen headers, bijvoorbeeld {"X-Campaign": "spring-2026"}, met een maximum van 25 headers met waarden tot 998 tekens. Drie soorten headers worden geretourneerd als een 422:
- Adresserings- en platformheaders. Stel de adressering van het bericht in via de specifieke velden (from, to, cc, bcc, reply_to, subject). Die namen, en de headers die wij voor je genereren (Content-Type, Content-Transfer-Encoding, DKIM-Signature, Received, Return-Path), kun je hier niet instellen.
- List-Unsubscribe en List-Unsubscribe-Post bij een marketing-verzending. Wij stellen zelf een conforme one-click-uitschrijfheader in. Bij een transactional-verzending laten we de jouwe precies zoals je ze instelt.
- Elke waarde met een carriage return of line feed.
Tracking
track_opens en track_clicks staan beide standaard op true. Zet een van beide op false om open-pixelinjectie of linkherschrijving voor deze verzending over te slaan. Tracking en statistieken beschrijft wat elk van beide in het bericht wijzigt.
Categorie en IP-pool
category classificeert de inhoud en bepaalt het suppressiebeleid: marketing blokkeert bezorging bij elke suppressiereden en bij elke opt-out, en transactional bezorgt ondanks een klachtsuppressie of een marketing-only opt-out (een opt-out voor alle berichten blokkeert ook). De standaard is de categorie van het template bij een templateverzending en anders marketing, dus stel transactional expliciet in voor bonnetjes, wachtwoordresets en andere operationele e-mail. Categorieën behandelt de keuze. Mail ingediend via SMTP haalt de categorie uit de SMTP-configuratie van de sleutel.
ip_pool_id kiest de verzendpool: een pool-ID (ipp_...), of ipp_shared om expliciet via de gedeelde pool te routeren. Laat het weg voor de standaardpool van je organisatie. Een onbekende pool, of een pool zonder beschikbare dedicated IP's om vanaf te verzenden, wordt geweigerd met een 422.
Veldreferentie
| Veld | Type | Verplicht | Limieten en opmerkingen |
|---|---|---|---|
| from | address | ja | Moet op een geverifieerd domein staan, of op het onboardingdomein |
| to | address[] | ja | 1 tot 50 |
| cc, bcc | address[] | nee | Maximaal 50 elk |
| subject | string | inline verzendingen | Maximaal 998 tekens; weglaten bij templateverzendingen |
| html, text | string | minstens één | Maximaal 524.288 tekens elk; weglaten bij templateverzendingen |
| reply_to | address[] | nee | 1 tot 25; antwoorden gaan naar elk vermeld adres |
| headers | object (string → string) | nee | Maximaal 25; gereserveerde namen worden geweigerd (zie aangepaste headers) |
| parameters | object | nee | Waarden voor {{ tokens }} in inline-inhoud; maximaal 16 KB geserialiseerd; gedeeld over ontvangers |
| tags | {name, value}[] | nee | Maximaal 20; naam ≤ 32 tekens, waarde ≤ 64 tekens; alleen [A-Za-z0-9_-]; namen uniek per verzending |
| metadata | object | nee | Willekeurige JSON, maximaal 2 KB geserialiseerd |
| track_opens | boolean | nee | Standaard true |
| track_clicks | boolean | nee | Standaard true |
| category | string | nee | marketing of transactional; standaard die van het template bij een templateverzending, anders marketing |
| ip_pool_id | string | nee | ipp_... of ipp_shared; weglaten voor de standaardpool van je organisatie |
| template | object | nee | Verzend een gepubliceerd template op id of slug, met parameters voor de variabelen en een optionele language |
| attachments | object[] | nee | Maximaal 20; zie bijlagen |
| scheduled_at | RFC 3339 timestamp | nee | Plan inline content of een template in; zie gepland verzenden |
Verzenden met een template
Verzend in plaats van inline-inhoud een gepubliceerd template: stel template in als een object dat het template benoemt op id (emt_...) of op slug, precies een van de twee, met de variabelewaarden in template.parameters. Laat subject, html en text weg, want het template bevat die al.
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" }
}
}'De inhoud van een template is Liquid, dus naast gewone {{ variable }}-substitutie kun je filters, {% if %}-condities en {% for %}-loops gebruiken. Personaliseren met variabelen beschrijft de constructies die een publicatie weigert. template.parameters is waar je de waarden voor de eigen parameters van het template plaatst, op naam. Laat er een weg en de verzending wordt geweigerd met een 422 die de ontbrekende parameter benoemt. Al het andere aan de verzending werkt hetzelfde als inline, inclusief ontvangers, tags, metadata, tracking en bijlagen. Wat specifiek is voor een templateverzending:
- Inline of met template, nooit beide. template verzenden samen met subject, html of text wordt geweigerd met een 422. De API weigert ook variabelewaarden in het toplevel parameters-veld; bij een templateverzending horen ze in template.parameters.
- bird is de enige gereserveerde naam. Een placeholderpad dat begint met bird. verwijst naar onze eigen data, zoals de uitschrijflink of het contactrecord van de ontvanger, dus een template.parameters-sleutel mag niet bird heten. Elke andere sleutel definieer je zelf, en elke sleutel is één woord: {"order_number": "A-1043"} vult {{ order_number }}.
- Een template kan nu of later verzenden. Voeg scheduled_at toe om het verzenden in te plannen. We leggen de gepubliceerde versie, de geselecteerde taal en de parameterwaarden vast bij acceptatie. Als je het template verwijdert vóór het verzendtijdstip, wordt het bericht geweigerd met generation_failure.
- Een verzending gebruikt de gepubliceerde versie van het template. Concepten worden nooit verzonden. Een onbekend template wordt geweigerd met een 404, en een template zonder gepubliceerde versie met een 422.
- language kiest een van de talen van het template. Laat het weg om de standaardtaal van het template te verzenden. Vraag een taal aan die het template niet heeft, en de eigen on_missing_language-instelling bepaalt of de dichtstbijzijnde match wordt verzonden of de verzending wordt geweigerd. Een template dat language_source_required instelt, weigert een verzending die helemaal geen taal benoemt.
- De categorie van het template is een standaard, en de jouwe overschrijft die. Laat category weg en de verzending erft die van het template, dus een transactioneel template hoeft die niet bij elke aanroep herhaald.
E-mailtemplates behandelt het maken, publiceren en de constructies die een template kan bevatten.
Tags vs metadata
Beide koppelen je eigen data aan een verzending en verschillen in hoe je er later op kunt zoeken:
- tags zijn gestructureerde {name, value}-paren: maximaal 20 per verzending, naam maximaal 32 tekens, waarde maximaal 64, alleen ASCII-letters, cijfers, underscore en koppelteken, en namen uniek binnen de verzending. Tags zijn filterdimensies, dus je kunt de berichtenlijst filteren op tag en analytics en dashboardoverzichten uitsplitsen op tag. Gebruik ze voor labels met lage kardinaliteit zoals campaign, experiment_variant of source.
- metadata is een willekeurig JSON-object, maximaal 2 KB geserialiseerd. We slaan het op, retourneren het bij API-reads en sturen het mee bij elk webhook-event, dus het is geschikt voor context die je teruggestuurd wilt krijgen: interne ID's, foreign keys, gestructureerde payloads.
Elk webhook-event bevat beide samen met de correlatie-ID's (email_id, recipient_id), zodat je kunt afstemmen met je eigen administratie zonder een extra lookup. Tagnamen en toplevel metadatasleutels die beginnen met __bird worden geweigerd. Je hoeft device, geografie, mailboxprovider, bouncetype of ontvangersdomein niet in een van beide velden te coderen, want we registreren elk daarvan al als een analytics-dimensie.
Codevoorbeeld
{
"tags": [{ "name": "campaign", "value": "onboarding" }],
"metadata": { "user_id": "usr_12345", "order_id": "ord_98765" }
}Bijlagen
attachments accepteert maximaal 20 bestanden per bericht, als base64-gecodeerde bytes inline. We weigeren een verzending waarvan de geschatte berichtgrootte na base64-codering boven de 20 MB komt, dus houd de ruwe bijlage-inhoud op of onder 15 MB voor speelruimte. Bijlagen beschrijft het veldcontract, inline-afbeeldingen, de geblokkeerde bestandstypen en hoe je een bijlage terugdownloadt.
Wat een 202 betekent
Een geslaagde verzending retourneert 202 Accepted met een bericht-ID met het voorvoegsel em_ en status: accepted:
Codevoorbeeld
{
"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"
}De 202 betekent dat we de verzending duurzaam hebben geaccepteerd. Fouten die je kunt oplossen komen terug op het verzoek zelf als een 422: een niet-geverifieerd afzenderdomein of een veld dat niet valideert. Uitkomsten per ontvanger (afgeleverd, gebounced, uitgesteld, klacht) komen daarna binnen via webhooks en de bericht-read-endpoints.
Daar volgen twee dingen uit:
- Reads retourneren status zonder de body. GET /v1/email/messages/{message_id} retourneert bericht- en ontvangerstatus, nooit de html- of text-body. Wanneer contentopslag is ingeschakeld voor de werkruimte, blijven opgeslagen body's tot 30 dagen beschikbaar via GET /v1/email/messages/{message_id}/content.
- Een read kan kort achterlopen op de verzending. Een 404 op de read-endpoints direct na een 202 betekent dat het bericht nog niet zichtbaar is; probeer het even later opnieuw.
Veilig opnieuw proberen
Stuur een Idempotency-Key-header mee met een unieke waarde per logische verzending. Als een verzoek is geslaagd maar je het antwoord nooit hebt gezien, herhaal het dan met dezelfde sleutel. De API retourneert het oorspronkelijke resultaat in plaats van een tweede e-mail te versturen, en bevat een Idempotency-Replay-header. Idempotency beschrijft het sleutelformaat en de bewaartermijn.
Batchverzending
Om het aantal API-verzoeken te verminderen, accepteert POST /v1/email/batches tot 100 onafhankelijke berichten en valideert ze als één geheel. Het single-send-endpoint in een lus aanroepen wordt ook ondersteund. Een batch-item gebruikt de payload op deze pagina, inclusief scheduled_at, dus één batch kan directe en geplande berichten combineren.
Facturering
E-mailverzendingen worden per ontvanger geteld tegen de maandelijkse limiet van je abonnement, dus een bericht aan drie ontvangers verbruikt drie verzendingen. Billing and usage beschrijft het telmodel en de actuele verbruiksweergave.
Volgende stappen
- E-mailtemplates: maak en publiceer de templates die je hier verstuurt
- Categorieën: hoe marketing en transactional het suppressiegedrag veranderen
- Suppressies: aan wie we niet bezorgen, en waarom
- Geplande verzending: bezorg op een toekomstig tijdstip met scheduled_at
- Testsandbox: sandbox-ontvangers en verzenden vóór domeinverificatie
- API-referentie: de volledige request- en responseschema's
Gerelateerde bronnen
Ga verder met de documentatie, gidsen en voorbeelden voor dit onderwerp. De bronnen zijn in het Engels.
Bekijk de gidsOrder confirmation emailsOntdek de mogelijkheidOrder confirmation emailsVolg het leerpadBuild your first integration
Probeer de oefening en ontvang een implementatieoverzicht