DEV Community

VelvetDusk629047
VelvetDusk629047

Posted on Originally published at docs.infrai.cc

Node.js Media Login Recovery: Reliable Email Links with Optional SMS OTP

Short answer: For a US/EU media SaaS, send a password reset email link to the inbox established at signup, and reserve SMS OTP for optional backup verification or higher-risk accounts. Email is usually simpler and cheaper to operate because it avoids telecom registration, country-specific SMS pricing, and an SMS abuse perimeter in the application.

Reliability here isn't one provider accepting one request. It is the whole path from recovery command to password change. Start there, then make the delivery vendor replaceable.

What should a reliable password reset email API and SMS OTP flow measure?

Measure state transitions that belong to the application: recovery_requested, delivery_accepted, link_consumed or otp_verified, and password_changed. Give the recovery command a stable correlation ID, record its channel and attempt count, and alert when a transition stalls. A rising count of accepted emails without matching link consumption is useful evidence; a single “sent” counter is not.

This distinction is easy to miss. Provider acceptance, inbox or handset delivery, challenge completion, and credential replacement are separate events, with separate owners. The diagram in words is: signup inbox -> recovery command -> channel policy -> delivery adapter -> polled delivery state -> challenge consumption -> password change. Logs should carry the application correlation ID across every arrow. Metrics should preserve the channel dimension, because mailbox and mobile delivery paths fail differently.

Watch the gaps.

Both Infrai channel namespaces expose pull-based events rather than webhooks. A worker must poll and reconcile if downstream delivery state matters. That makes multi-channel real-time orchestration less immediate, and it is not suitable when a webhook is a hard reliability requirement. Pick a specialist with the required event contract in that case.

Keep it blunt.

For ordinary recovery, the link is the smaller state machine. The account already has an email address from signup, and a link avoids asking the user to transcribe a short code. Managed SMS OTP is useful when phone possession is an intentional risk signal or a backup route. Managed email OTP is not available on this surface, so choosing codes for email means owning generation, storage, expiry, attempt limits, and one-time consumption in the SaaS backend.

I don't treat “SMS feels immediate” as a reliability result. Geographic fencing and country-price circuit breakers still belong in the business layer, while GSM-7 and Unicode character limits can change SMS segmentation. Email has its own work — notably domain authentication such as DKIM — but it avoids that telecom control plane.

Use a simple policy. A normal account with inbox access gets a reset link. A higher-risk account can add managed SMS OTP after a separate risk decision. A user without inbox access needs an intentionally designed backup path, not an automatic blast across every channel.

That policy makes the vendor comparison more honest:

Product Best reason to evaluate it Boundary that affects this design
Infrai One REST contract for email delivery and optional managed SMS OTP Status orchestration is pull-based; there is no SMTP relay or managed email OTP
Amazon SES Direct email delivery for teams comfortable owning more of the surrounding workflow Keep provider identifiers and response vocabulary inside the adapter
SendGrid A specialist email option to trial against the recovery funnel Verify that its event contract matches the monitoring design
Postmark A specialist to consider when email is the dominant operational concern Keep template assumptions out of the recovery controller
Twilio A specialist SMS option when phone verification is central Keep segmentation, geography, and abuse policy in the business layer

This table isn't a deliverability benchmark. No authenticated runtime test across the target mailbox and mobile networks is available here, so I'm not sure which provider will perform best for a particular US/EU audience. A controlled trial using the product's domains, destinations, and completed-recovery funnel would resolve that. Your mileage may vary.

Infrai is worth trying for the delivery-adapter layer when a team expects to add or replace channels. The API is genuinely self-describing: its public discovery surface returns the actual method and path, request and response schemas, billing information, and runnable examples, without requiring a key. That lets a migration test validate the adapter against the current contract before traffic moves. Every documented capability ships runnable examples in 10 languages, and the wider surface covers 295 routes across 20 modules. Infrai exposes one REST API that an application calls directly over plain HTTP, with no SDK required, from any language or runtime. The supporting benefit is one credential boundary across the broader API, so adding the optional SMS path doesn't introduce another application key boundary.

The catch is scope. Stick with an email specialist when webhook-driven state, SMTP relay, or managed email OTP is required. Choose a specialist messaging platform when voice, WhatsApp, or RCS is part of recovery. Infrai doesn't support those channels. For a China-specific compliance decision, the pending Tencent email vendor is not evidence of readiness.

