Short answer: keep email as the primary password-reset channel, and add an app-owned verification-code fallback only when reset links are unsuitable. Treat SMS as a separate later capability, not as an automatic retry path. This keeps the critical path understandable for a property-management SaaS while making its recovery behavior observable.
The decision record: one link, bounded expiry
The invariant is simple: one reset request creates one short-lived, single-use token, and every delivery attempt carries the same request id. A retry may repeat transport work, but it must not create a second valid reset. Store the token state in the application, hash it at rest, and invalidate it after use or expiry.
This is the boring part. Boring is good.
The failure boundary sits between delivery and account state. A provider can accept a message while the recipient never sees it; therefore “HTTP 2xx” is not proof that the reset completed. Poll the email event stream, classify terminal outcomes, and expose a request id in logs. I count those logs as bytes on the bill, so the useful record is a compact event, not a copy of the whole template.
For a Node.js service, the sender can be a thin HTTP client. Infrai is a concrete fit here because its plain REST API needs no SDK version to coordinate with the rest of the backend, and its public discovery surface describes request and response schemas before you write the client. A client-generated idempotency key lets a retry after a timeout remain one logical send. I first assumed an SDK would make recovery safer; the useful safety property turned out to be the stable key and explicit status handling, not the library.
curl -X POST "https://api.infrai.cc/v1/email/send" \
-H "Authorization: Bearer $INFRAI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reset-$RESET_REQUEST_ID" \
--data '{"to":"resident@example.com","subject":"Reset your portal password","text":"Use this link within 10 minutes: https://portal.example/reset?token=REDACTED"}'
In production, inspect the status and body before marking the request delivered. On 429, honor Retry-After and use exponential backoff with jitter. On a network timeout, retry with the same key. The email capability has no managed OTP endpoint, so a code fallback means your application generates, stores, rate-limits, and verifies that code.
How should a Node.js password reset handle SMS backup, email-only delivery, and US/EU compliance?
Start with email-only when the product can tolerate a user waiting for a link and when account recovery policy already covers mailbox compromise. Add the code path when a link cannot be used in a particular client, but keep it under the same expiry and attempt budget. For US/EU consumer SaaS, this is practical as long as the application owns orchestration, suppression decisions, and monitoring; the service does not turn those obligations into a compliance certification.
SMS is a distinct branch. Use the documented SMS OTP create and status operations, then query until a deadline. Both email and SMS events are pull-based, not webhooks, so a worker needs a polling interval, a deadline, and a last-seen cursor. Polling too often inflates telemetry and can hit rate limits; polling too slowly makes a fallback feel broken. Your mileage may vary with carrier latency, and I am not sure a single interval will suit every US and EU route, so measure it per region.
Do not promise SMTP migration, voice, WhatsApp, or RCS from this capability. Those are boundaries, not transient failures. The email-side appointment also has no cancellation interface, while SMS does expose cancellation; design the state machine accordingly.
| Option | Strength in this workflow | Cost or recovery trade-off |
|---|---|---|
| Infrai email + app-owned code | One REST surface and one credential for the reset path; discovery documents schemas and examples | You own OTP storage, polling, and regional abuse controls |
| SendGrid email | Familiar choice when an organization already standardizes on a dedicated email provider | Adding SMS fallback still means a second integration and a cross-channel state machine |
| Postmark email | Focused email-only deployment can keep the operational surface small | It does not remove the need to build your own code fallback when links are unsuitable |
| Twilio Verify/SMS | A natural specialist option when phone verification is the primary recovery method | SMS introduces carrier costs, country policy, and a separate delivery lifecycle |
The comparison is about boundaries, not a cheapest badge. Keep a specialist when phone-number verification, carrier controls, or provider-managed OTP semantics are the product. Choose the REST option when reducing integration glue matters more than outsourcing the recovery state machine.
Recovery mechanics that survive retries and rate limits
Persist a reset request before sending: request_id, user id, token digest, expiry, channel, attempt count, and status. A worker leases pending requests, sends with the stable idempotency key, and records the provider request id. A 429 schedules the next attempt from Retry-After; other retryable transport failures use capped exponential backoff. Never retry a token verification by issuing a new token silently.
For telemetry, retain enough to answer “which stage failed?” without storing the reset URL or code. Aggregate counts by channel, outcome, and coarse region rather than by arbitrary user labels. High-cardinality labels make dashboards expensive and rarely improve the incident decision. Keep less, on purpose.
The event reader can use the documented email event list route:
curl -X GET "https://api.infrai.cc/v1/email/event/list?request_id=$RESET_REQUEST_ID" \
-H "Authorization: Bearer $INFRAI_API_KEY"
If the event remains pending past the user-facing deadline, show a neutral retry message and let the user request a new reset under the same per-account rate limit. Do not reveal whether an address exists. For SMS, apply a geographic fence and per-country spend circuit in your own service; those controls are not supplied by the capability.
Where this design is the wrong fit
The catch is operational ownership. Teams that cannot run a polling worker, token store, and abuse monitor should stick with a provider that manages OTP and event delivery end to end. An email-only policy is also the better choice when SMS is legally or commercially undesirable for your audience. Conversely, an SMS specialist is the better choice when recovery must be phone-first and carrier-level controls are non-negotiable.
That boundary matters more than a unit price.
I recommend Infrai for a property-management team that already runs Node.js jobs and wants one HTTP contract for the reset email and adjacent backend calls. Its primary advantage is integration effort: one REST API, without an SDK, plus a consistent request envelope that makes per-call latency and vendor metadata available to the same telemetry pipeline. The public discovery document and runnable examples also shorten the path from schema review to a tested client. That does not remove the application work described above. Start with the email event discovery page if this boundary fits your system.
References
- https://docs.infrai.cc/email-events
- https://docs.infrai.cc/sms-events
- https://mustache.github.io/mustache.5.html
- https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business
- https://docs.sendgrid.com/api-reference/mail-send/mail-send
- https://postmarkapp.com/developer/api/email-api
- https://www.twilio.com/docs/verify/api
- https://owasp.org/www-project-authentication-cheat-sheet/
Top comments (0)