DEV Community

UlyssesBlack2385
UlyssesBlack2385

Posted on

Custom Password Reset Email API Selection — Auth Template Ownership Without Webhooks

Short answer: keep the marketplace's compliance-notice template under the control of the team accountable for its wording, and choose an email API only after proving that its event records can be collected without webhooks. Apply the same test to a custom password-reset email: the authentication system owns the reset token and its validity; the sender owns transport evidence. An accepted API request is not proof of delivery.

For a marketplace notice, this distinction matters at 3 a.m. If support asks whether a seller was notified of a policy change, a green send dashboard answers the wrong question. What page fired when collection stopped? What evidence can an operator retrieve for that seller, that notice version, and that attempt?

How should auth handle a custom password reset email API without webhooks?

Start with a template-ownership decision, before comparing APIs. The marketplace should control the legally reviewed notice text, its version, the recipient-selection rule, and the exact rendered content or immutable content reference associated with each send. A delivery service can render a template if its version is pinned and exportable, but letting an editable external template change independently of a reviewed notice creates an audit gap. The same risk appears with password-reset email when an authentication system generates the recovery link while another system supplies the surrounding message: the token issuer must remain authoritative about expiry and redemption, while the message owner must know which copy was actually sent.

The names Supabase Auth, Clerk, and NextAuth are useful as integration questions, not as a leaderboard. For the exact authentication configuration in use, determine who generates the token, who renders the message, and where a custom sender can be inserted; test those boundaries against its current documentation and a staging send. Do not assume that choosing an email API transfers ownership of recovery-token security or template approval to that API. If the auth system does not expose the rendered message or a supported handoff at the boundary you need, the design must change before sender selection can mean anything. Consider a notice queued under template version 4 while an editor approves version 5: the audit record for that queued notice must still identify version 4, even if the provider renders it hours later. A mutable template name alone cannot establish which text the recipient was sent. For a reset email, the same race can change instructions while leaving the already issued token unchanged, so validate the end-to-end handoff before enabling production traffic.

Version the text first.

Which signal would wake the on-call engineer?

Define the evidence chain as separate states: notice approved, recipient selected, send attempted, provider accepted, and a subsequent delivery or failure event observed. SMTP distinguishes acceptance for relay from final delivery; a delivery status notification may supply later status, but even a reported delivery is not proof that a human read or understood the notice. Record those distinctions in the audit log. Do not rename accepted to delivered just because the API returned success.

No webhook is a real constraint. Ask each prospective sender whether authenticated event retrieval is supported, what event types and retention window are available, whether pagination and stable event identifiers exist, and how far back a cursor can recover after an outage. These are acceptance tests, not assumed features. If the API cannot return the events needed for the audit period, polling faster will not create evidence that the API does not retain. Preserve the provider's raw event identifier and timestamp alongside your own state transition, and keep the original response for investigation under your data-retention rules. The limitation of polling is delayed visibility and dependence on event retention: it is unsuitable when the required response deadline is shorter than the worst-case collection gap you can tolerate. In that case, use a supported push-event path or another auditable delivery workflow, subject to the same evidence test.

Page on a broken evidence chain, not a pretty aggregate: a sustained gap between accepted attempts and collected events, an event-collection cursor that stops advancing, or notice attempts that remain unresolved beyond the operational deadline. Set the deadline against the actual notice policy and the measured event delay, not a made-up universal number. A dashboard of total sends can stay green while the one compliance notice under investigation has no correlated outcome.

How should the send and poll loop be wired?

Give each notice an internal ID and immutable template version before sending. Store the recipient and the relevant policy revision with restricted access; persist an attempt ID and the provider's message ID when the API returns one. Use a durable queue for sending and a separate scheduled event collector. On retry, reconcile the attempt and any known provider ID first: a timed-out HTTP response may mean the sender accepted the request but the client never saw the answer. Whether an API offers idempotency keys is a specific selection criterion to verify, not a license to blindly resend a compliance notice or a reset link.

The collector should request a bounded time window with overlap, advance its checkpoint only after a full page is durably stored, and deduplicate by stable event ID or a documented composite key. Overlap protects against late-arriving events; it also means duplicates are normal. If the provider exposes neither an event ID nor reliable pagination semantics, write down the ambiguity and test how it affects the audit requirement before deployment. Authentication credentials for polling need read-only scope where supported, rotation, and the same secret-handling discipline as send credentials.

Keep the state transition deliberately small. This Go example accepts a normalized event after retrieval; the caller persists the event and resulting state in one transaction, then advances its page checkpoint. It does not assume a particular provider's API shape.

package notice

type State string

const (
    Accepted  State = "accepted"
    Delivered State = "delivered"
    Failed    State = "failed"
)

type Event struct {
    ID        string
    AttemptID string
    Kind      string
}

func Apply(current State, event Event, seen map[string]bool) State {
    if event.ID == "" || seen[event.ID] {
        return current
    }
    seen[event.ID] = true
    switch event.Kind {
    case "delivered":
        return Delivered
    case "failed":
        return Failed
    default:
        return current
    }
}
Enter fullscreen mode Exit fullscreen mode

This is an illustration of duplicate handling, not a complete event-ordering policy: a provider can report different event types at different times, and a map in memory is not an audit database. Define precedence from documented event semantics and store deduplication durably before letting a late event revise a terminal state.

Treat personal data as part of the design. A reset URL contains a sensitive recovery credential; do not place it in logs or event metadata. Audit records should identify the attempt and template version without storing a reusable token. The Fetch API's response status and body let a client distinguish an HTTP request outcome from application data, but neither says whether a mailbox received the message. That boundary is why the event collector exists.

No token in logs.

How do we verify and roll back?

In staging, send a reviewed notice to controlled addresses, then inspect the stored template version, attempt record, provider acceptance response, and any later event separately. Exercise an invalid recipient, a network timeout after submission, a duplicate poll page, an expired event window, and a collector restart. For password reset, additionally verify that an old or redeemed link cannot be used and that no token leaks into logs. A test that only checks for an HTTP success response is too shallow for either workflow.

Deploy the collector before moving live traffic, so the event checkpoint and alarms already exist when the first notice goes out. If the event API stops yielding usable records, pause new compliance-notice batches when policy permits, retain the queued work and all accepted attempt IDs, and investigate the retrieval gap. Rolling back a template means selecting a previously approved version for future sends; it does not rewrite the content or status of messages already attempted. For urgent notices whose deadline cannot wait, escalation belongs to the marketplace's compliance procedure, with the missing evidence explicitly recorded. Silent retries are not a rollback plan.

The decision rule is practical: adopt a sender only when the responsible team can prove template control, correlate every attempt to a retrievable outcome within the required window, and reconstruct what happened after the polling process fails. If any of those tests fails, the sender is not ready for this notice workflow, regardless of how clean its send API looks.

References

Top comments (0)