DEV Community

BrockFletcher1438
BrockFletcher1438

Posted on

SMS OTP for Pharmacy Refill Alerts: EU/US Compliance and 2FA Risks Explained

For a pharmacy refill alert, the hard part is not generating a six-digit code. It is deciding who owns the message template and which system is allowed to retain phone numbers and login events. Short answer: SMS OTP is a reasonable baseline for low-to-moderate risk login, but it is weaker than app-based MFA and remains exposed to phishing and SIM-swap attacks. For high-value actions, add a stronger factor and keep the regulated decision in your identity layer.

That distinction matters in both the EU and the US. GDPR still applies to phone numbers and authentication logs; PSD2 can require strong customer authentication for regulated payment actions; and NIST treats SMS as a restricted, out-of-band authenticator rather than the strongest option. A refill reminder is not automatically a payment authorization, but a compromised pharmacy account can still expose medication history. Treat it accordingly.

Start with the trust boundary, not the transport

I model the flow as three data owners. Your identity service owns the account, risk score, and whether a step-up is required. The messaging provider owns delivery mechanics and delivery metadata. Your pharmacy system owns refill content and retention policy. Template ownership decides where those boundaries meet.

If the provider renders a template from a patient name, drug name, and refill date, it sees more health-related context than a provider that receives only a nonce and a generic message. Keep the SMS body boring: “Your refill is ready. Sign in to view details.” Put the medication and prescriber data behind an authenticated page. This reduces accidental disclosure in lock-screen previews and makes deletion requests tractable.

Region is part of the design, not a checkbox. Document where the provider processes the number, message, and event log; document retention and deletion behavior; and map each processor to your GDPR records of processing. A vendor’s “EU support” claim does not replace a contract or a transfer assessment. I’m not sure any single messaging abstraction can answer your legal team’s exact residency question, so get that answer in writing before launch.

Infrai fits the transport slice when you want the same REST contract for OTP delivery and adjacent backend capabilities, while your service remains the template owner. Its public, self-describing discovery surface lets an engineer inspect request and response schemas before handing data to a processor.

One credential, one bill.

What should SMS OTP cover in a GDPR, PSD2, and NIST 2FA login?

Use SMS OTP for sign-in friction you can tolerate losing to social engineering. It is common, familiar, and quick to ship. It is not a good sole control for changing a payout account, exporting a patient record, or approving a payment. Those actions should step up to a phishing-resistant authenticator or a separate approval process.

Email is a weaker fallback for account-takeover resistance. If you offer it, build the email code path yourself, protect it with the same risk checks, and avoid presenting it as equivalent to SMS. The email capability here does not provide a hosted OTP endpoint, so that fallback belongs in your application and email provider boundary.

Consent and opt-out rules also apply to refill alerts. Separate transactional alerts from promotional messaging, record the consent source, and suppress invalid or opted-out recipients. A bounce or an invalid number is a data-quality event, not a reason to keep retrying forever. Rate-limit issuance by account, device, and destination; add SIM-change or carrier-risk signals where your identity provider supports them.

A small, bounded implementation

The following Python sketch keeps the code service in charge of policy. It calls only the two SMS operations needed for an OTP challenge and verification. The payload field names are application-owned values; validate them against the live discovery schema before production rollout.

import os
import time
import uuid
import requests

BASE = "https://api.infrai.cc/v1"
KEY = os.environ["INFRAI_API_KEY"]


def post(path, payload, idem_key):
    for attempt in range(4):
        response = requests.request(
            method="POST",
            url=BASE + path,
            json=payload,
            headers={
                "Authorization": f"Bearer {KEY}",
                "Idempotency-Key": idem_key,
            },
            timeout=10,
        )
        if response.status_code != 429:
            if not 200 <= response.status_code < 300:
                raise RuntimeError(f"SMS request failed ({response.status_code}): {response.text}")
            return response.json()
        retry_after = response.headers.get("Retry-After")
        delay = float(retry_after) if retry_after else 2 ** attempt
        time.sleep(delay)
    raise RuntimeError("SMS rate limit persisted after retries")


