cURL
Send your first email with nothing but curl: export an API key, POST a message, and GET it back to watch it deliver. This is the raw HTTP flow; everything the SDKs do starts here.
1. Export your API key
Create a key in the dashboard under Developers > API keys (Send your first email walks through it), then export it:
代码示例
export BIRD_API_KEY="bk_us1_..."The region prefix selects your API host: bk_us1_ keys call https://us1.platform.bird.com, and bk_eu1_ keys call https://eu1.platform.bird.com. The send examples use us1; change the host if your key has another region.
2. Send an email
POST to /v1/email/messages, sending from our shared onboarding domain to the delivered@messagebird.dev sandbox address. You do not need domain verification or a real mailbox. Select the cURL tab for this quickstart.
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 \
--category transactional \
--cc manager@acme.com \
--from 'Acme Support <noreply@acme.com>' \
--header X-Campaign=spring-2026 \
--html '<h1>Hi there 👋</h1>' \
--metadata '{"user_id":"usr_12345"}' \
--reply-to support@acme.com \
--subject 'Welcome aboard' \
--tag category=welcome \
--text 'Hi there' \
--to 'Jane Doe <delivered@messagebird.dev>' \
--track-clicks=false{
"name": "email_send",
"arguments": {
"category": "transactional",
"cc": [
"manager@acme.com"
],
"from": {
"email": "noreply@acme.com",
"name": "Acme Support"
},
"headers": {
"X-Campaign": "spring-2026"
},
"html": "<h1>Hi there 👋</h1>",
"metadata": {
"user_id": "usr_12345"
},
"reply_to": [
"support@acme.com"
],
"subject": "Welcome aboard",
"tags": [
{
"name": "category",
"value": "welcome"
}
],
"text": "Hi there",
"to": [
{
"email": "delivered@messagebird.dev",
"name": "Jane Doe"
}
],
"track_clicks": false
}
}curl -X POST https://us1.platform.bird.com/v1/email/messages \
-H "Authorization: Bearer $BIRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "onboarding@messagebird.dev",
"to": ["delivered@messagebird.dev"],
"subject": "Hello from Bird",
"html": "<p>My first Bird email.</p>"
}'The API responds with 202: Bird has accepted the email and delivers it asynchronously. The *_count fields track recipients through the delivery states. In the initial response, one recipient is accepted and none are delivered:
代码示例
{
"id": "em_01ky7ma8y2es1s2akzk53tmjn0",
"status": "accepted",
"category": "marketing",
"from": { "email": "onboarding@messagebird.dev" },
"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"
}Raw HTTP has no SDK generating an idempotency key for you: if you retry a POST after a timeout, add an Idempotency-Key: <your-unique-key> header so the retry replays the original result instead of sending twice. Idempotency has the full semantics.
3. Watch it deliver
GET the message by its em_ ID and watch the status move from accepted through processed to delivered. Select the cURL tab again:
const msg = await bird.email.get("em_abc123");
msg.status; // "accepted" | "processed" | "delivered" | "bounced" | …
msg.delivered_count;
msg.bounced_count;message = client.email.get("em_abc123")
print(message.id, message.status, message.delivered_count)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.Get(context.Background(), "em_abc123")
if err != nil {
log.Fatal(err)
}
fmt.Println(*msg.Status, *msg.DeliveredCount)
}$message = $bird->email->get('em_01krdgeqcxet5s7t44vh8rt9mg');
echo $message->getStatus();bird email get <message-id>{
"name": "email_get",
"arguments": {
"email_id": "<message-id>"
}
}curl https://us1.platform.bird.com/v1/email/messages/em_01ky7ma8y2es1s2akzk53tmjn0 \
-H "Authorization: Bearer $BIRD_API_KEY"Because the recipient is the delivered@messagebird.dev sandbox address, delivery is guaranteed: the message flows through Bird's real pipeline but never touches a real mailbox.
Next steps
- Email API reference: the full request and response schema.
- Authentication: keys, scopes, and the Authorization header.
- Send your first email: the same flow with the dashboard and SDKs.
- Sending domains: verify your own domain for production sending.
Related resources
Continue with the documentation, guides and examples for this topic. Resources are in English.
Watch the guideGetting started with emailUnderstand the conceptShould I use a Bird SDK or call the API directly?Explore the capabilityEmailFollow the learning pathBuild your first integration
Try the practice and get an implementation brief