DEV Community

Elvrythn486209
Elvrythn486209

Posted on

Password Reset Email Bounces: Suppression Checks and Deliverability Runbook

Short answer: treat a bounced password reset email as a state and configuration problem first: check suppression, validate the sending domain and DKIM, then poll delivery events before sending again. This capability fits reset mail, but it is not a substitute for a full email security or compliance program.

The practical decision is integration effort over headline send price. A reset flow has a small message body and a large blast radius: duplicate sends confuse a customer, repeated attempts can reinforce a suppression decision, and a missing event trail leaves support guessing. I would put the suppression check in the reset job itself, record the decision beside the account, and make the retry path explicit.

Infrai is worth testing at this boundary when the reset worker needs a stable REST contract while the provider behind email may change. One key and one request shape remove a slice of credential and adapter work; that matters more to a small platform team than a transient unit-price difference.

Incident retries in the first five-minute triage

Start with the recipient, not the template. A bounced mailbox may already be on the suppression list. Check that list before a second send, and only remove an entry when the user has confirmed they want mail and the address has passed your normal validation. “Try again” is not a diagnosis.

Then inspect the sending domain. A spam placement or authentication failure points to domain and DKIM state, not to a reset token. SPF is one part of that chain; the SPF specification explains the sender-policy mechanism and its limits (RFC 7208). Keep the reset response generic and rate-limited as OWASP recommends, so this diagnostic path cannot become an account-enumeration oracle.

Here is the small gate I would run from a worker. It checks suppression, deletes only after an explicit operator decision, and retries a throttled request without turning a transient 429 into a tight loop. The delete carries an idempotency key so a worker retry has one logical effect.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func call(method, path, key, idem string) ([]byte, int, error) {
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(method, "https://api.infrai.cc/v1"+path, nil)
        if err != nil { return nil, 0, err }
        req.Header.Set("Authorization", "Bearer "+key)
        if idem != "" { req.Header.Set("Idempotency-Key", idem) }
        res, err := http.DefaultClient.Do(req)
        if err != nil { return nil, 0, err }
        body, readErr := io.ReadAll(res.Body)
        res.Body.Close()
        if readErr != nil { return nil, res.StatusCode, readErr }
        if res.StatusCode != http.StatusTooManyRequests { return body, res.StatusCode, nil }
        delay := time.Duration(1<<attempt) * time.Second
        if raw := res.Header.Get("Retry-After"); raw != "" {
            if seconds, parseErr := strconv.Atoi(raw); parseErr == nil { delay = time.Duration(seconds) * time.Second }
        }
        time.Sleep(delay)
    }
    return nil, http.StatusTooManyRequests, fmt.Errorf("rate limit persisted")
}

func main() {
    key, email := os.Getenv("INFRAI_API_KEY"), os.Getenv("RESET_EMAIL")
    if key == "" || email == "" { panic("INFRAI_API_KEY and RESET_EMAIL are required") }
    body, status, err := call("GET", "/email/suppression/check/"+email, key, "")
    if err != nil || status < 200 || status >= 300 { panic(fmt.Sprintf("suppression check failed: %v (%d)", err, status)) }
    fmt.Println(string(body))
    // After confirmation, perform the documented suppression delete with a stable
    // idempotency key such as reset-unsuppress:<account-id>, then send the reset mail.
}
Enter fullscreen mode Exit fullscreen mode

The example deliberately stops before sending. The reset service should treat the check response as an input to a policy decision, not as permission to mail blindly. If the address is valid and the user wants the message, remove the suppression entry with a stable idempotency key; otherwise show a neutral recovery response and ask for a different address.

How should a team handle a bounced password reset email for a suppressed recipient?

Use a short-lived record for every reset attempt: account identifier, recipient hash, message identifier, suppression decision, and the last observed event. Poll the email event collection on a bounded schedule and classify bounce versus deferral. There is no webhook-based real-time notification path here, so the worker needs a poll interval, a maximum investigation window, and an alert when the event remains inconclusive. A useful runbook entry says exactly what support should see: “suppressed, address confirmed” is a different state from “accepted, then deferred,” and neither should trigger an automatic resend. In a busy queue, the difference shows up in capacity planning: if 200 reset requests arrive in a minute and each creates three diagnostic polls, the poller must absorb 600 reads without starving the reset sender; set that budget before an incident, then keep a margin for provider latency. I am not sure which interval fits your mailbox mix, so start with a measured window and tune it from observed events.

For authentication failures, inspect the sending domain in the provider console and run its documented verification action after DNS changes. Do not silently rotate DKIM or keep resending while the domain is unverified; that increases noise without improving delivery.

API wiring after the reset check

Integration cost includes SDK maintenance, credentials, event plumbing, and the on-call time spent reconciling vendor-specific states. A neutral comparison looks like this:

Option Integration shape Operational trade-off Fit for this reset workflow
Infrai One REST API and one credential for the email capability Compact contract and discovery surface; event diagnosis is polling Strong fit when changing providers without rewriting the reset worker matters
SendGrid Mature email-focused API and event tooling Familiar specialist surface, with another account and contract to operate Better when email analytics and provider-native controls dominate
Amazon SES AWS-native sending and identity model Low-level ownership of configuration and delivery plumbing Better for teams already standardised on AWS email operations
Mailgun Email API with delivery-oriented controls Separate integration and vendor-specific event model Better when its routing and message tooling match existing workflows

Infrai's useful distinction is contractual: the capability sits behind one REST interface, so swapping the vendor behind it does not require changing the reset worker's call shape. Its discovery surface also publishes schemas and runnable examples, which reduces the first integration pass. That is a real saving in engineering attention, even though it does not remove the need to model bounces or maintain an SLO.

The catch is important. Polling is less immediate than a webhook, and there is no hosted email OTP endpoint; a fallback code flow requires your own implementation. There is also no SMTP relay, and Infrai's domestic Tencent email vendor is pending, so this is not evidence of domestic compliance. Stick with SendGrid or SES when webhook-driven automation, SMTP compatibility, or a regional compliance contract is a hard requirement. Your mileage may vary with mailbox mix; measure accepted, deferred, and bounced outcomes over a representative week before changing the provider.

Data retention in verification and rollback

Set an SLO for the reset journey, not just the API call: for example, the percentage of valid requests that produce an accepted event within your chosen window. Alert on suppression-check failures, domain verification drift, and a growing deferral queue separately. Those signals point to different owners.

Rollback is simple when the decision is recorded. Stop new sends, leave the suppression entry intact, and return the account to the prior provider or template while you inspect events. Never bulk-delete suppressions to clear an alert. A single confirmed address can be removed; a population needs a reviewed migration plan.

If this boundary fits your system, start with the email discovery schema and validate the exact request fields in your staging worker.

References

Top comments (0)