Testowanie dostarczania e-maili (mail sandbox)
Mail sandbox testuje handlery webhooków, logikę supresji i wyniki dostarczania bez wysyłania do prawdziwej skrzynki. Wyślij przez normalny API na adres w domenie messagebird.dev. Część lokalna decyduje o wyniku: bounce@messagebird.dev powoduje odbicie, a delivered@messagebird.dev dostarczenie.
Wysyłka przez sandbox korzysta ze standardowych ścieżek akceptacji, zdarzeń i webhooków. Zwraca tę samą odpowiedź 202 i generuje te same kształty payloadów zdarzeń per odbiorca, co wysyłka produkcyjna. Payload nie zawiera flagi testowej. Wiadomość nie dociera do zewnętrznej infrastruktury dostarczania ani do prawdziwej skrzynki. Symulowane odbicia i skargi nie wpływają na reputację nadawcy ani nie zapisują się na liście supresji, więc możesz ponownie używać tych adresów.
Sandbox nie wymaga konfiguracji: żadnego przełącznika, trybu testowego ani specjalnego klucza API. Aktywuje się wyłącznie na podstawie adresu odbiorcy, na standardowych endpointach wysyłki (POST /v1/email/messages i POST /v1/email/batches) oraz w broadcastach: kontakt w grupie odbiorców, którego adres jest adresem sandbox, jest symulowany zamiast wysyłany, dzięki czemu możesz przećwiczyć kampanię bez wysyłania do nikogo. Symulowany odbiorca nadal zużywa limit wysyłki, więc próba generalna obciąża ten sam limit co prawdziwa wysyłka.
Magiczne adresy
Wszystkie adresy należą do domeny @messagebird.dev. Część lokalna wybiera wynik:
| Adres | Symulowany wynik | Sekwencja webhooków | Uwagi |
|---|---|---|---|
| delivered@ | Odbierający serwer pocztowy akceptuje wiadomość | email.accepted → email.processed → email.delivered | Ścieżka pozytywna |
| bounce@ / hardbounce@ | Twarde odbicie: SMTP 550, 5.1.1 Unknown User, klasa odbicia 10 | email.accepted → email.processed → email.bounced z bounce_type: "hard" | Brak zapisu na liście supresji, więc adres pozostaje do ponownego użycia |
| softbounce@ | Miękkie odbicie: SMTP 451, 4.3.0 Temporary failure, please retry, klasa 20 | email.accepted → email.processed → email.bounced z bounce_type: "soft" | Miękkie odbicia nigdy nie powodują supresji, zarówno rzeczywiste, jak i symulowane |
| deferred@ / delay@ | Odroczenie: SMTP 451, 4.2.1 Mailbox temporarily unavailable, will retry, klasa 21 | email.accepted → email.processed → email.deferred | Symulowane odroczenie jest ostateczne: nie następuje ponowna próba, więc odbiorca pozostaje w stanie deferred |
| complaint@ / spam@ | Odbiorca zgłasza wiadomość jako spam (feedback_type: "abuse") | email.accepted → email.processed → email.complained | Brak zapisu supresji; adres do ponownego użycia |
| suppressed@ | Odbiorca jest traktowany tak, jakby już znajdował się na liście supresji | email.accepted → email.rejected z rejection_reason: "recipient_suppressed" | Przerywa przetwarzanie dokładnie tak jak prawdziwy odbiorca z supresji: brak email.processed, brak zdarzeń dostarczenia |
| reject@ | Wiadomość zostaje odrzucona przed jakąkolwiek próbą dostarczenia | email.accepted → email.rejected z rejection_reason: "transmission_failed" | Brak email.processed ani zdarzeń dostarczenia |
Powyższe sekwencje to webhooki generowane przez pojedynczą wysyłkę. W broadcastach pomiń wiodący email.accepted: odbiorca broadcastu jest akceptowany niezależnie, ale to zdarzenie rejestrujemy bez wysyłania webhooka, więc każda sekwencja zaczyna się od tego, co następuje dalej, czyli email.processed na ścieżkach dostarczenia i email.rejected dla suppressed@ i reject@. Wszystko po tym jest identyczne, a dokumentacja zdarzeń opisuje tę zasadę w pełni.
Wysyłka może łączyć odbiorców sandbox i rzeczywistych. Każdy odbiorca ma własny cykl życia: rzeczywiści odbiorcy są dostarczani normalnie, odbiorcy sandbox są symulowani.
Te same zdarzenia pojawiają się także na osi czasu wiadomości w logu e-mail i w endpoincie zdarzeń API, więc możesz korzystać z sandboxa bez endpointu webhook i odczytywać wyniki bezpośrednio.
Zasady adresowania
- Wykrywanie opiera się wyłącznie na części lokalnej i tylko w domenie messagebird.dev. bounce@yourdomain.com to zwykły adres.
- Tylko części lokalne z tabeli magicznych adresów są magiczne. Każdy inny adres w domenie messagebird.dev to zwykły odbiorca. Gdy wysyłasz ze współdzielonej domeny onboardingowej, adres musi należeć do zweryfikowanego członka obszaru roboczego.
- Dopasowanie nie rozróżnia wielkości liter: Bounce@messagebird.dev i bounce@messagebird.dev zachowują się identycznie.
- Subadresowanie +label jest usuwane przed dopasowaniem: bounce+signup-flow@messagebird.dev nadal powoduje odbicie. Używaj etykiet do korelowania przypadków testowych; pełny adres, łącznie z etykietą, pojawia się w zdarzeniach i webhookach, więc każdy przebieg testowy może oznaczać własnych odbiorców.
Przewodnik: symulacja odbicia od początku do końca
Nie potrzebujesz zweryfikowanej domeny wysyłkowej. Wyślij z onboarding@messagebird.dev, jak opisano w Wyślij swój pierwszy e-mail. Rozpoznane adresy sandbox są zwolnione z wymogu zweryfikowanego członka domeny onboardingowej, ale nadal wliczają się do jej dziennego limitu.
Upewnij się, że masz endpoint webhook subskrybujący zdarzenia e-mail (zobacz Webhooki), a następnie wyślij:
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" }
}'Jeśli Twój klucz zaczyna się od bk_eu1_, wywołaj https://eu1.platform.bird.com.
Endpoint API odpowiada 202 Accepted z identyfikatorem wiadomości em_*, nieodróżnialnym od wysyłki produkcyjnej. O to chodzi: ścieżka kodu, którą testujesz, jest Twoją prawdziwą ścieżką. Twój endpoint webhook otrzymuje następnie email.accepted, email.processed i na końcu email.bounced. Każde zdarzenie zawiera tags i metadata wysyłki (null, gdy wysyłka ich nie miała), a payload email.bounced zawiera pełną klasyfikację odbicia:
Przykład kodu
{
"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
}
}Znaczenie pól opisuje dokumentacja zdarzeń. W payloadzie nie pojawia się żadna flaga symulatora: typ i kształt zdarzenia są dokładnie takie, jakie generuje prawdziwe twarde odbicie. Jedynym elementem identyfikującym je jako symulowane jest sam adres odbiorcy, więc jeśli Twój handler musi wyodrębniać ruch testowy, filtruj po domenie odbiorcy messagebird.dev.
Aby zweryfikować, że odbiorca z supresji nigdy nie otrzyma wiadomości, powtórz wysyłkę z suppressed@messagebird.dev. Sprawdź, czy otrzymujesz email.accepted, a następnie email.rejected z rejection_reason: "recipient_suppressed". Nie powinieneś otrzymać email.processed ani zdarzeń dostarczenia. To dokładnie odzwierciedla zachowanie prawdziwego odbiorcy z supresji: wiadomość jest akceptowana, a następnie przetwarzanie jest przerywane przed jakąkolwiek wysyłką.
Co sandbox robi, a czego nie robi
- Brak zapisów na liście supresji. Symulowane twarde odbicia i skargi nie dodają odbiorcy do listy supresji; to właśnie pozwala na ponowne użycie adresów. Webhook nadal się uruchamia (email.bounced, email.complained), więc Twoja własna logika supresji jest w pełni testowana. Aby przetestować ścieżkę odrzucenia z powodu istniejącej supresji, użyj dedykowanego adresu suppressed@.
- Żadnego rzeczywistego dostarczenia. Odbiorcy sandbox są przechwytywani, zanim wiadomość dotrze do infrastruktury dostarczania. Nic nie jest transmitowane, żadna skrzynka nie jest zaangażowana, a Twoja reputacja nadawcy pozostaje nienaruszona.
- Walidacja żądań nadal obowiązuje. Wysyłka przez sandbox korzysta ze standardowych endpointów, więc sprawdzanie schematu, limity rozmiaru i reguły nagłówków odrzucają nieprawidłowe żądanie jak zwykle. Sandbox pomija wszystko po przekazaniu: renderowanie i zachowanie dostarczania od tego momentu nie są testowane.
- Otwarcia i kliknięcia nie są symulowane. Magiczny adres symuluje wynik transmisji, a nikt nie otwiera wiadomości, więc email.opened i email.clicked pochodzą wyłącznie z prawdziwej poczty.
- Statystyki obejmują ruch sandbox. Wysyłki sandbox wliczają się do zagregowanych statystyk obszaru roboczego oraz wskaźników odbić i skarg. Intensywne odbijanie w sandboxie zniekształca dashboardy, nie zmieniając reputacji.
Następne kroki
- Zdarzenia: pełny słownik zdarzeń i cykl życia per odbiorca
- Webhooki: subskrybowanie, weryfikacja podpisu i ponowne próby
- Wyślij swój pierwszy e-mail: szybki start z domeną onboardingową, na którym opiera się ten przewodnik
- Supresje: jak działa lista supresji
- Testowanie e-maili bez spamowania kogokolwiek: film prezentujący adresy sandbox i zdarzenia generowane przez każdy z nich
Powiązane zasoby
Kontynuuj z dokumentacją, przewodnikami i przykładami dotyczącymi tego tematu. Zasoby są w języku angielskim.