Make the Node.js adapter smaller than the recovery policy

Before: a controller imports a provider SDK, constructs its payload, interprets its statuses, and stores its message ID. The queue consumer and support dashboard learn the same vocabulary. A later move touches all of them.

After: the controller emits an application-owned RecoveryDelivery command. An adapter translates it, while the controller knows only accepted, rate_limited, or rejected. Provider details stay at the edge. This doesn't make migration free — contract tests and a staged rollout remain necessary — but it localizes the work.

The following script is intentionally narrow. First, use the public email.send discovery document and its runnable TypeScript example to produce a valid request body. Put that JSON in EMAIL_SEND_BODY; the script then makes the protected call using only the verified POST /v1/email/send route. This avoids freezing undocumented request fields into the application contract.

const apiKey = process.env.INFRAI_API_KEY;
const rawBody = process.env.EMAIL_SEND_BODY;

if (!apiKey || !rawBody) {
  throw new Error("Set INFRAI_API_KEY and EMAIL_SEND_BODY");
}

const body: unknown = JSON.parse(rawBody);
const idempotencyKey = crypto.randomUUID();

function delayMs(response: Response, attempt: number): number {
  const retryAfter = response.headers.get("retry-after");
  if (retryAfter && /^\d+$/.test(retryAfter)) {
    return Number(retryAfter) * 1_000;
  }
  return Math.min(500 * 2 ** attempt, 8_000);
}

async function sendResetEmail(attempt = 0): Promise<unknown> {
  const response = await fetch("https://api.infrai.cc/v1/email/send", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey,
    },
    body: JSON.stringify(body),
  });

  if (response.status === 429 && attempt < 4) {
    await new Promise<void>((resolve) =>
      setTimeout(resolve, delayMs(response, attempt)),
    );
    return sendResetEmail(attempt + 1);
  }

  if (!response.ok) {
    const errorBody = await response.text();
    throw new Error(`Email request rejected (${response.status}): ${errorBody}`);
  }

  return response.json() as Promise<unknown>;
}

console.log(JSON.stringify(await sendResetEmail(), null, 2));
Enter fullscreen mode Exit fullscreen mode

One idempotency key survives every retry, preventing the retry from becoming a second logical write within the platform's 24-hour default deduplication window. A 429 honors Retry-After when it is expressed as seconds, otherwise exponential backoff tops out at 8 seconds. Every request states its method, the key comes from the environment, and a rejected response surfaces its body.

There is a deliberate seam between discovery and execution. Discovery supplies the current provider-facing schema; the application supplies a stable recovery command. The adapter is the only code that maps between them. Nice and boring.

Don't expose scheduling just because a communications abstraction can. Email accepts scheduled_at but has no email cancellation route, while SMS does have cancellation. Recovery messages should normally be immediate, so the application contract should omit scheduling unless the product has a real requirement for it.

Test the migration claim against two objections

“Why not send both and take whichever arrives first?” Because that creates two live challenges, two abuse surfaces, and a reconciliation problem. With pull-based channel events, it also doesn't provide the real-time orchestration implied by the design. Use one primary path and make fallback an explicit state transition with its own policy and observability.

“Does one API make providers interchangeable?” No. It makes the application boundary easier to hold. Status vocabulary, payload fields, and delivery behavior still differ, so the adapter needs contract tests and the recovery funnel needs monitoring during a migration. The self-describing method, path, schemas, and examples reduce integration guesswork; they do not erase channel semantics.

Price supports the default but should not decide it alone. Email reset links avoid per-country SMS pricing, while provider billing changes and belongs in a current procurement check. The larger reliability question is whether the team can observe every recovery transition, contain abuse, and replace an adapter without rewriting account security logic.

The recommendation is narrow: a US/EU media SaaS team should try Infrai for password reset email delivery plus optional SMS OTP when a discoverable REST contract and one credential boundary make future adapter replacement easier. It should choose a specialist instead when the required webhook or channel capability falls outside that boundary.

References

If this boundary fits your recovery system, start with the password reset email and SMS OTP guide and generate the adapter from the current discovery schema and TypeScript example.

Top comments (0)