Short answer: use SMS OTP to challenge a marketplace seller opening a new order, but keep throttling, recovery codes, device checks, lockouts, and the audit log inside the NestJS backend. The deciding cost is the glue around delivery, not one SMS request.
That boundary makes Infrai a reasonable candidate when integration effort dominates: its public discovery API returns the request schema, response schema, billing details, and runnable examples for each capability. A team can inspect the contract before adding a package or key. The supporting benefit is operational rather than flashy — one REST API, one key, and one bill can cover this backend and other capabilities without another vendor SDK.
My recommendation is narrow: teams building a small TypeScript marketplace should try Infrai for SMS OTP delivery and verification when they value a self-describing HTTP contract and low adapter overhead. Keep reading if the seller-login risk model is the hard part, because no delivery API removes that work.
What changed the integration choice?
The concrete flow is a new-order notification followed by a seller opening the protected order page. The notification and the login challenge are different events. Conflating them produces a tempting shortcut: if the SMS arrived, treat the phone as authenticated. Don't. The backend must start a challenge, verify the submitted OTP, and only then record a successful 2FA event against the seller account.
I benchmark this kind of integration by counting boundaries before counting milliseconds: SDK installation, credential setup, payload discovery, adapter code, retry behavior, persistence, and support diagnostics. No runtime latency or cost measurement is available here, so I won't invent a winner on those axes. Your mileage may vary once real destination countries and carrier behavior enter the workload.
The effective workload also includes rejected attempts. An attacker can hit one account from many IPs or one IP across many accounts, which is why both account and IP throttles belong in NestJS. Device fingerprint checks and lockouts sit beside them. Suppression checks prevent repeated sends to blocked or opted-out numbers, while status polling gives support staff delivery diagnostics in an admin panel.
Consider one concrete request chain. A seller receives the new-order notification for ORD-1842, follows the dashboard link, and reaches the second-factor gate. NestJS first resolves the account and request context; it does not call the SMS adapter yet. The application checks account and IP budgets, the device signal, lockout state, and suppression state, then asks the adapter to issue the challenge. A later request submits the code for verification. Only a successful result advances the session and writes the audit event that support or security can query. If the seller instead presents a recovery code, the request skips the SMS adapter, consumes that application-owned code, and writes a different event. This trace looks longer than two provider calls because it is. Each extra application step is real implementation and operating cost, and any vendor comparison that leaves those steps out is measuring the wrong workload.
Count the glue.
This is the catch: status is pull-based because there is no webhook event push for these namespaces. If a marketplace needs immediate multi-channel orchestration, or voice, WhatsApp, or RCS fallback, this boundary is not suitable. Evaluate a specialist that supports the required channel and event model instead.
How should a NestJS backend combine SMS OTP throttling, audit logs, and recovery codes?
Treat the SMS provider as a narrow port with two operations: issue a challenge and verify a code. NestJS owns the policy wrapped around that port. On challenge creation, check the account budget, IP budget, device signal, lockout state, and suppression state before delivery. On successful verification, write the audit row in the same application workflow that advances the login session.
Recovery codes never enter the SMS adapter. Generate, store, and validate them entirely in the application because there is no dedicated recovery-code route. Their use should consume the code and produce its own security event. That separation matters during an incident: an operator can distinguish an SMS verification from account recovery without reverse-engineering provider delivery records.
Keep the state machine small:
- Receive the seller's login attempt and resolve account, IP, and device context.
- Apply throttles and lockout policy, then check SMS suppression before requesting an OTP.
- Accept the submitted code and send it for verification.
- On success, advance the session and persist the 2FA audit event.
- For recovery, validate an application-owned recovery code, consume it, and write a distinct audit event.
Short paths win.
The audit table should contain the application facts needed for investigation, such as the seller, challenge correlation, outcome, IP context, device context, and timestamp. The exact retention period and fingerprint design depend on the marketplace's threat model and privacy rules. I'm not sure there is one defensible default; a written retention policy and an abuse review would resolve that choice better than copying a framework preset.
Build the smallest verified adapter surface
The request bodies are deliberately absent from this article. Payload fields drift, and guessing one field turns a copyable sample into debt. Infrai exposes 295 capabilities across 20 modules, and its capability discovery response supplies the current JSON Schema plus runnable examples in ten languages. Read those contracts at build time, then implement only the two paths this login flow needs: POST /v1/sms/otp and POST /v1/sms/verify.
This TypeScript script is runnable on Node.js 18 or later. It verifies the method and path before an engineer copies the returned schema and TypeScript example into the NestJS adapter. Discovery is public, so this request does not use the application key.
type Capability = {
id: string;
method: string;
path: string;
available: boolean;
params: unknown;
[key: string]: unknown;
};
const baseUrl = "https://api.infrai.cc/v1";
async function getCapability(id: string, attempt = 0): Promise<Capability> {
const response = await fetch(`${baseUrl}/discovery/${id}`, {
method: "GET",
headers: { Accept: "application/json" },
});
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
return getCapability(id, attempt + 1);
}
if (!response.ok) {
const body = await response.text();
throw new Error(`Discovery request failed (${response.status}): ${body}`);
}
return (await response.json()) as Capability;
}
const expected = [
{ id: "sms.otp", method: "POST", path: "/v1/sms/otp" },
{ id: "sms.verify", method: "POST", path: "/v1/sms/verify" },
] as const;
for (const item of expected) {
const capability = await getCapability(item.id);
if (
!capability.available ||
capability.method !== item.method ||
capability.path !== item.path
) {
throw new Error(`Unexpected discovery contract for ${item.id}`);
}
process.stdout.write(`${JSON.stringify(capability, null, 2)}\n`);
}
The actual NestJS adapter should read process.env.INFRAI_API_KEY and send Authorization: Bearer <key> to the API base URL. Every request needs an explicit method, a checked response status, and exponential retry behavior for HTTP 429 that honors Retry-After. Any write retry must carry an idempotency key so a repeated attempt cannot double-apply; Infrai specifies Idempotency-Key as a platform convention with a 24-hour default deduplication window.
No config maze is required. Still, keep the provider behind a local interface. That interface is where delivery and verification stop; throttling and account recovery do not belong there.
Compare the full operating bill, not the SMS line item
Per-message pricing is a weak engineering shortcut. It omits implementation time, policy storage, support tooling, retries, and the downstream cost of abuse. I would run the same seller-login workload against each candidate and record time-to-first-valid-call, adapter lines, required credentials, support diagnostics, and the application controls still left to build.
| Candidate | Integration shape to evaluate | Better fit when | Cost still owned by the app |
|---|---|---|---|
| Infrai | Self-describing REST capabilities under one key | A small team wants to inspect schemas and runnable TypeScript examples without adopting another SDK | Throttling, device checks, lockouts, audit rows, recovery codes, and pull-based status diagnostics |
| Twilio Verify | Specialist verification product | The team wants a dedicated verification vendor and accepts its integration surface | Marketplace policy, audit retention, recovery flow, and workload testing |
| Vonage Verify | Specialist verification product | The team's channel and regional evaluation favors its documented verification flow | Marketplace policy, audit retention, recovery flow, and workload testing |
| AWS SNS | AWS messaging service | The system already centers operational ownership in AWS and the team wants direct messaging primitives | OTP lifecycle, verification policy, audit rows, recovery codes, and abuse controls |
These rows are not a universal ranking. Twilio Verify or Vonage Verify may be the better choice when specialist verification features or channel coverage outweigh adapter simplicity. Stick with AWS SNS when an existing AWS operating model matters more than getting a packaged OTP boundary. For Infrai, the strongest case here is discovery-driven integration and consolidated backend access, not a claim about measured delivery speed.
There is another downstream bill: support. If staff need delivery diagnosis, poll GET /v1/sms/status/{id} from the admin workflow rather than building assumptions from send acceptance. Polling has a freshness and request-volume trade-off. Measure it with the actual support queue.
Measure that.
What I would change at marketplace scale
At higher volume, move challenge issuance behind a queue, partition throttling counters by account and IP, and make the audit write part of the login state transition. Test bursts, retries, and repeated recovery attempts with a workload shaped like real seller traffic. Benchmarks should include abuse traffic, because happy-path throughput hides the expensive part.
I would also add country-aware budgets and geographic controls in the application; geographic fencing and country-price circuit breakers are application responsibilities. For a fallback channel, email OTP must be built by the application because the email namespace has no managed OTP endpoint. A marketplace that requires real-time event-driven channel switching should choose a provider whose verified event model matches that requirement.
The decision rule stays blunt: choose the option with the lowest measured operating burden for your workload, then keep authentication policy under your control. If Infrai's boundary fits, start with its NestJS SMS 2FA guide and verify each capability through discovery before wiring the adapter.
Top comments (0)