The usual way to test an email-verification step is: create an inbox, trigger the signup, then poll the inbox every couple of seconds and run a regex over the body to find the code. It works, but it's the flakiest part of a signup test — the regex breaks when someone edits the email template, and the poll interval is either too slow (wasted CI minutes) or too fast (rate limits).
receivemail.dev's paid plans remove both halves of that. Both use an API key,
shown right after checkout, sent as Authorization: Bearer rmk_… when
you create the mailbox.
Mailboxes created with a key return every message with an otp field —
the verification code pulled out for you (a number next to "code", "OTP",
"verify"…, or the lone 4–8 digit number in the email, ignoring things like a
copyright year). No regex in your test:
resp=$(curl -s -X POST https://receivemail.dev/mailboxes \
-H "Authorization: Bearer $RECEIVEMAIL_KEY")
addr=$(echo "$resp" | jq -r .address)
secret=$(echo "$resp" | jq -r .secret)
# ...trigger your signup with $addr...
for i in $(seq 1 15); do
otp=$(curl -s "https://receivemail.dev/mailboxes/$addr/messages" \
-H "Authorization: Bearer $secret" | jq -r '.messages[0].otp // empty')
[ -n "$otp" ] && break
sleep 2
done
echo "code: $otp"
Paid mailboxes also live up to 24 hours instead of 60 minutes, and a key gets 120 creates per minute instead of the free per-IP limit — useful when many CI jobs run in parallel behind one NAT address.
Pass a webhookUrl when creating the mailbox and every email that lands
is POSTed there within about a second, as JSON:
curl -X POST https://receivemail.dev/mailboxes \
-H "Authorization: Bearer $RECEIVEMAIL_KEY" \
-H 'content-type: application/json' \
-d '{"ttlMinutes": 1440, "webhookUrl": "https://test-harness.example.com/hooks/email"}'
{
"address": "7f2b0094b9fc@receivemail.dev",
"from": "noreply@your-app.example",
"subject": "Your verification code",
"bodyText": "Your verification code is 739204...",
"otp": "739204",
"receivedAt": "2026-10-16T03:42:38.226Z"
}
Each request carries X-Receivemail-Signature: the hex HMAC-SHA256 of the
raw body, keyed with your API key. Verify it before trusting the payload:
import express from 'express';
import crypto from 'node:crypto';
const app = express();
app.post('/hooks/email', express.raw({ type: 'application/json' }), (req, res) => {
const expected = crypto.createHmac('sha256', process.env.RECEIVEMAIL_KEY)
.update(req.body).digest('hex');
const given = req.get('x-receivemail-signature') ?? '';
if (given.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
return res.sendStatus(401);
}
const mail = JSON.parse(req.body);
console.log(mail.address, mail.otp); // hand the code to whatever is waiting on it
res.sendStatus(200);
});
app.listen(3000);
otp field from option 1 is
usually the simpler win.Both plans are on the pricing section; the free API stays free and unchanged.