测试邮件投递(邮件沙盒)
邮件沙盒用于测试 Webhook 处理程序、屏蔽逻辑和投递结果,无需发送到真实收件箱。通过常规的 API 发送到 messagebird.dev 下的地址即可。地址的本地部分决定结果:bounce@messagebird.dev 会退信,delivered@messagebird.dev 会成功投递。
沙盒发送使用常规的受理、事件和 Webhook 路径。它返回与生产发送相同的 202 响应,并生成结构相同的逐收件人事件载荷。载荷中没有测试标记。邮件不会到达外部投递基础设施或真实收件箱。模拟的退信和投诉不会影响发件信誉,也不会写入屏蔽列表,因此这些地址可以重复使用。
沙盒无需任何设置:没有开关,没有测试模式,也不需要特殊的 API 密钥。它仅根据收件人地址触发,适用于常规发送端点(POST /v1/email/messages 和 POST /v1/email/batches)以及群发:受众中地址为沙盒地址的联系人只会被模拟,不会真正收到邮件,借此你可以在不给任何人发邮件的情况下演练一次营销活动。模拟收件人仍会计入你的发送额度,因此演练消耗的配额与真实发送相同。
特殊地址
所有地址都在 @messagebird.dev 域下。本地部分决定结果:
| 地址 | 模拟结果 | Webhook 顺序 | 说明 |
|---|---|---|---|
delivered@ | 接收方邮件服务器接受该邮件 | email.accepted → email.processed → email.delivered | 正常成功路径 |
bounce@ / hardbounce@ | 硬退信:SMTP 550,5.1.1 Unknown User,退信类别 10 | email.accepted → email.processed → email.bounced,附带 bounce_type: "hard" | 不写入屏蔽列表,因此该地址可重复使用 |
softbounce@ | 软退信:SMTP 451,4.3.0 Temporary failure, please retry,类别 20 | email.accepted → email.processed → email.bounced,附带 bounce_type: "soft" | 无论真实还是模拟,软退信都不会触发屏蔽 |
deferred@ / delay@ | 延迟投递:SMTP 451,4.2.1 Mailbox temporarily unavailable, will retry,类别 21 | email.accepted → email.processed → email.deferred | 模拟的延迟投递是最终状态:之后不会有重试,因此收件人保持为 deferred |
complaint@ / spam@ | 收件人将邮件举报为垃圾邮件(feedback_type: "abuse") | email.accepted → email.processed → email.complained | 不写入屏蔽列表;可重复使用 |
suppressed@ | 该收件人被视为已在你的屏蔽列表中 | email.accepted → email.rejected,附带 rejection_reason: "recipient_suppressed" | 与真实的被屏蔽收件人完全一样,在处理过程中提前终止:没有 email.processed,也没有投递事件 |
reject@ | 邮件在任何投递尝试之前被拒绝 | email.accepted → email.rejected,附带 rejection_reason: "transmission_failed" | 之后不会产生 email.processed 或投递事件 |
以上序列是单次发送产生的 webhook。对于群发,请去掉开头的 email.accepted:群发收件人会被单独接受,但我们只记录该事件,不为其发送 webhook,因此每个序列从其后的事件开始,投递路径上是 email.processed,suppressed@ 和 reject@ 则是 email.rejected。之后的内容完全相同,完整规则见事件参考。
一次发送可以同时包含沙盒收件人和真实收件人。每个收件人有各自的生命周期:真实收件人正常投递,沙盒收件人则被模拟。
这些事件同样会出现在邮件日志中该邮件的时间线上,以及事件 API 中,因此无需 webhook 端点也能使用沙盒并读取结果。
地址规则
- 仅根据本地部分识别,且仅限
messagebird.dev域名。bounce@yourdomain.com是普通地址。 - 只有特殊地址表中的本地部分才是特殊地址。
messagebird.dev下的其他任何地址都是普通收件人。从共享的入门域名发送时,该地址必须属于已验证的工作区成员。 - 匹配不区分大小写:
Bounce@messagebird.dev和bounce@messagebird.dev的行为完全相同。 - 匹配前会去掉
+label子地址:bounce+signup-flow@messagebird.dev仍会退信。可用标签关联测试用例;包含标签的完整地址会出现在事件和 webhook 中,因此每次测试运行都能标记自己的收件人。
演练:端到端模拟一次退信
你不需要已验证的发信域名。按照发送第一封邮件中的说明,从 onboarding@messagebird.dev 发送即可。可识别的沙盒地址不受引导域名“仅限已验证成员”限制的约束,但仍计入其每日配额。
先确保你有一个已订阅邮件事件的 webhook 端点(参见 Webhooks),然后发送:
const msg = await bird.email.send({
from: { email: "onboarding@messagebird.dev", name: "Bird" },
to: ["bounce+signup-flow@messagebird.dev"],
subject: "Sandbox bounce test",
html: "<p>This message will hard-bounce.</p>",
tags: [{ name: "flow", value: "signup" }],
metadata: { test_run: "docs-capture-1" },
});
console.log(msg.id, msg.status); // "em_…", "accepted"msg = client.email.send(
from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
to=["bounce+signup-flow@messagebird.dev"],
subject="Sandbox bounce test",
html="<p>This message will hard-bounce.</p>",
tags=[{"name": "flow", "value": "signup"}],
metadata={"test_run": "docs-capture-1"},
)
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{"bounce+signup-flow@messagebird.dev"},
Subject: "Sandbox bounce test",
HTML: "<p>This message will hard-bounce.</p>",
Tags: []bird.Tag{{Name: "flow", Value: "signup"}},
Metadata: map[string]any{"test_run": "docs-capture-1"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$message = $bird->email->send(
from: 'Bird <onboarding@messagebird.dev>',
to: ['bounce+signup-flow@messagebird.dev'],
subject: 'Sandbox bounce test',
html: '<p>This message will hard-bounce.</p>',
);
echo $message->getId(), ' ', $message->getStatus();bird email send \
--from onboarding@messagebird.dev \
--html '<p>This message will hard-bounce.</p>' \
--metadata '{"test_run":"docs-capture-1"}' \
--subject 'Sandbox bounce test' \
--tag flow=signup \
--to bounce+signup-flow@messagebird.devcurl -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": ["bounce+signup-flow@messagebird.dev"],
"subject": "Sandbox bounce test",
"html": "<p>This message will hard-bounce.</p>",
"tags": [{ "name": "flow", "value": "signup" }],
"metadata": { "test_run": "docs-capture-1" }
}'如果你的密钥以 bk_eu1_ 开头,请改为调用 https://eu1.platform.bird.com。
API 返回 202 Accepted 和一个 em_* 消息 ID,与生产环境发送没有任何区别。这正是目的所在:你测试的就是真实的代码路径。随后你的 webhook 端点依次收到 email.accepted、email.processed,最后收到 email.bounced。每个事件都会回传该次发送的 tags 和 metadata(发送时未提供则为 null),email.bounced 载荷包含完整的退信分类:
{
"type": "email.bounced",
"timestamp": "2026-07-23T14:51:00.362Z",
"data": {
"email_id": "em_01ky7qanhrejer0bn34v38hrxh",
"recipient_id": "er_01ky7qanhrejds4decpk83q5qq",
"workspace_id": "ws_01ky7m21hjffh9s8kq76gyb1xf",
"recipient": "bounce+signup-flow@messagebird.dev",
"recipient_role": "to",
"bounce_type": "hard",
"bounce_class": 10,
"bounce_code": "550",
"bounce_description": "5.1.1 Unknown User",
"sending_ip": null,
"tags": [{ "name": "flow", "value": "signup" }],
"metadata": { "test_run": "docs-capture-1" },
"broadcast_id": null
}
}各字段的含义见事件参考。载荷中没有任何模拟标记:事件类型和结构与真实硬退信完全相同。唯一能表明它是模拟事件的是收件人地址本身。因此,如果你的处理程序需要对测试流量做特殊处理,请根据 messagebird.dev 收件人域名来判断。
要验证系统绝不会向已被屏蔽的收件人发送邮件,请使用 suppressed@messagebird.dev 重复发送。断言你先收到 email.accepted,然后收到带有 rejection_reason: "recipient_suppressed" 的 email.rejected。你不应收到 email.processed 或任何投递事件。这与真实的被屏蔽收件人完全一致:消息先被接受,然后在处理阶段、实际发送之前被中止。
沙盒会做什么、不会做什么
- 不写入屏蔽列表。 模拟的硬退信和投诉不会把收件人加入你的屏蔽列表,因此这些地址可以重复使用。你的 webhook 仍会触发(
email.bounced、email.complained),所以你自己的屏蔽逻辑会得到完整测试。要测试“已被屏蔽”的拒绝路径,请使用专用的suppressed@地址。 - 绝不进行真实投递。 沙盒收件人会在消息到达投递基础设施之前被拦截。不会传输任何内容,不涉及任何收件箱,你的发信信誉也不受影响。
- 请求校验照常进行。 沙盒发送使用常规端点,因此结构校验、大小上限和头部规则会照常拒绝无效请求。沙盒跳过的是移交之后的所有环节:此后的渲染和投递行为不会被测试。
- 不模拟打开和点击。 魔法地址只模拟传输结果,没有人会打开这封邮件,因此
email.opened和email.clicked只会来自真实邮件。 - 统计数据包含沙盒流量。 沙盒发送会计入工作区的汇总统计数据以及退信率和投诉率。大量沙盒退信会使仪表板数据失真,但不会改变你的发件信誉。
后续步骤
- 事件:完整的事件类型及每个收件人的生命周期
- Webhook:订阅、签名验证和重试
- 发送你的第一封邮件:本演练所基于的引导域名快速入门
- 屏蔽列表:真实屏蔽列表的工作方式
- 测试邮件发送而不打扰任何人:一段视频,逐一介绍沙盒地址及每个地址产生的事件