The Developer's Email Testing Checklist for Staging
A practical checklist for testing application email before release: delivery, rendering, links, authentication, failures and a safe staging setup.
Email is one of the few parts of an application that leaves your system and lands in somebody else's, where you cannot debug it. That makes it easy to ship broken: a link that points at localhost, a template with a missing variable, a reset email that never leaves staging. This checklist covers what to verify before a release, and how to set up a test environment that keeps real customers safe.
1. Set up the environment safely
The most important rule: staging must never email real customers.
- Use a separate sending configuration for each environment, with its own API key or SMTP credentials.
- In development and staging, either send everything to a test inbox, or use an allow-list so only addresses on your team's domain or a test inbox domain receive mail.
- Add the environment to the subject line in non-production ("[STAGING] Reset your password") so nobody mistakes a test for the real thing.
- Keep API keys in environment variables, never in the repository.
For capturing test mail without touching real mailboxes, a public temporary inbox is the simplest option, as long as the content is not sensitive.
2. Delivery checks
| Check | Pass condition |
|---|---|
| Message arrives | Within a few seconds for time-sensitive mail |
| Correct recipient | To, Cc and Bcc as designed; Bcc not visible |
| Correct sender | Domain matches your authenticated domain |
| Reply-To | Set where replies should go |
| No duplicates | A retry does not send two copies (see idempotency keys) |
| Failure handling | Your app copes if the mail service is down |
3. Content checks
- Subject and preheader read well and contain no template placeholders.
- Names and dynamic values render correctly. Test a name with an apostrophe, an accent, an emoji and a very long value.
- Plain-text version exists and is useful.
- Links point to the right environment, use HTTPS, and work when clicked.
- Unsubscribe exists for marketing mail and not for transactional mail.
- Dates and currencies use the recipient's locale, or at least say the time zone.
- Escaping. A user name like
<script>must appear as text, not markup.
4. Rendering checks
Email HTML is not web HTML. Outlook for Windows uses Word's rendering engine; Gmail removes some CSS; dark mode changes colours. Test in at least:
- Gmail (web and app)
- Outlook (desktop and web)
- Apple Mail / iOS
- A dark-mode setting
Keep layouts simple: tables for structure, inline styles, a single column on mobile, and meaningful alt text on images because many clients block images by default.
5. Authentication checks
Send a real message to a Gmail or Outlook account you control and open the original headers:
spf=pass,dkim=pass,dmarc=pass- The
Fromdomain aligns with the signing domain.
Step-by-step instructions are in how to check SPF, DKIM and DMARC.
6. Security checks
- Tokens in links are long, random and single-use.
- Reset and verification links expire, and expired ones fail gracefully. See password reset email security.
- The same response is shown whether or not an address has an account, so attackers cannot discover who is registered.
- Rate limits exist on every form that triggers an email, otherwise your app can be used to spam third parties.
- Secrets never appear in the email body, logs or the URL query string of tracking links.
7. Failure and edge cases
- The mail service returns a temporary error. Does your code retry, queue, or tell the user?
- The recipient address is invalid. Is the bounce handled?
- A request times out after the mail was actually sent. Does a retry duplicate it?
- The monthly sending allowance is used up. Does the user see a clear message?
- The template is missing a variable. Does sending fail loudly instead of sending a broken message?
8. Observability
- Log message IDs and outcomes (not message bodies or secrets).
- Alert on a sudden fall in sent volume or a rise in failures.
- Give support a way to find out whether a particular email was sent.
Pre-release summary
| Area | Minimum |
|---|---|
| Environments | Staging cannot reach real customers |
| Delivery | Arrives fast, no duplicates |
| Content | No placeholders, links correct |
| Rendering | Gmail, Outlook, mobile, dark mode |
| Authentication | SPF, DKIM, DMARC pass |
| Security | Tokens random, expiring, rate-limited |
| Failures | Retries and clear user messages |
How Mailions helps with testing
Each domain on Mailions has a verified sending identity, so staging mail can be authenticated just like production. The sending API accepts an Idempotency-Key so a retried request does not produce a second copy, and a queued status when the mail server is briefly unavailable. The panel's sending log shows each message's outcome, and public temporary inboxes at /temp give you a login-free place to read test mail.
Frequently asked questions
How do I test emails without sending them to real users?
Give staging its own configuration that can only deliver to test inboxes, such as a mail catcher or temporary addresses, and block real customer domains. Never reuse production recipient lists or API keys in staging.
What should I test in a transactional email?
That it arrives quickly, has the right sender, subject and links, works as plain text and HTML, passes SPF, DKIM and DMARC, and that failures such as a bad address or a slow mail server do not break the user's flow.
Should email tests run in CI?
The parts you control should: template rendering, link building and the code that calls your sending API. Full delivery tests to a real inbox are slower and best run before a release or on a schedule, with unique addresses so results never mix.