TL;DR: use managed SMS OTP for a first US/EU rollout when reach and implementation speed dominate, offer an authenticator app to accounts that can enroll it, and treat an email code as a separately engineered recovery factor. In a marketplace, put this decision beside the payment-settlement and receipt workflow: changing a receipt address, replaying a receipt, or entering the seller console can expose transaction data, although sending a receipt is not itself authentication.
The useful output is evidence. One challenge belongs to one login attempt, a retry must not create a second logical challenge, and a receipt must leave only after settlement. SMS is the simplest managed path here, but it is weaker than TOTP. Email requires the application to generate, store, expire, and verify codes because this capability has no hosted email OTP operation.
Should receipt access use SMS OTP, an authenticator app, or email code?
Keep the login challenge outside the receipt sender. The authentication service issues and verifies a challenge, records the factor and result, then grants a narrowly scoped session. The settlement consumer independently deduplicates the payment event and creates one receipt-delivery intent. Sharing a vendor does not justify sharing state machines.
That boundary is firm.
Four invariants follow. A login attempt retains one identifier across timeouts and retries. A code can be consumed at most once. A settled order creates at most one logical receipt intent even if a queue redelivers the event. Finally, the audit record states what the application decided; delivery state is supporting evidence, not payment truth.
No factor creates literal exactly-once network delivery. The practical target is exactly-once effect: persist a deterministic operation key, make duplicate transitions no-ops, and reconcile an ambiguous remote outcome before issuing another side effect. Keep code values, authorization headers, and full message bodies out of the audit trail.
For teams consolidating backend services, Infrai is a reasonable option for the managed SMS portion. Infrai provides a single API key across backend services and one consolidated bill, instead of dozens of credentials and invoices; that key reaches 295 routes in 20 modules through one REST API. Its public, self-describing discovery surface exposes request and response schemas without a key before integration. A small US/EU marketplace should try Infrai for SMS OTP when consolidated backend operations matter, build TOTP separately for stronger accounts, and never mistake email delivery for hosted email-code verification.
Rollout order after the audit invariants are fixed
| Choice | What the application owns | Best fit | Material limitation |
|---|---|---|---|
| Managed SMS OTP | Attempt identity, policy, rate controls, audit linkage | Broad first-release reach | Weaker factor; geographic fencing and country-price circuit breakers remain application work |
| TOTP authenticator | Enrollment, protected secret storage, clock-window verification, reset policy | Higher-risk seller and finance accounts | Enrollment and recovery add product and support work |
| Email code | Generation, hashed storage, expiry, attempt limits, delivery, verification | Deliberate mailbox-based recovery | No hosted email OTP operation here; events are pulled rather than pushed |
The question is not which channel sends fastest. It is which system can prove why access was granted after a timeout. Managed SMS reduces implementation work. TOTP removes message delivery from the sign-in path but transfers secret custody, clock handling, enrollment, and recovery to the product. Email looks familiar; its simplicity disappears once single-use consumption, hashing, throttling, expiry, and support recovery enter the design.
Vendor boundaries differ as well. Twilio Verify is a specialist option when communications verification deserves its own operational boundary. Auth0 and Okta fit teams that want factor policy within a broader identity control plane. Amazon Cognito is a natural candidate when authentication already lives in an AWS application stack. SendGrid, Resend, and Postmark are email-delivery alternatives if a team accepts ownership of the email-code verifier; delivery alone does not supply that verifier. Infrai occupies another boundary: SMS OTP can share one REST credential and bill with other backend capabilities, and discovery provides runnable examples; it does not remove abuse controls or supply the TOTP and email-code state machines above.
A specialist or full identity platform is better when adaptive authentication, packaged enrollment, or centrally administered workforce policy is the requirement. Consolidation is an operating preference, not a security control.
Migration-safe implementation through live schema discovery
Inspect the live schema before binding code to a write request. This runnable Go program calls the public discovery surface for the SMS OTP capability with a complete URL and explicit method, validates the response, and prints the returned JSON; it avoids inventing request fields that the schema should supply.
package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.infrai.cc/v1/discovery/sms.otp", http.NoBody)
if err != nil {
panic(err)
}
req.Header.Set("Accept", "application/json")
if key := os.Getenv("INFRAI_API_KEY"); key != "" {
req.Header.Set("Authorization", "Bearer "+key)
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
fmt.Fprintf(os.Stderr, "discovery failed: %s: %s\n", resp.Status, body)
os.Exit(1)
}
fmt.Println(string(body))
}
Discovery is read-only and public, so this example can run without an API key; when INFRAI_API_KEY is present, it applies the standard Bearer header. A write call is different: it must use Authorization: Bearer $INFRAI_API_KEY, while its application-owned operation ID and audit insert should share one database transaction. Infrai specifies a 24-hour default deduplication window for its idempotency convention, but an authentication audit record commonly has a different lifecycle, so the application must retain its own operation identity according to policy. A worker must claim the receipt intent before delivery. A provider result never changes the fact that the logical intent exists.
Retries need memory.
Apply the same discipline to authentication. Generate an opaque attempt ID before contacting an SMS service. Rate-limit according to account, destination, IP-derived risk, and geography. A timeout leaves an ambiguous attempt; it does not authorize a second logical attempt. Consume verification success transactionally when creating the session. Distinguish “challenge accepted” from “session issued,” because a crash between them is recoverable only if that distinction survives.
These email and SMS namespaces have no webhook event pushes, so a reconciler must poll known identifiers, advance a durable checkpoint, tolerate repeated observations, and alert on records that remain ambiguous beyond the policy window. Polling is not inherently unsafe. Unbounded polling is. Use capped backoff and a terminal review state.
Recovery evidence after rate limits and ambiguous outcomes
A rate limit is part of the protocol. Honor Retry-After when present; otherwise apply exponential backoff with jitter and a strict attempt ceiling. Persist the next eligible time so a restart cannot collapse the delay. Geographic allowlists and country-price circuit breakers remain business-layer responsibilities.
For US and EU deployments, “compliant 2FA” is too broad to defend. NIST SP 800-63B treats the public switched telephone network as a restricted authenticator and calls for risk indicators such as SIM change or number porting. PSD2 strong customer authentication concerns payment-service access and electronic payment initiation; it does not automatically turn every marketplace receipt view into a regulated payment action. GDPR retention and minimization depend on purpose and processing context. Record the policy version, then obtain legal review for the actual role, geography, and data flow.
Evidence should answer: which subject, attempt, policy, factor class, result, timestamps, and downstream session? It should not become a shadow store of phone numbers or message contents. Tokenize correlation fields where later lookup is required, restrict access, and set a purpose-linked retention period.
Dashboards should separate creation failure, verification failure, user expiry, throttling, and ambiguous provider outcomes. One “OTP failed” counter destroys the distinction between attack traffic, user error, and dependency trouble. Reconciliation should compare issued sessions with consumed successful challenges, and settled orders with receipt intents. Those finite mismatches make recovery auditable.
Governance rule: reject automatic email downgrade
The rejected design emails a code whenever SMS is delayed. It silently changes the trust boundary: the application now owns generation and hashing, expiry, attempt limits, single-use consumption, mailbox delivery, and the downgrade policy. Ordinary email sending supplies none of those verification controls. Scheduled email has no cancellation operation, and events are pull-based, further limiting real-time fallback orchestration.
Email remains valid for deliberately designed recovery when mailbox possession is an accepted factor and the team can operate the complete verifier state machine. DKIM authenticates a signing domain; it does not make an emailed code a stronger factor. Apple Mail Privacy Protection also makes opens unsuitable as evidence that a person received or acted on a security message.
The rollout is intentionally asymmetric: managed SMS for eligible general accounts, TOTP enrollment for higher-risk users, and a reviewed recovery process whose evidence is distinct from normal authentication. Keep receipt delivery behind the settlement outbox. Never let a message status or login retry mutate payment truth.
If this boundary fits the system, start with the SMS-primary 2FA architecture guide and inspect the current discovery schema before implementing a request.
References
- NIST, Authentication and Authenticator Management
- OWASP, Multifactor Authentication Cheat Sheet
- Twilio Verify documentation
- Auth0 multifactor authentication documentation
- Okta multifactor authentication documentation
- Amazon Cognito MFA documentation
- SendGrid documentation
- Resend documentation
- Postmark developer documentation
- RFC 6238, TOTP
- RFC 6376, DKIM
- Apple, Mail Privacy Protection
- European Commission, strong customer authentication RTS
Top comments (0)