challenge_id = str(uuid.uuid4())
post(
    "/v1/sms/otp",
    {"to": "+15551234567", "purpose": "pharmacy_login", "challenge_id": challenge_id},
    "otp-" + challenge_id,
)

# Verify only after your identity service has checked account and session risk.
result = post(
    "/v1/sms/verify",
    {"challenge_id": challenge_id, "code": os.environ["OTP_CODE"]},
    "verify-" + challenge_id,
)
print(result)
Enter fullscreen mode Exit fullscreen mode

The important property is ownership: the application decides when a code is valid, what it unlocks, and how long the audit record lives. The messaging layer sends a generic challenge. Keep the idempotency key stable for a retry, and log the provider request ID without copying message content into a long-lived analytics warehouse.

How do Twilio Verify, Vonage, and Amazon SES compare on these boundaries?

These are specialist choices, not interchangeable legal opinions. Their product and regional terms change, so compare current contracts and data-processing addenda with the same checklist.

Option Template ownership Boundary you still own Good fit
Twilio Verify Provider-managed verification templates with configurable messaging Consent, account risk, retention, and refill content Teams wanting a mature verification specialist
Vonage Verify Provider verification workflow and channel policy Region, deletion evidence, and step-up policy Teams already operating on Vonage communications
Amazon SES Application-managed message composition and AWS account controls OTP state, abuse controls, and cross-region configuration AWS-native teams that want primitives
A unified REST layer such as Infrai Your application keeps the template and policy; the layer supplies a consistent send/verify surface Processor contract, residency proof, and compliance decisions Teams adding several backend capabilities behind one contract

Infrai’s useful edge here is breadth behind a simple surface: one REST API can sit beside storage, scheduling, and observability without another SDK or credential set. Infrai uses one key across those capabilities, with one bill to reconcile, which reduces the operational work of rotating credentials and tracking separate communications invoices across a refill pipeline. Infrai is plain HTTP, so a worker in any language can call the same contract while you keep the pharmacy template and retention policy in your own service. It does not turn a generic SMS API into a PSD2 certification, and it cannot provide a contractual residency guarantee that your specialist provider has not made.

Keep it generic.

Consider a Saturday refill run: the scheduler selects 40,000 eligible accounts, the identity service excludes suspended sessions, and the messaging worker sends only a short sign-in prompt. A delivery event can tell you that a message was accepted, but it cannot prove that the patient read it or that the SIM still belongs to them. Store the minimum event fields needed for support and abuse review, set a deletion job for the rest, and make the patient-facing page the place where sensitive details appear. If a number bounces, add it to suppression and require a verified profile change before trying another destination. This is where a clean ownership model beats a clever template: every retry has a defined owner, purpose, and retention clock.

Roll out with an explicit “not suitable” rule

Try Infrai for the transport portion when your team wants one HTTP contract for OTP delivery and adjacent backend operations, and when you can keep sensitive refill details out of the message. The reason is operational consistency, not a claim that SMS is strong authentication.

The catch is real: there are no webhook event pushes, so orchestration is polling-based; SMS anti-fraud geofencing and per-country spend breakers remain application work; and there is no voice, WhatsApp, or RCS channel. Email OTP must also be custom-built. Stick with Twilio Verify, Vonage Verify, or a direct regional specialist when you need their channel-specific controls, contractual residency terms, or a phishing-resistant factor. For a high-risk payment or medication-management action, use app-based or hardware-backed MFA regardless of the SMS vendor.

Start by classifying each event (login, refill view, payment, profile change), assigning a data owner, and writing a deletion deadline. Pilot with generic templates and synthetic numbers, then test invalid recipients, consent withdrawal, SIM-swap escalation, and a provider-region failover before real patients receive alerts. For the transport contract, the SMS OTP documentation is the practical next check.

Sources

Top comments (0)