How to monitor whether your OTP codes actually get delivered

Uptime monitors tell you your API returned 200. They don't tell you the verification SMS or email reached the user. Here's how to close that gap.

If your login uses a one-time code sent by SMS or email, there is a failure mode that almost nothing in a normal monitoring stack catches: the code is generated, your auth provider returns 200, your status page stays green — and the message never reaches the user. A carrier starts filtering your sender, a bounce gets your domain blocked, an SMS route silently degrades, a template trips a spam rule. Nobody can log in, and the first signal you get is a support ticket hours later.

Why the usual monitoring misses it

What it checksWhat it can't tell you
HTTP uptime check (Pingdom, UptimeRobot, Better Stack)Only that your endpoint answered. A 200 from "send code" says nothing about delivery.
Your SMS/email provider's own dashboardTheir send stats. "Sent" or even "delivered to carrier" is not "landed in the user's inbox / phone". And it can't see a problem that starts one hop downstream.
Browser synthetic checks (Checkly, Playwright in CI)They can drive your login form, but they can't actually receive a real SMS or read a real external inbox to confirm the code arrived.
Status pagesAggregate, lagging, and usually only updated once humans already know something broke.

What actually works: receive the message, like a user would

The only reliable check is an end-to-end one: hold a real target, trigger your real verification send to it on a schedule, and confirm the message shows up within a few seconds.

  1. Get a real target — a real phone number or a real email address you control.
  2. Trigger your own send — call your production "send verification code" path against that target, from a cron job or CI.
  3. Wait for arrival — poll the target for a new message, with a timeout (e.g. 60s).
  4. Alert on failure — if it didn't arrive, page someone. A failed scheduled job is the alert.

Doing it yourself

You can build this with a rented number or a disposable inbox plus a cron job. The shape:

# pseudo-cron, every 15 min
target=$(rent_a_number_or_inbox)
trigger_your_app_verification_send "$target"
sleep 60
got_code=$(poll_target_for_new_message "$target")
[ -z "$got_code" ] && alert "OTP delivery check FAILED for $target"

The fiddly parts are renting a real number programmatically, matching the inbound message to the right check, and keeping the whole thing cheap if you run it often.

Doing it with otp-watch

otp-watch is that check as a small API. Email checks are free; SMS monitors are €29/month for a dedicated number with unlimited checks.

# one-off email check
curl -X POST https://otpwatch.flo-voice1.com/checks \
  -H 'authorization: Bearer ow_...' \
  -H 'content-type: application/json' -d '{"channel":"email"}'
# => {"id":"chk_...","target":"a1b2c3@receivemail.dev","status":"pending"}

# trigger your app's verification email to that address, then:
curl https://otpwatch.flo-voice1.com/checks/chk_... -H 'authorization: Bearer ow_...'
# => {"status":"received","latencyMs":4200}   (or "timed_out")

Point your CI or cron at it, fail the job on timed_out, and your existing alerting does the rest. See the endpoints and pricing on the front page. If you're testing an OTP integration in CI rather than monitoring it in production, see testing your OTP integration without burning SMS credits.

FAQ

How often should I check?

Every 15–30 minutes is enough to catch a delivery outage well before your users flood support, without generating meaningful cost or load.

Won't this send me real verification codes constantly?

Yes — to a throwaway target you own, not to a real user. The codes are discarded; only "did it arrive, and how fast" matters.

What about measuring delivery in specific countries?

otp-watch uses UK numbers today. Multi-country delivery testing (across carriers and regions) is a different, heavier category — enterprise telecom testing services do that. otp-watch is the lightweight "is my flow working at all, right now" check.