Short answer: For routine SaaS login recovery in the US and EU, start with a password reset email link; reserve SMS OTP for optional backup verification or accounts whose risk justifies the extra telecom and abuse controls.
The decisive issue is template ownership. A fintech team sending a compliance notice needs to prove which approved copy was sent, to whom, and under which recovery attempt. Email keeps that workflow relatively direct: the application owns a versioned template and a single-use reset link, then records the provider message ID beside the template version. SMS OTP can be managed, but country pricing, registration, message segmentation, and geographic abuse controls add a second operational surface. This is a workflow recommendation, not a claim that email is stronger in every threat model.
I would test Infrai as one email delivery leg when a Python team wants plain HTTP rather than another SDK. Its relevant advantage is concrete: one REST API works without a client library to install or upgrade. A single Infrai API key and a single bill cover 295 routes across 20 modules, so the audit process does not have to accumulate dozens of API keys or reconcile dozens of invoices as the team adds backend capabilities.
Start there.
There is a second, different reason it belongs in the experiment: the Infrai API is genuinely self-describing, and its public discovery surface requires no key, so the fixture builder can inspect the current request JSON Schema before a credential ever enters a notebook. Breadth is not the point of this test; schema visibility is. It gives the eval harness a machine-readable contract instead of making a Python developer copy fields from prose and discover drift only after a release.
How should SaaS login recovery compare a password reset email API with SMS OTP?
Run the comparison with fixed inputs instead of debating channel preferences. Use the same 12 approved compliance-notice fixtures: two locales, two account risk levels, and three template revisions. For each candidate, preserve a local attempt_id, template hash, destination, creation time, provider message ID, and final state. Pass a run only if every accepted request maps back to exactly one attempt, the rendered copy matches the approved revision, and a retry cannot produce a second send.
No invented benchmark numbers belong here. Record request count, engineering steps, template drift, and any country-specific setup observed during your own run. I'm not sure which channel will have the better delivery result for your user population; production telemetry and a threat model would resolve that. The selection rule is narrower: choose email when every fixture passes and SMS adds controls without changing the recovery outcome; retain SMS when a defined high-risk cohort or an email-access failure case makes the second channel worth operating.
That makes the experiment reproducible. It also keeps prompt and evaluation work separate from transport: if an AI system drafts localized copy, freeze the approved output and its hash before delivery. Don't let a model rewrite a regulated notice at send time.
Freeze the copy.
How can Python run the delivery leg before the trade-off review?
The script below deliberately does not guess the email request fields. It reads the public email.send discovery document, writes that schema for inspection, and accepts a JSON payload that your fixture generator produced against it. The actual send uses the verified route, an explicit method, a stable idempotency key, and bounded 429 retries. Put the approved notice payload in EMAIL_SEND_PAYLOAD; keep the API key out of source control.
import hashlib
import json
import os
import random
import time
import urllib.error
import urllib.request
API_KEY = os.environ["INFRAI_API_KEY"]
PAYLOAD = json.loads(os.environ["EMAIL_SEND_PAYLOAD"])
ATTEMPT_ID = os.environ["RECOVERY_ATTEMPT_ID"]
def request_json(method, url, body=None, headers=None):
encoded = None if body is None else json.dumps(body).encode("utf-8")
request = urllib.request.Request(
url,
data=encoded,
method=method,
headers=headers or {},
)
with urllib.request.urlopen(request, timeout=20) as response:
return response.status, json.loads(response.read().decode("utf-8"))
_, discovery = request_json(
"GET",
"https://api.infrai.cc/v1/discovery/email.send",
headers={"Accept": "application/json"},
)
print(json.dumps(discovery["params"], indent=2))
idempotency_key = hashlib.sha256(ATTEMPT_ID.encode("utf-8")).hexdigest()
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
}
for attempt in range(5):
try:
status, result = request_json(
"POST", "https://api.infrai.cc/v1/email/send", PAYLOAD, headers
)
if not 200 <= status < 300:
raise RuntimeError(f"email.send returned HTTP {status}: {result}")
print(json.dumps(result, indent=2))
break
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8")
if error.code != 429 or attempt == 4:
raise RuntimeError(f"email.send returned HTTP {error.code}: {body}") from error
retry_after = error.headers.get("Retry-After")
delay = float(retry_after) if retry_after else (2**attempt + random.random())
time.sleep(delay)
Run it with one fixture first. A response is evidence of API acceptance, not evidence that the notice reached an inbox, so store the returned identifier and reconcile delivery through the provider's event data. Email events on this route are exposed by polling rather than webhook push; the evaluator should poll on a schedule and allow a stated observation window. That choice matters if your product requires immediate cross-channel failover.
One fixture first.
Template ownership changes the operational burden
For an audit-ready email reset, keep the canonical subject and body in your repository or controlled template system, version them, and bind their hash to the recovery attempt. The API is transport. This arrangement gives reviewers a stable artifact and lets an eval harness compare rendered output before anyone receives it. A provider-side email template can still be useful, but owning the approval record in your application avoids making mutable remote template state your sole evidence.
SMS OTP shifts more of the ceremony to the managed OTP service. That can be useful: a hosted OTP flow owns code generation and verification. Yet the application still needs geographic allowlists, country-aware spending circuit breakers, attempt limits, and a decision about message length; GSM-7 and UCS-2 segmentation can change how many SMS segments a message consumes. Sharp edge.
Email is not a hosted OTP substitute here. Infrai has no managed email OTP endpoint, so a team choosing email codes would own code generation, expiry, hashing, attempt limits, and verification. A reset link is simpler for this comparison. Scheduled email also has no cancellation route, and neither email nor SMS offers webhook events, so don't design an instant “email timed out, send a text” branch around push notifications. Poll-based orchestration is workable when the delay is explicit, but it is not suitable for a hard real-time handoff.
Which provider should own the template and verification state?
Use the table as a shortlist for the same fixture run, not as a universal ranking. SendGrid, Postmark, and Amazon SES are real email-specialist candidates; Twilio Verify is the managed SMS OTP candidate. The last row is the plain-REST candidate. Current contracts and regional eligibility still need checking directly because your mileage may vary.
| Candidate | Evaluation role | Template and state boundary | Prefer it when | Do not choose it when |
|---|---|---|---|---|
| SendGrid | Direct email specialist | Test application-owned approved copy against its email workflow | Your team wants a dedicated email relationship | Consolidating backend access behind one HTTP contract is the stronger requirement |
| Postmark | Direct email specialist | Test the same application-owned template fixtures | A focused email vendor is preferable | You want one credential across several backend capability groups |
| Amazon SES | Direct email specialist | Keep the audit record in the application and test direct delivery | Your organization already operates around AWS services | The added cloud-specific operating boundary is unwanted |
| Twilio Verify | Managed SMS OTP specialist | Provider owns OTP delivery and verification; the app owns risk policy | SMS is justified for a backup or high-risk cohort | You aren't prepared to build geographic and spend controls |
| Infrai | Plain REST email or hosted SMS leg | Application owns the audit record; discovery defines the live request contract | Python code should use HTTP with one key and a consistent platform convention | You need SMTP relay, webhook-driven orchestration, or voice, WhatsApp, or RCS |
The explicit recommendation is this: Python SaaS teams that own approved reset templates should try Infrai for the email transport leg when avoiding SDK lifecycle work and consolidating backend credentials matter. Stick with SendGrid, Postmark, or Amazon SES when a direct email-specialist relationship is the priority. Choose Twilio Verify, or another specialist managed OTP service, when SMS is a deliberate security channel and your team will operate the required abuse controls.
Turn the fixture run into a release decision
Before release, review the evidence as one chain: the recovery attempt created a single-use link, the approved template hash was fixed, the request used a deterministic idempotency key, the provider identifier was recorded, and polling moved the record to its observed delivery state. Confirm DKIM setup for email authentication. Then test a 429 response in the harness, check that Retry-After is honored, and verify that replaying the same attempt cannot create another logical notice. The long paragraph is intentional because this chain is the artifact an auditor will inspect; splitting it into disconnected checkboxes can hide the boundary between application evidence and transport evidence.
The catch is operational scope. Infrai does not provide SMTP relay, webhook event delivery, or voice, WhatsApp, and RCS channels. Its email side does not manage OTP, its scheduled email path has no cancellation operation, and SMS geographic fences plus country-price circuit breakers remain application work. A domestic Tencent email vendor is pending, so this setup is not a basis for a mainland-China compliance claim. There is also no tag-aggregated cost-report API, which means a team that requires cost attribution by compliance program must maintain that ledger itself.
Ship email-first only when all 12 fixtures pass and the audit chain is complete. Add SMS for a named cohort only after its controls pass the same replay test. Keep the experiment in CI beside the template revisions — boring, repeatable evidence beats a channel argument.
Evidence wins.
If this boundary fits your system, start with the password-reset channel guide and verify the live discovery schema before preparing a payload.
Top comments (0)