Answer first: A verification code email is a race against a 5–10 minute timer. Send it immediately through the HTTP email API on a sending-active domain, from a dedicated address such as codes@ or security@, as a short plain-text plus HTML pair with the code on its own line. Do not park OTP behind receipt queues or wrap it in a newsletter layout.
The user is watching an expiry countdown. The code is sitting on the same Redis queue as ten thousand receipt jobs. By the time the worker reaches it, the token is dead and support hears “2FA code email not arriving.”
Why the code arrives after it expired
Three delays dominate OTP email deliverability:
- Application queue: Laravel mailables that implement ShouldQueue are fine for receipts. They are a latency bug for one-time passwords if they share a worker with bulk transactional mail. Deeper queue design belongs in the forthcoming ShouldQueue guide; the rule here is simple — OTP should not wait behind a batch.
- Shared-host php mail(): Local MTA pickup adds minutes and skips your authenticated sending identity.
- Promotional wrapping: Tracking pixels, hero images, and list-unsubscribe chrome make a six-digit code look like bulk. Filters delay or junk it. Gmail’s mobile “Copy code” shortcut (reported 19 Sep 2026) also needs a standalone numeric token near the top — not a code buried in a branded footer.
Android Credential Manager “Verified Email” can replace some OTP flows. This article stays on email one-time passwords.
Should OTP share the receipts@ From address?
No. Keep a stable, dedicated From such as [email protected] or [email protected]. Mixing codes with receipts and marketing on one From trains filters to treat the stream as mixed intent. The from domain must be registered in Arawa Mail, allowed by the API key, and sending-active. Enable Sending requires receiving to be active first. MCP and mailbox UI are irrelevant here; OTP is an API send.
Password-reset links have a longer life and a different template. Treat them separately in password reset email deliverability.
Template: keep it under 80 words, code on its own line
Subject: Your Acme login code
Your Acme verification code:
482917
It expires in 10 minutes. If you did not request this, ignore the email.
Pair the number with the product name and one purpose sentence so the message does not look like a bare bulk digit dump. Send text and a short html twin through POST /emails. Libraries: Next.js transactional send and the Laravel quickstart.
How many times should you resend?
Once, with a new code. Five retries of the same expired token multiply complaints without helping the user. If the user mashes “resend,” treat that as an idempotency problem — covered separately in the forthcoming transactional-email idempotency article — not as a reason to spray the same digits.
How to confirm the API accepted the send
The send endpoint returns an id. Look that id up with Retrieve Email. last_event in the docs example is sent — accepted for outbound handoff, not inbox placement. Support should use that id instead of asking engineering whether “the mail went out.” See support should see the transactional email log.
Do not treat Outgoing Webhooks as OTP delivery callbacks. Those webhooks fire for inbound mailbox messages. There is no documented inbox-placement or bounce webhook for API sends. Arawa Mail does not publish a delivery SLA; design the UX around a short expiry and one clean retry.
If you forward a copy of inbound mail for debugging, forwarded destinations count toward the monthly transactional allowance (Free 1, Pro 5, Business 10). That is a mailbox-forwarding limit, not an OTP feature.
How we analyzed this
Constraints come from Enable Sending, Send Email, Retrieve Email, Outgoing Webhooks, and the Laravel / Next.js quickstarts. We did not invent bounce webhooks, SMS fallback inside Arawa Mail, or a first-party WordPress plugin. Queue worker internals are deferred so this piece stays on latency and template shape.
Checklist before the next signup spike
- Domain is sending-active; From is codes@ or security@.
- OTP send path is synchronous or on a dedicated high-priority queue — not behind receipts.
- Code is a standalone token near the top; no newsletter chrome.
- One retry issues a new code; store the send
idfor support.
FAQ
Why is my verification email delayed?
Usually a shared queue, a local MTA, or a promotional template. Move OTP to the HTTP API on a sending-active domain and keep the body short.
Is a queue worker safe for OTP?
Only if OTP is not stuck behind bulk jobs. Prefer an immediate API call for codes.
How do I know the code was sent?
Save the Send Email id and retrieve it. sent means the API accepted the message, not that Gmail placed it in the inbox.
Can I wrap the code in my marketing template?
Do not. You lose parseability for Copy-code shortcuts and you look like bulk to filters.