All posts
    Guides

    Email Testing Best Practices for Developers

    The Devmailr Team7 min read

    Email is one of the flakiest things to test — it’s asynchronous, it lives outside your app, and the obvious approaches (a shared Gmail, a sleep() and a regex) fall apart fast. After building a lot of email tests, here are ten practices that keep them fast, reliable, and meaningful.

    1. Use a disposable inbox, not a real mailbox

    Real mailboxes need OAuth to read, collide across parallel runs, and force you to store credentials in CI. A disposable inboxis isolated, readable over an API, and needs nothing but an API key. It’s the foundation everything else builds on.

    2. One inbox per test, not a shared one

    Give every test (or every run) its own address with a unique prefix — a timestamp works. Shared inboxes mean tests read each other’s mail and fail intermittently under parallelism.

    3. Wait, don’t sleep

    sleep(5)is both slow and flaky — too short and the email hasn’t arrived, too long and your suite crawls. Block on a wait-for-email call that returns the instant the message lands.

    4. Filter what you wait for

    Don’t wait for “any” email — a welcome message can arrive before the code email and fail your assertion. Narrow it with from, subject, or has_code=true so you get exactly the message you mean.

    5. Extract codes and links, don’t regex bodies

    A \d{6} regex matches years, order numbers, and zip codes too. Read the parsed extractedCodes[0] and primaryLink fields instead — see testing OTP codes.

    6. Assert on the email itself, not just the flow

    Since you have the raw message, check it’s correct: the right sender and subject, and that it passes authentication.

    javascript
    expect(email.from).toContain('noreply@myapp.com')
    expect(email.verdicts.dkim).toBe('PASS')

    That guards against deliverability regressions at the same time.

    7. Clean up after each test

    Delete the inbox when the test finishes (a fixture teardown is ideal) so leftover mailboxes don’t pile up against your plan’s limit and 403 your next run.

    8. Keep the API key out of the repo

    Read it from an environment variable (conventionally DEVMAILR_API_KEY) sourced from a CI secret — never commit it.

    9. Test the negative cases

    The happy path is the easy half. Also assert that a wrong or expired code is rejected, that codes/links are single-use, and that requesting a reset for an unknown email doesn’t leak whether the account exists.

    10. Don’t re-test the provider on every run

    You’re testing your integration— that your app sends the right email and your code handles it. You don’t need a full deliverability audit in every test. Keep one focused SPF/DKIM/DMARC check rather than asserting verdicts everywhere.

    Put it into practice

    These map cleanly onto specific stacks — see the guides for Playwright, Cypress, Python, and Node.js with Jest.

    Try it in two minutes

    Create a disposable prefix@devmailr.app inbox over the API, send mail to it from your app, and read it in your tests. Free plan, no card required.