Testar entrega de e-mail (sandbox de e-mail)
O sandbox de e-mail testa handlers de webhook, lógica de supressão e resultados de entrega sem enviar para uma caixa de entrada real. Envie pela API normal para um endereço em messagebird.dev. A parte local determina o resultado: bounce@messagebird.dev causa bounce e delivered@messagebird.dev entrega.
Um envio pelo sandbox usa os caminhos normais de aceitação, evento e webhook. Ele retorna a mesma resposta 202 e produz os mesmos formatos de payload de evento por destinatário que um envio de produção. O payload não tem flag de teste. A mensagem não alcança a infraestrutura de entrega externa nem uma caixa de entrada real. Bounces e reclamações simulados não afetam a reputação de envio nem gravam na lista de supressão, então você pode reutilizar os endereços.
O sandbox não exige configuração: sem toggle, sem modo de teste, sem chave API especial. Ele é acionado exclusivamente pelo endereço do destinatário, nos endpoints normais de envio (POST /v1/email/messages e POST /v1/email/batches) e em um broadcast: um contato na audiência cujo endereço é um endereço de sandbox é simulado em vez de receber o envio real, e é assim que você ensaia uma campanha sem enviar e-mail para ninguém. Um destinatário simulado ainda conta contra sua cota de envio, então o ensaio exercita a mesma cota que o envio real usaria.
Endereços mágicos
Todos os endereços estão em @messagebird.dev. A parte local seleciona o resultado:
| Endereço | Resultado simulado | Sequência de webhooks | Notas |
|---|---|---|---|
delivered@ | O servidor de e-mail receptor aceita a mensagem | email.accepted → email.processed → email.delivered | O caminho feliz |
bounce@ / hardbounce@ | Hard bounce: SMTP 550, 5.1.1 Unknown User, bounce class 10 | email.accepted → email.processed → email.bounced com bounce_type: "hard" | Sem gravação na lista de supressão, então o endereço continua reutilizável |
softbounce@ | Soft bounce: SMTP 451, 4.3.0 Temporary failure, please retry, class 20 | email.accepted → email.processed → email.bounced com bounce_type: "soft" | Soft bounces nunca suprimem, reais ou simulados |
deferred@ / delay@ | Deferral: SMTP 451, 4.2.1 Mailbox temporarily unavailable, will retry, class 21 | email.accepted → email.processed → email.deferred | O deferral simulado é terminal: nenhuma nova tentativa ocorre, então o destinatário permanece deferred |
complaint@ / spam@ | O destinatário reporta a mensagem como spam (feedback_type: "abuse") | email.accepted → email.processed → email.complained | Sem gravação de supressão; reutilizável |
suppressed@ | O destinatário é tratado como já presente na sua lista de supressão | email.accepted → email.rejected com rejection_reason: "recipient_suppressed" | Interrompe o processamento exatamente como um destinatário suprimido real: sem email.processed, sem eventos de entrega |
reject@ | A mensagem é rejeitada antes de qualquer tentativa de entrega | email.accepted → email.rejected com rejection_reason: "transmission_failed" | Nenhum email.processed ou evento de entrega segue |
As sequências acima são os webhooks que um envio individual produz. Em um broadcast, descarte o email.accepted inicial: um destinatário de broadcast é aceito por si só, mas registramos esse evento sem enviar um webhook para ele, então cada sequência começa no que vem depois, email.processed nos caminhos de entrega e email.rejected para suppressed@ e reject@. Tudo depois disso é idêntico, e a referência de eventos cobre a regra por completo.
Um envio pode misturar destinatários de sandbox e reais. Cada destinatário tem seu próprio ciclo de vida: destinatários reais entregam normalmente, destinatários de sandbox são simulados.
Os mesmos eventos também aparecem na linha do tempo da mensagem no log de e-mail e na API de eventos, então você pode usar o sandbox sem um endpoint de webhook e ler os resultados de volta.
Regras de endereçamento
- A detecção é apenas pela parte local, e somente no domínio
messagebird.dev.bounce@yourdomain.comé um endereço normal. - Apenas as partes locais na tabela de endereços mágicos são mágicas. Qualquer outro endereço em
messagebird.devé um destinatário normal. Quando você envia pelo domínio compartilhado de onboarding, esse endereço precisa pertencer a um membro verificado do espaço de trabalho. - A correspondência não diferencia maiúsculas de minúsculas:
Bounce@messagebird.devebounce@messagebird.devse comportam de forma idêntica. - O subendereçamento
+labelé removido antes da correspondência:bounce+signup-flow@messagebird.devainda causa bounce. Use labels para correlacionar casos de teste; o endereço completo, incluindo o label, aparece nos seus eventos e webhooks, então cada execução de teste pode marcar seus próprios destinatários.
Passo a passo: simular um bounce, de ponta a ponta
Você não precisa de um domínio de envio verificado. Envie de onboarding@messagebird.dev, como descrito em Envie seu primeiro e-mail. Endereços de sandbox reconhecidos são isentos da restrição de membro verificado do domínio de onboarding, mas ainda contam na cota diária.
Certifique-se de que você tem um endpoint de webhook inscrito em eventos de e-mail (veja Webhooks), e então envie:
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" }
}'Se a sua chave começa com bk_eu1_, chame https://eu1.platform.bird.com em vez disso.
A API responde 202 Accepted com um ID de mensagem em_*, indistinguível de um envio de produção. Esse é o objetivo: o caminho de código que você está testando é o seu real. Seu endpoint de webhook então recebe email.accepted, email.processed e, por fim, email.bounced. Cada evento ecoa o tags e o metadata do envio (null quando o envio não tinha nenhum), e o payload email.bounced tem a classificação completa do bounce:
{
"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
}
}Os significados dos campos estão na referência de eventos. Nenhuma flag de simulador aparece em qualquer lugar do payload: o tipo e o formato do evento são exatamente o que um hard bounce real produz. A única coisa que o identifica como simulado é o próprio endereço do destinatário, então se o seu handler precisa tratar tráfego de teste de forma especial, use o domínio do destinatário messagebird.dev como chave.
Para verificar que um destinatário já suprimido nunca recebe envio, repita o envio com suppressed@messagebird.dev. Confirme que você recebe email.accepted, depois email.rejected com rejection_reason: "recipient_suppressed". Você não deve receber nenhum email.processed ou evento de entrega. Isso espelha um destinatário suprimido real exatamente: a mensagem é aceita, depois interrompida durante o processamento antes de qualquer envio.
O que o sandbox faz e não faz
- Sem gravação na lista de supressão. Hard bounces e reclamações simulados não adicionam o destinatário à sua lista de supressão; é isso que mantém os endereços reutilizáveis. Seu webhook ainda dispara (
email.bounced,email.complained), então a sua própria lógica de supressão é totalmente exercitada. Para testar o caminho de rejeição de já suprimido, use o endereço dedicadosuppressed@. - Sem entrega real, nunca. Destinatários de sandbox são interceptados antes de a mensagem alcançar a infraestrutura de entrega. Nada é transmitido, nenhuma caixa de entrada é envolvida, e sua reputação de envio permanece intacta.
- A validação da solicitação ainda se aplica. Um envio pelo sandbox usa os endpoints normais, então verificações de schema, limites de tamanho e regras de cabeçalho rejeitam uma solicitação inválida normalmente. O que o sandbox pula é tudo após a transferência: renderização e comportamento de entrega além desse ponto não são exercitados.
- Aberturas e cliques não são simulados. Um endereço mágico simula o resultado da transmissão, e ninguém abre a mensagem, então
email.openedeemail.clickedvêm apenas de e-mails reais. - As estatísticas incluem tráfego de sandbox. Envios pelo sandbox contam nas estatísticas agregadas do seu espaço de trabalho e nas taxas de bounce e reclamação. Bounces intensos pelo sandbox distorcem seus dashboards, mas deixam sua reputação inalterada.
Próximos passos
- Eventos: o vocabulário completo de eventos e o ciclo de vida por destinatário
- Webhooks: inscrição, verificação de assinatura e novas tentativas
- Envie seu primeiro e-mail: o quickstart do domínio de onboarding em que este passo a passo se baseia
- Supressões: como a lista de supressão real funciona
- Testar emails sem incomodar ninguém: um vídeo que percorre os endereços de sandbox e os eventos que cada um produz
Recursos relacionados
Continue com a documentação, guias e exemplos sobre este tópico.