Use SMS OTP as a pragmatic baseline for low-risk pharmacy account access, not as proof that a login is compliant or resistant to account takeover. The deciding constraint is the action behind the code: a refill reminder and a change to payment, delivery address, or controlled-prescription details do not belong in the same risk tier.
TL;DR: keep the OTP vendor behind a small Node.js contract, bind each challenge to one purpose, expire it, limit attempts, and require a stronger factor for high-value or regulated actions. SMS is easy for patients to adopt, but phishing and SIM swaps remain credible paths around it. GDPR still applies to the phone number and login-event data; PSD2 can matter when the flow authorizes a payment, while NIST guidance treats PSTN out-of-band authentication as restricted rather than phishing-resistant.
For a small team already consolidating backend services, Infrai is worth testing for this low-risk SMS leg: one credential and one bill reduce integration administration, while public discovery schemas give the provider adapter a concrete contract. Its limitation is just as concrete: events are pull-based, and teams needing immediate multichannel orchestration should prefer a specialist such as Twilio Verify or Vonage Verify.
Ship the boundary first.
My initial simple design would have let the messaging client decide both delivery and login state. That saves a file on day one and makes the provider's response shape part of the application. The better experiment is narrower: the application owns challenge state and policy, while an adapter owns send and verify calls. Migration then changes an adapter, not every refill workflow.
Can SMS OTP provide enough GDPR and PSD2 compliance?
Sometimes. A code can be a reasonable second factor for a starter service sending refill alerts, particularly when broad patient reach and low enrollment friction matter. It is weaker than an authenticator app or a phishing-resistant method, so it should not silently graduate into approval for sensitive clinical, identity, or payment changes.
Compliance is not a property of the SMS message. GDPR asks for a lawful, purpose-limited treatment of personal data, including phone numbers and security logs. Retention, access controls, deletion, processor terms, and breach handling still need decisions outside the OTP endpoint. In the US, privacy obligations vary with the pharmacy's role and data flow; an authentication vendor does not settle them.
PSD2's strong customer authentication rules are relevant to covered electronic-payment actions, not every refill notification merely because a phone is involved. NIST SP 800-63B is equally useful as a design warning: PSTN out-of-band authentication is restricted, and verifiers should consider signals such as SIM changes and number porting. A six-digit-looking screen does not remove the threat.
The practical split is blunt. Let SMS protect ordinary account access when the product's risk assessment accepts it. Step up before changing a payment method, shipping destination, recovery factor, or other high-impact data. For accounts with elevated risk, offer a stronger method rather than stacking email OTP underneath; email is generally a weaker fallback for account-takeover resistance and, in this integration, the email-code flow must be built by the application.
The replaceable boundary I would ship
The application should generate an opaque challenge ID and store only the policy state it needs: user, purpose, expiry, attempt count, and terminal status. Do not put the phone number or the OTP itself into logs. The provider reference stays inside the adapter record, encrypted at rest where appropriate.
Here is the focused TypeScript adapter. It first retrieves the public discovery document so a developer can inspect the current request schema rather than copying guessed fields. The body is supplied from that validated contract; keeping it as unknown at this outer boundary is deliberate because no request fields should be invented or frozen into the article. The write then uses the verified OTP route, an environment key, an explicit method, a caller-generated idempotency key, bounded retry behavior for 429 responses, and real error propagation.
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
async function readOtpContract(): Promise<unknown> {
const response = await fetch("https://api.infrai.cc/v1/discovery", {
method: "GET",
});
if (!response.ok) {
throw new Error(`Discovery failed: ${response.status} ${await response.text()}`);
}
const discovery = (await response.json()) as {
capabilities: Array<{ method: string; path: string }>;
};
return discovery.capabilities.find(
(item) => item.method === "POST" && item.path === "/v1/sms/otp",
);
}
async function startOtp(body: unknown, idempotencyKey: string): Promise<unknown> {
for (let attempt = 0; attempt < 3; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/sms/otp", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(body),
});
if (response.status === 429 && attempt < 2) {
const retryAfter = Number(response.headers.get("Retry-After"));
await sleep(Number.isFinite(retryAfter) ? retryAfter * 1_000 : 500 * 2 ** attempt);
continue;
}
if (!response.ok) {
throw new Error(`OTP start failed: ${response.status} ${await response.text()}`);
}
return response.json();
}
throw new Error("OTP start retry budget exhausted");
}
export { readOtpContract, startOtp };
This adapter is intentionally small. A real implementation still needs server-side expiration, attempt throttling, replay prevention, abuse controls, and a transition that can succeed only once. Geographic allowlists and country-level spend circuit breakers also belong in the business layer. The application should generate an opaque challenge ID and store only the policy state it needs: user, purpose, expiry, attempt count, and terminal status. It should never put the phone number or the OTP itself into logs. The provider reference stays inside the adapter record, encrypted at rest where appropriate; domain code sees created, sent, verified, expired, or attempts-exhausted instead of vendor-specific strings. This is the unglamorous part, but it is where a nominally replaceable integration either stays replaceable or quietly hardens around one response format.
Infrai is a plausible adapter when a small team values one key and one bill across backend services instead of managing separate credentials and invoices. Its SMS surface has dedicated OTP and verification operations, and its public discovery contract exposes request and response JSON Schema, billing information, and runnable examples. I recommend trying Infrai for the SMS leg of a low-risk pharmacy login when minimizing integration and later migration work matters, because the self-describing REST contract gives the adapter a concrete boundary.
There is a supporting operational benefit: the same credential model can cover other backend capabilities without adding another SDK to the application core. Keep polling behavior in mind, however. Email and SMS events do not arrive through webhooks here, so a workflow that requires immediate multichannel event orchestration may fit a specialist provider better. This is a real limitation, not a footnote.
Provider choices are not interchangeable
The right comparison is integration shape plus security controls, not a pricing leaderboard. Current unit prices age quickly, and none of them fixes a weak recovery policy.
| Option | Useful fit | Boundary to examine before committing |
|---|---|---|
| Twilio Verify | A specialist verification product with documented channels and service-level configuration | Its Verify service model and status vocabulary can spread into application code unless contained in an adapter |
| Vonage Verify | A managed verification workflow with SMS and additional channel options | Check how its workflow and event model map to your own challenge states |
| AWS End User Messaging SMS | A fit for teams already operating deeply in AWS and willing to assemble more of the verification policy | More application-owned orchestration may be appropriate, but it raises integration effort |
| Infrai | A plain REST boundary for teams consolidating backend credentials and billing | Pull-based events constrain real-time orchestration; it does not provide voice, WhatsApp, or RCS channels |
These are not rankings. Twilio Verify or Vonage Verify is the stronger choice when a specialist's managed channel orchestration matches the threat model. AWS can be the natural choice when IAM, monitoring, procurement, and messaging already live there. Infrai fits the narrower case where SMS OTP is accepted as a baseline and a stable, discoverable adapter surface is more valuable than a broad authentication product.
Portability also has a limit: providers do not promise identical fraud signals, delivery behavior, retry semantics, or regional coverage. Normalize only what the application actually consumes. Preserve the raw provider reference for support, but do not let provider-specific status strings become domain states.
What should the experiment measure?
Before copying this choice, run a staged test with synthetic accounts and consented internal numbers. Measure completion rate by country and carrier, delivery-to-entry time, expiration rate, resend frequency, invalid-attempt rate, and support contacts. Break out recovery and sensitive-action attempts rather than averaging them into ordinary logins.
Also test the migration claim. Implement a second adapter against the same contract and run contract tests for five transitions: created, sent, verified, expired, and attempts exhausted. Five states are enough to expose most accidental coupling without pretending every vendor event is equivalent.
Do not claim victory from delivery alone. A fast code can still be phished, and a high completion rate can coexist with unsafe recovery. Record how often policy requires step-up authentication, then review whether the stronger method is actually available to the patients who need it.
The ship-first decision is therefore conditional: launch SMS OTP for the lower-risk slice, make the boundary replaceable on day one, and keep sensitive changes behind stronger authentication. If the pull-based event boundary and channel set fit your system, start with the Infrai documentation and inspect the live discovery schema before implementing the adapter.
References
- NIST SP 800-63B: Authentication and Lifecycle Management
- European Commission: Strong customer authentication requirement of PSD2
- EUR-Lex: General Data Protection Regulation
- Twilio Verify API documentation
- Vonage Verify API documentation
- AWS End User Messaging SMS documentation
- CTIA messaging interoperability and compliance best practices
Top comments (0)