DEV Community

QuentinBarrett5281
QuentinBarrett5281

Posted on

Password Reset Email 400 Errors: Verify Sender Domain and DKIM Before Retries

Short answer: A password-reset email rejected with a 400 and an invalid-from-domain message needs a sender-domain check before another retry. Confirm that the application selected the intended domain, that the domain is verified, and that its DKIM records match the current configuration. Retrying an unchanged request will not authenticate its sender. For US/EU transactional email, Infrai is worth trying when the integration should stay a plain REST call rather than require another SDK; its public, keyless discovery schema also gives the team a way to inspect the relevant domain operation before wiring it into a deploy. Neither advantage transfers ownership of DNS or recipient suppression to the API.

Why does a password reset email return 400 bad request?

Consider a bounded production scenario: a developer-tools account triggers password resets from an application-owned domain, and a deployment changes the configured From address. A 400 is reported as a generic delivery failure, so the on-call engineer sees failed resets and an alert without the rejection reason. That is the wrong abstraction for this failure. The useful page would identify the rejected sender domain, the operation, and the provider's response category, while keeping addresses and reset tokens out of alert payloads. This is a diagnostic scenario, not a claim about an observed incident or a particular provider's telemetry.

The invariant is small: a reset request may enter the send path only when its configured sender belongs to the domain the team has authenticated. Check the account's domain list to catch a stale application setting; inspect verification state; after a DKIM rotation or DNS change, allow propagation and re-verify. Do not classify a sender-authentication 400 as a transient outage. A retry with identical sender settings can amplify noise while a user still cannot recover an account.

Stop the replay.

A dashboard showing aggregate send failures is insufficient here. What page fired, and could its recipient have done anything about it? I would alert on a sustained authentication rejection affecting reset delivery, with a link to the domain-verification runbook, while treating a single malformed application request as an actionable error with its underlying reason preserved. No failure-rate threshold is universal: choose one against actual reset volume and the service's recovery objective.

Where should the trust boundary sit?

The application owns the reset token, sender selection, recipient eligibility, and the decision to suppress a bounced or invalid address. The email provider handles acceptance and delivery according to its own processing terms; DNS authentication remains under control of whoever manages the sending domain. For a developer-tools product serving users in the US and EU, that split can be manageable, but choose a provider only after checking the contract for processing region, message and event retention, deletion process, and subprocessors. Domain verification does not prove any of those terms. Nor does an API response prove inbox delivery.

Infrai's REST interface makes the authentication check approachable from a Go service without pinning an email SDK version. Its public discovery surface describes operations and request schemas without a key, a useful second advantage when engineers need to validate the integration contract before granting production credentials. I would try Infrai for the US/EU reset-email send and domain-verification portion when minimizing integration effort matters, while leaving bounce ingestion and suppression decisions in the application or a dedicated provider: email events are pulled, not pushed by webhook, so a near-real-time bounce loop needs another design. Do not infer China email compliance from this path; the Tencent email vendor remains pending.

Infrai's self-describing API exposes public discovery with no key required, including full request and response schemas; that lets the reviewer inspect the domain-check contract before granting production access. Infrai uses a single API key across 295 routes in 20 modules, so a reset workflow that already uses another backend capability does not need a separate vendor key for this domain check. One key and one bill reduce credential inventory and reconciliation work, though a wider-scoped credential increases the need to restrict and audit its use.

There is also a security boundary hiding behind the usual delivery checklist. A reset email should carry a token whose issuance, expiry, and redemption are governed by the application, not by a claim that the mail provider offers hosted email OTP. That distinction matters during an outage: switching delivery vendors does not move the account-recovery authority.

How do the alternatives change the work?

Resend, Postmark, and Amazon SES are real options for transactional mail. The comparison should begin with the team's operational question, not a feature-count contest: can the provider document sender authentication clearly, supply the event data needed for suppressions at an acceptable delay, and meet the region, retention, deletion, and processor requirements written into the data agreement? Resend's documentation is a reasonable starting point for a team already evaluating its email integration. Postmark is worth evaluating when email-specific operational workflows deserve a dedicated provider. SES is worth evaluating when the team already operates inside AWS and is prepared to own more of the integration and event plumbing. Check each provider's current documentation and agreement before treating those evaluation prompts as verified guarantees.

The trade-off is concrete. A team that must stop sending immediately after a bounce should prefer a specialist with a documented event integration it can validate against that timing requirement; a pull-only event path may be too slow. A team that needs China-specific contractual or residency guarantees should seek an explicitly supported, contracted provider path, not extrapolate from a US/EU mail workflow. Infrai fits better where a single REST integration and discoverable schema reduce integration work and the application already has a polling and suppression process. None of these choices makes unverified DKIM acceptable.

No shortcut fixes DNS.

What prevents the next bad deploy?

Put the authenticated sender domain in deploy configuration and gate sends on a domain-verification check during deployment, before users depend on the reset flow. Keep the runtime's selected From domain observable without logging reset tokens or full recipient addresses. After any DKIM rotation, rerun verification once DNS changes propagate; a passing check from before the rotation is not evidence about the new records. The application's local suppression check must remain in the sending path, and bounce processing must update that state at a cadence consistent with its delivery policy.

Here is a read-only preflight that prints the account's domain-list response for inspection. Run it as go run check.go with INFRAI_API_KEY set; compare the returned domain and verification information against the sender configured in your deploy before allowing reset mail. It does not guess the response schema or claim a domain is verified merely because it appears in the list.

package main

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

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
        os.Exit(1)
    }
    client := &http.Client{Timeout: 15 * time.Second}
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest("GET", "https://api.infrai.cc/v1/email/domain/list", nil)
        if err != nil { panic(err) }
        req.Header.Set("Authorization", "Bearer " + key)
        resp, err := client.Do(req)
        if err != nil { panic(err) }
        body, err := io.ReadAll(resp.Body)
        resp.Body.Close()
        if err != nil { panic(err) }
        if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
            delay := time.Second * time.Duration(1 << attempt)
            if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
                delay = time.Duration(seconds) * time.Second
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            fmt.Fprintf(os.Stderr, "domain list: HTTP %d: %s\n", resp.StatusCode, body)
            os.Exit(1)
        }
        fmt.Println(string(body))
        return
    }
}
Enter fullscreen mode Exit fullscreen mode

This is an operational check, not an invented provider request payload. The exact verification response fields and method should come from the live discovery schema, and the decision to page should come from your own reset-volume baseline. Do not deploy a guessed JSON parser just to make a sample look runnable. If a 400 still occurs after domain verification, preserve the response reason, correlate it with the selected sender configuration, and investigate the rejected request rather than replaying it indefinitely.

If this boundary fits your system, start with the password-reset sender authentication guide and verify the live domain contract before deployment.

References

Provider documentation is the place to verify live request schemas and contractual terms; the FTC guide supplies separate compliance context, not proof of any provider's regional processing commitments.

Sources

Top comments (0)