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.dev e bounce@messagebird.dev se comportam de forma idêntica.
- O subendereçamento +label é removido antes da correspondência: bounce+signup-flow@messagebird.dev ainda 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:
Exemplo de código
{
"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 dedicado suppressed@.
- 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.opened e email.clicked vê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. Os recursos estão em inglês.
Explore a funcionalidadeTest email deliverySiga o percurso de aprendizagemOperate messaging reliably
Experimente na prática e obtenha um resumo de implementação