Prerequisites
Prerequisites
- The CLI (
npm install -g devhelm) or curl, plusjq, for the steps;@devhelm/sdkwith Vitest (ESM only), ordevhelmwith pytest, for the test - Room for an email domain, or an assigned domain that already exists in the workspace. Domains are counted per organization: 1 on the Free and Developer testing plans
DEVHELM_WORKSPACE_ID and takes the key in its constructor. With curl, send the workspace as a header:Receive a message
1
Get an address
first-email-1a2b3c4d@defaultworks-4j4n.devhelmmail.com. The first call in a workspace creates its assigned domain, and later calls reuse it. Read the domain from the helper or devhelm email domains list; do not build the name yourself. There is no mailbox to create: every local part on the domain is its own inbox. The helper adds a random 8-hex suffix, so each call gives a fresh address.2
Record the start time, then inject
SINCE before anything sends: wait returns the oldest message received at or after it. Without it, the CLI and SDKs use the clock at the moment you call wait, and a message that already arrived is missed.Inject stands in for your app’s mail provider and runs the same parsing as real mail, including code and link extraction. It answers 202 with {eventId, receivedAt, inbox}: accepted, not yet stored. Injected messages count toward the monthly event quota.3
Wait and assert on the code
timeoutMs (default 30000), the API returns 408 with code WAIT_TIMEOUT. REST returns {"message": {...}} with no data wrapper. CLI email wait --to takes the full address.otp is a list of {value, source}, where source is text or html. A code that appears in both parts is listed once. Use otp[0] when the email has one code, and match on value when it has several. Labelled codes are extracted, as in “verification code is 482913” or “Code: 482913”; years and phone numbers are ignored.The message also carries from, to, subject, headers, bodyPreview, and links ({href, text}). text and html are null on wait and list responses: get the message by ID for the body, or in Python call message.source(), since the Python SDK has no text or html. Wait for an email in a test shows both.4
Clear the address
Write the test
The same flow as one test file, with cleanup.receive stands in for the code under test: replace it with the call that makes your app send its email.
npx vitest run first-email.test.ts or pytest test_first_email.py. One test passes.
The runner timeout (45 s) is longer than the wait timeout (30 s), so a missing email fails with the API’s 408 instead of the runner killing the test. Each run gets a new address, so an earlier message cannot match.
Send real mail from your app
The address also receives real mail.devhelmmail.com has a wildcard MX record, so your app’s mail provider can deliver to the address like any other, and the same wait picks the message up. Point your sign-up form at an address from the helper, take since before you submit, and wait. Test a sign-up email flow covers provider sandboxes and click tracking.
Troubleshooting
The wait returns 408 WAIT_TIMEOUT
The wait returns 408 WAIT_TIMEOUT
sincewas taken after the inject, or not passed. Take it before the trigger.- The address differs from the one you sent to. CLI
--totakes the full address, not the local part. - Your organization hit its monthly event quota. Inject can still answer
202and then drop the message. See Limits, quotas, and errors.
403 INBOUND_EMAIL_DOMAIN_LIMIT_REACHED
403 INBOUND_EMAIL_DOMAIN_LIMIT_REACHED
The domain cap counts the whole organization. On Free or Developer, a domain in another workspace blocks creating one here. Use the workspace that has the domain.
otp is empty
otp is empty
The code has no label near it. Put “code” or “verification code” next to it in your template, or read the body and match it yourself. See Read OTP codes and links.
Next steps
How testing mailboxes work
Domains, stored fields, search, source, and raw downloads.
Read OTP codes and links
Several codes, picking a link, and click-tracking redirects.
Test a sign-up email flow
Playwright signs up, your app sends real mail, the test reads the code.