Learn
How to test email in staging
Your staging environment sends the same signup confirmations, OTPs, password-reset links and cron digests your production environment does — but you never want a test run to land in a real customer’s inbox. This is a vendor-neutral guide to doing it safely: the two strategies for testing outbound email, when each one fits, and a checklist you can work through before you ship.
Last updated July 2026
The one rule that governs email in staging is simple: mail your app generates in a non-production environment must never reach a real user. Everything below is a way to honour that rule while still confirming the mail your product sends is correct. There are two broad strategies, and mature teams often use both.
Strategy A — intercept outbound so nothing leaves
The first approach is to capture mail at the SMTP boundary. You point your staging app at a sandbox SMTP endpoint that accepts every message but delivers none of it onward. The mail is trapped in a virtual inbox you inspect through a dashboard or API; it can never escape to a real address, no matter what recipient your code puts on it.
This is the territory of a mail sandbox — for example, Mailtrap’s Email Sandbox. It is a good fit when:
- Your recipient addresses are unpredictable — seeded test users, fixtures, or fuzzed data you don’t control ahead of time.
- You want a hard guarantee that a misconfigured environment physically cannot send to the outside world.
- You are wiring email assertions into an automated test suite and want to fetch captured messages over an API.
The trade-off is that you are inspecting mail that never actually traversed real DNS and a real receiving MX. Because the message is intercepted before delivery, SPF, DKIM and DMARC are simulated or scored rather than evaluated by a receiver that looked up your live records. For a closer look at that distinction, see our comparison with Mailtrap.
Strategy B — deliver to real private inboxes you own
The second approach is to let the mail actually send — through your real provider, over real DNS, to a real receiving server — but address it to inboxes you control and no one else does. You then inspect exactly what arrived: the same bytes a customer would have received, having passed through the same authentication checks. This is what Perenmail is built for: private, registered-only inboxes on real domains, tied to your signed-in account.
This fits when:
- You control the recipient address — you set the
to:on your staging test users, so you can point them at inboxes you own. - You need to verify the mail as delivered: real headers, real SPF, DKIM and DMARC results, real rendering.
- You want the inbox to stay put across a testing session or across releases, rather than expiring underneath you.
The trade-off is the mirror image of Strategy A: mail does leave your app, so you have to address it deliberately. That is a matter of discipline in your seed data, not a limitation — and it buys you the one thing an intercept sandbox can’t give you, which is confidence about what a receiving server actually did with your message.
Which one should you use?
They answer different questions, so the choice is really about what you’re trying to prove:
- Reach for intercept (A) when the priority is containment and automated assertions over unpredictable recipients.
- Reach for real delivery (B) when the priority is fidelity — you want to see the message a human would receive, click its links, and read its authentication results end-to-end.
A common setup is to use an intercept sandbox in CI, where runs are frequent and recipients are synthetic, and a real private inbox for manual QA and staging sign-off, where a human is verifying that the actual delivered message looks and behaves right. See QA & staging email for how that manual pass fits into a release.
A checklist for staging email testing
Whichever strategy you pick, the things worth verifying are the same. Work through these before you consider an email flow shippable:
- Send to addresses you control. Never let staging address a real user. Seed your test accounts with inboxes you own (or an intercept sandbox), and fail loudly if anything tries to send elsewhere.
- Verify templates render. Open the HTML body and confirm it looks right — not just the plain-text fallback. Check that merge fields resolved and no
{{placeholder}}leaked through. - Click every link. Confirm each URL points at the right environment — staging links should never point at production, and vice versa — and that tracking or redirect wrappers resolve.
- Check OTP and reset tokens. Verify one-time codes and password-reset links actually work when you use them, and that they expire when they should. See testing OTP & 2FA and password-reset testing.
- Confirm delayed and cron mail actually fires. Scheduled digests, trial-ending nudges and retry emails are the easiest to miss because nothing arrives immediately. Watch a persistent inbox over the window and confirm the job ran — see delayed & cron email.
- Inspect SPF, DKIM and DMARC. For real-delivery testing, read the authentication results on the delivered message so you catch a broken selector or an alignment problem before production does. Our SPF, DKIM & DMARC guide covers how to read them.
Strategy B, step by step, with Perenmail
Here is the real-delivery approach worked through end-to-end. The mechanics are the same with any private inbox; the specifics below are how Perenmail does it.
1. Make a project per environment. Create a Perenmail project for the environment you’re testing — one for
staging, another forprevieworlocal, so the mail from each is grouped and never tangled together.2. Create the inboxes you’ll send to. Add a private inbox on a real domain for each test persona — for example one for the signup flow, one for password reset. Because Perenmail inboxes are persistent by default, they stay exactly where you left them; you keep each address across releases until you deliberately archive or delete it, so your seed data doesn’t rot between test runs.
3. Point your staging test users at those addresses. Set the recipient in your seed data or fixtures to the inboxes you just created. Your app sends normally, through your real provider.
# staging seed — send to inboxes you own TEST_SIGNUP_EMAIL=signup@your-inbox.perenmail.com TEST_RESET_EMAIL=reset@your-inbox.perenmail.com4. Trigger the flow and watch it land live. Run the signup, request the reset, or fire the cron job. Perenmail’s listening indicator and poll heartbeat surface new mail at the top the moment it arrives — no reload — so you see the message appear as your app sends it.
5. Inspect what actually arrived. Open the message. HTML renders in a locked, script-free sandboxed iframe, and you can toggle between
Rendered,TextandSource. One-tap capture pulls out every verification code and every link, so you can copy the OTP or open the reset link in a single click and confirm it works.6. Debug the delivery itself. For outbound problems, the raw source view shows the full
RFC822message with synthesized SPF, DKIM and DMARC auth results, so you can confirm your own mail authenticated correctly. Raw source, headers and attachments are part of Personal+ — see plans & pricing.
The free Personal plan gives you one project and up to five persistent inboxes with live delivery and one-click code and link capture — enough to cover a single staging environment’s core flows. Personal+ raises the project and inbox limits and adds the raw source, headers and attachment views for outbound debugging.
Keep reading
- QA & staging email — where the manual verification pass fits into a release.
- Delayed & cron email — confirming scheduled and retried mail actually fires.
- SPF, DKIM & DMARC explained — how to read the authentication results on delivered mail.
- Perenmail vs Mailtrap — real private inboxes versus an intercept sandbox.
Test your staging email against a real inbox
Point your staging flows at a private, persistent Perenmail address and inspect exactly what arrives. Free to start, no credit card.