> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devhelm.io/llms.txt
> Use this file to discover all available pages before exploring further.

# What Is Email Testing?

> What email testing proves, how a testing mailbox differs from public inboxes, and why a provider's 250 is not proof of delivery

Email testing proves that your app sent the right email to the right address, that it arrived, and that the code or link inside it works. A test triggers the send, waits for the message in a mailbox it controls, and asserts on what it reads.

## How it works

A testing mailbox receives mail at addresses your test controls and lets the test read it through an API:

1. **Get an address** that belongs to this test and no other.
2. **Trigger** the email: submit a sign-up form, request a password reset.
3. **Wait** for the message to arrive at that address.
4. **Read and assert**: the subject, the recipient, the one-time code, the link.

## Three kinds of mailbox

| | Who sends | Who can read | Use it for |
| - | - | - | - |
| **Testing mailbox** | Your app, or your test via inject | Members and API keys of your workspace | Assertions in automated tests |
| **Alert email channel** | DevHelm | Your team's real inboxes | Hearing about incidents |
| **Public inbox service** | Anyone | Anyone who knows or guesses the address | Throwaway manual checks |

A DevHelm testing mailbox and the [email alert channel](/integrations/email) are separate features: one receives mail for tests, the other sends notifications to people.

Messages in a public disposable-inbox service are readable by anyone with the address, which is the wrong place for one-time codes and password-reset links, even in staging. A testing mailbox accepts mail from anyone, like any address, but reading it needs access to your workspace.

## Assigned domain or your own

Every address needs a domain whose MX record points at the testing mailbox.

| | Assigned domain | Your own domain |
| - | - | - |
| Name | `<workspace>-<4 chars>.devhelmmail.com` | A subdomain you own, such as `test.example.com` |
| Setup | Created on first use | A TXT record and an MX record, then verify |
| Plan | Every testing plan | Team testing plan and above |

Start with the assigned domain. Move to your own when your app only accepts sign-ups from certain domains, or when addresses have to look like yours. [Receive on your own domain](/testing/email/custom-domains) covers the DNS.

On either domain, every local part is its own inbox. There is nothing to create per address, so a test can use a fresh address on every run.

## Inject or real delivery

| | Inject | Real delivery |
| - | - | - |
| Path | One API call puts the message in the mailbox | Your app, your mail provider, then MX delivery to the mailbox |
| Needs | Nothing beyond an API key | A configured provider that is allowed to send to the address |
| Tests | Your templates, OTP and link extraction, and your test code | The whole sending path, including provider settings |
| Speed | Seconds | Seconds to minutes, set by the provider |

Inject runs the same parsing as real mail, including OTP and link extraction, so a test written against inject keeps working when you switch to real delivery. Use inject for fast tests and tutorials. Use real delivery for the end-to-end test that proves users actually get the email.

## Why "the provider returned 250" is not proof

When your app hands a message to a mail provider, the provider answers right away: an SMTP `250` or an HTTP `200` or `202`. That answer means *accepted for delivery*. It does not mean delivered. Between the two, the message can be:

* held by a sandbox or test mode that accepts mail and never sends it,
* dropped because the address is on the provider's suppression list after an earlier bounce,
* queued behind rate limits, or retried after a temporary failure at the receiving server,
* sent to the wrong address by a template or configuration bug.

DevHelm's inject works the same way: it answers `202` when it accepts the message, before the message is stored. A message accepted right at the organization's monthly quota can still be dropped.

The proof is the message itself, readable in the mailbox. That is why a test waits for the message instead of checking the send call's status.

## Waiting for the message

A wait call blocks until a matching message is stored, then returns it, or fails with `408 WAIT_TIMEOUT`. It matches messages received at or after a start time, so take that time **before** the trigger:

```typescript theme={null}
const since = new Date().toISOString();
await submitSignupForm(address.email);
const message = await address.wait({ receivedAfter: since, timeoutMs: 60_000 });
```

The DevHelm CLI and SDKs default the start time to the moment you call wait, which misses a message that arrived first. Pass it explicitly.

A fresh address per test means an older message cannot match. Reusing one address across runs is how a test ends up reading yesterday's code.

## Reading what is inside

Each message comes with the codes and links already extracted. A code labelled in the email, as in "Your verification code is 482913", appears in `otp`, and each link appears in `links` with its text. Click tracking from your mail provider rewrites links to the provider's redirect host, and the testing mailbox keeps them as written. [Read OTP codes and links](/testing/email/otp-and-links) covers several codes and tracked links.

## Keep it to test data

A testing address receives any mail sent to it. Use it for test accounts only, never for real users, real accounts, or customer data. [Security and data](/testing/security) lists who can read what.

## DevHelm email testing

<CardGroup cols={2}>
  <Card title="Your first test email" icon="rocket" href="/guides/first-email">
    Get an address, inject a message, and assert on the code.
  </Card>

  <Card title="How testing mailboxes work" icon="envelope" href="/testing/email/overview">
    Domains, stored fields, and every way to read a message.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.