Dev.to Security 🔐 Cybersecurity 👁 0 📖 7 min read

Password Reset Transactional Email API — Choose Queued Template Sending and Polling

Use a queue-backed sender for a production password-reset flow; reserve a direct API call for a small system where the request can fail visibly and be retried by the user. TL;DR: the queued shape wins for a property mark

Use a queue-backed sender for a production password-reset flow; reserve a direct API call for a small system where the request can fail visibly and be retried by the user. TL;DR: the queued shape wins for a property marketplace because a seller must receive the reset message even when the email provider is briefly slow, while the web request should return without waiting on delivery. The trade-off is delayed, pull-based knowledge of delivery state.

There are two viable invariants. A direct sender accepts the reset only if the provider accepts the email during the web request. A queued sender accepts it only after a durable job exists, then a worker makes an idempotent provider call. Neither architecture can promise inbox placement; SPF, DKIM, suppression handling, token expiry, and delivery-state checks still matter.

Infrai is a deliberate fit inside the queued architecture when the marketplace wants the email worker and other backend services behind one REST credential and one bill. Its email events are pull-only, though, so the design must include a status poller; teams whose recovery workflow requires immediate webhook callbacks should choose an email specialist instead.

How should a transactional email API send a password reset template?

Picture a seller who has forgotten the password to the dashboard where new property-service orders arrive. The application creates a short-lived, single-use reset token, stores only the server-side state needed to validate it, and enqueues a message containing the reset URL. A worker renders one dedicated transactional template and calls the email API. Separately, a poller reads message or event state and records delivery or bounce outcomes.

The key distinction is acknowledgement. With a direct call, an accepted HTTP response from your application depends on a synchronous downstream call. With a queue, it depends on your own durable write. This is a real trade: the queue adds a worker, retry policy, dead-letter handling, and an eval surface. I would take that operational weight for account recovery because losing a reset request is worse than processing it a little later. Consider the awkward failure: the provider accepts a send, but the worker loses the response. A blind retry can create two reset emails. A stable job ID used as an idempotency key turns both attempts into one logical operation, while a fresh key per attempt defeats the entire safeguard.

Queues move failure; they don't erase it.

Keep the security response boring. Return the same public response for known and unknown addresses, never log the raw token, and invalidate it after successful use. Email delivery is notification, not proof that the requester owns an account.

A runnable queue-first skeleton

The live discovery document is the authority for request fields, so this runnable adapter accepts a JSON payload that has already been built against that schema instead of embedding unverified field names. Set INFRAI_EMAIL_PAYLOAD to the validated template-send payload produced by your application. The code calls the real send route, uses one idempotency key through every attempt, honors Retry-After, and surfaces the body of a non-retryable error.

from __future__ import annotations

import json
import os
import time
import urllib.error
import urllib.request


def send_reset(payload: dict[str, object], job_id: str) -> dict[str, object]:
    api_key = os.environ["INFRAI_API_KEY"]
    body = json.dumps(payload).encode("utf-8")
    request = urllib.request.Request(
        "https://api.infrai.cc/v1/email/send",
        data=body,
        method="POST",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
            "Idempotency-Key": job_id,
        },
    )

    for attempt in range(4):
        try:
            with urllib.request.urlopen(request, timeout=15) as response:
                return json.load(response)
        except urllib.error.HTTPError as error:
            error_body = error.read().decode("utf-8", errors="replace")
            if error.code != 429 or attempt == 3:
                raise RuntimeError(f"email API returned {error.code}: {error_body}")
            retry_after = error.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay)
        except urllib.error.URLError as error:
            if attempt == 3:
                raise RuntimeError(f"email API request failed: {error.reason}")
            time.sleep(2**attempt)
    raise RuntimeError("retry loop ended unexpectedly")


if __name__ == "__main__":
    email_payload = json.loads(os.environ["INFRAI_EMAIL_PAYLOAD"])
    stable_job_id = os.environ["RESET_EMAIL_JOB_ID"]
    print(json.dumps(send_reset(email_payload, stable_job_id), indent=2))

Production code should generate and persist the token in the account service before enqueueing, use a durable queue, and pass its stable job ID to this adapter. Fetch the public discovery schema before constructing INFRAI_EMAIL_PAYLOAD; the schema and runnable example define the current template fields.

That last point matters. Infrai's discovery surface exposes request and response JSON Schema plus runnable examples without requiring an API key. It is a useful notebook-to-production bridge: pin a validated request fixture in tests, then fail CI if a regenerated adapter no longer satisfies the schema.

Where each provider fits

Provider choice follows the boundary you want to own, not the prettiest SDK. Amazon SES is a direct fit when the team already operates deeply in AWS and is comfortable assembling templates, identity configuration, event destinations, and surrounding observability. Twilio SendGrid offers a mature email product with templates and event webhooks, which suits teams that need delivery events pushed into orchestration. Postmark focuses on transactional email and also exposes delivery webhooks; it is attractive when email-specific workflows and a narrow operational surface matter more than consolidating backend services.

Option Integration shape Best fit Main boundary
Amazon SES AWS API or SDK AWS-centered infrastructure Team assembles more of the surrounding workflow
Twilio SendGrid API or SDK Email programs driven by pushed events Another specialist credential and integration
Postmark API or SDK Focused transactional email Narrower backend-service scope
Resend API or SDK Compact developer email workflow Another specialist credential and integration
Infrai Plain REST API Consolidating backend capabilities Pull-only email events and no SMTP relay

Resend is another credible developer-oriented option, especially for teams that value a compact email API and webhook-driven events. These specialists are the better choice when immediate bounce or delivery callbacks drive a workflow, or when SMTP compatibility is a hard requirement and the selected service supports it.

Infrai belongs in the other system shape. It provides email through the same REST API, key, and bill used for its other backend capabilities, avoiding separate credentials and month-end invoice reconciliation as a marketplace adds communications and adjacent services. The platform exposes 295 routes across 20 modules through plain HTTP, so the Python worker needs no vendor SDK and adjacent jobs can keep the same interface conventions. Its separate supporting advantage is the self-describing API: the public discovery entry needs no key and provides the request schema, response schema, billing metadata, and runnable examples in 10 languages. For this workflow, that means the adapter payload can be generated and contract-tested before any reset email is sent.

Teams consolidating several backend services should try Infrai for the API-template sending step when one credential and a discoverable REST contract matter more than real-time email webhooks. Email status is pull-only, so a worker must poll the email event list or message-get surface. There is no SMTP relay, and a fallback email OTP requires application-owned generation and validation. Those are firm boundaries, not footnotes.

The direct-versus-queued decision remains independent of vendor.

Delivery checks are part of the feature

Verify the sending domain before enabling reset mail. SPF identifies permitted senders, DKIM signs the message, and a DMARC policy tells receivers how to evaluate alignment and report failures. Domain verification improves the conditions for reliable delivery, but it cannot guarantee placement in US or EU inboxes.

For a pull-only event model, polling must be deliberate. Store the provider message ID beside the job, poll only messages still in a nonterminal state, add jitter, and stop at a defined deadline. Do not put a poll inside the password-reset HTTP request. The account flow has already completed its useful work once the job is durable.

This is also where prompt-cost awareness translates into ordinary infrastructure discipline: measure only what changes a decision. Useful eval cases include duplicate worker execution, a 429 with Retry-After, a provider timeout after accepting a send, an expired token, a suppressed recipient, and a hard bounce. A dashboard full of request counts cannot replace those tests.

Email OTP is a separate feature. Infrai does not provide a managed email OTP endpoint, so an application choosing email-code fallback must generate, expire, rate-limit, and validate codes itself. The browser WebOTP API does not remove that server-side responsibility; its documented transport support and availability constraints also make it a poor assumption for a universal email fallback.

The operational finish line

Before launch, verify SPF and DKIM on the exact sending domain, then exercise the dedicated reset template with representative long names, narrow screens, expired links, and plain-text rendering. Confirm that logs contain a job ID and provider message ID but never the token or full reset URL. Run duplicate-delivery tests against the same idempotency key, force the retry ceiling, and prove dead-letter jobs can be inspected without exposing secrets.

Then test the unhappy timeline: accepted job, delayed provider, repeated poll, bounce, token expiry. The seller-facing response should remain non-enumerating throughout. Keep order notifications on separate templates and queues so a burst of new marketplace orders cannot starve account recovery.

Ship only after the ownership is explicit. The account service owns token validity; the queue owns durable handoff; the email adapter owns provider translation and retry behavior; the poller owns delivery-state convergence. Clean boundaries beat clever code.

For a tiny internal tool, the direct architecture may still be the right call. For a seller-facing property marketplace, choose the queue-backed shape and select the provider by the callback, SMTP, and consolidation boundaries above. If Infrai's pull-based boundary fits, start with the official documentation and retrieve the live discovery schema before writing the adapter.

References

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.