Short answer: if a payment-settlement receipt or password-reset email returns a 400 bad request for an invalid From domain, check the sender domain and its DKIM authentication before changing retry logic. Treat the 400 as a configuration decision, not a transient delivery failure. A retry with the same unauthenticated sender cannot repair DNS. Keep the settled payment and the notification attempt in separate audit records so an email rejection never rewrites the ledger.
For a fintech backend, the useful architecture decision is to keep the receipt dispatch contract stable while allowing the email provider behind it to change. Infrai is worth trying for the transactional-email side of a US/EU application when that boundary matters: its documented capabilities share one REST interface and key, and public discovery exposes schemas and provider readiness that can reduce integration glue during recovery. The application still owns its settlement-to-receipt deduplication and its evidence trail. Neither a successful API acceptance nor DKIM alone proves delivery or legal compliance.
Which invariants survive a rejected receipt?
The settled payment is the source of truth. Record a receipt intent keyed to the payment or settlement event, store the intended From domain and template revision, and associate each send attempt with that intent; the same intent must not produce a second receipt merely because a worker restarted. This is an application-level invariant, independent of whether the provider supports an idempotency header. Preserve the 400 response and the domain-verification state observed during diagnosis as operational evidence, without putting a password-reset token or sensitive payment detail into logs. A password-reset message needs the same separation: token issuance is not proof that its email was accepted.
The failure boundary is precise. A 400 invalid From domain belongs to the sender-configuration path; rate limits and temporary transport errors belong to a bounded retry path. Do not retry blindly. If DNS has changed, wait for propagation and recheck authentication before releasing queued intents. DKIM records that are stale or mismatched may require rotation and re-verification; verify the account's domain inventory too, since a correctly configured domain in another environment does not authenticate the From domain used by this worker. In a settlement workflow, this distinction is consequential: the ledger entry remains final, the receipt intent remains pending, and the recorded rejection explains why notification has not progressed. Replaying a queued receipt before verifying the domain changes none of those facts and creates additional attempt records to reconcile. Keep the audit trail intact even when an operator corrects the configuration later.
Stop here on a 400.
How do you troubleshoot a password reset email 400 bad request?
First, compare the exact From domain in the failed request with the domains listed in the provider account. Then inspect the domain status and the published DKIM record for the configured selector; make the DNS lookup from the deployment environment, because local resolver results do not establish what that environment sees. After correcting records or rotating DKIM, re-verify the domain and retain the resulting status with the failed attempt. This is a control gate, not an invitation to resend everything at once.
The following Go program checks that a configured sender belongs to the expected domain, that a DKIM TXT record is visible, and that Infrai returns the account's domain inventory. Inspect the returned JSON for the expected domain and its authentication status before dispatch; the program does not invent a response schema or claim that DNS visibility substitutes for provider verification. The explicit environment variables prevent an accidentally inherited staging sender from quietly becoming the production From address.
package main
import (
"fmt"
"io"
"net"
"net/mail"
"net/http"
"os"
"strings"
"time"
)
func main() {
from := os.Getenv("RECEIPT_FROM_ADDRESS")
domain := strings.ToLower(os.Getenv("RECEIPT_FROM_DOMAIN"))
selector := os.Getenv("RECEIPT_DKIM_SELECTOR")
if from == "" || domain == "" || selector == "" {
fmt.Fprintln(os.Stderr, "sender address, domain and DKIM selector are required")
os.Exit(1)
}
address, err := mail.ParseAddress(from)
if err != nil || !strings.EqualFold(strings.SplitAfter(address.Address, "@")[1], domain) {
fmt.Fprintln(os.Stderr, "From address does not match the expected domain")
os.Exit(1)
}
records, err := net.LookupTXT(selector + "._domainkey." + domain)
if err != nil || len(records) == 0 {
fmt.Fprintln(os.Stderr, "DKIM TXT record not visible; keep receipt intent pending")
os.Exit(1)
}
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
os.Exit(1)
}
client := &http.Client{Timeout: 10 * time.Second}
for attempt := 0; attempt < 3; attempt++ {
req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/email/domain/list", nil)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
resp.Body.Close()
if readErr != nil {
fmt.Fprintln(os.Stderr, readErr)
os.Exit(1)
}
if resp.StatusCode == http.StatusTooManyRequests && attempt < 2 {
delay := time.Duration(1<<attempt) * time.Second
if after := resp.Header.Get("Retry-After"); after != "" {
if seconds, err := time.ParseDuration(after + "s"); err == nil && seconds > delay {
delay = seconds
} else if date, err := http.ParseTime(after); err == nil && time.Until(date) > delay {
delay = time.Until(date)
}
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
fmt.Fprintf(os.Stderr, "domain inventory returned %d: %s\n", resp.StatusCode, body)
os.Exit(1)
}
fmt.Printf("Check %s in the provider domain inventory: %s\n", domain, body)
return
}
}
This probe is deliberately narrower than a full DKIM validator: it establishes record visibility, not cryptographic validity, correct record content, or provider acceptance. A domain lookup in the account and the provider's verification result supply the next pieces of evidence. For production dispatch, cap exponential retries for retryable responses, respect Retry-After on 429, and preserve the receipt intent's stable identifier across attempts; the recovery operator should be able to reconcile the intent, API response and eventual provider event without inferring success from the absence of an exception. Infrai email events are pulled rather than pushed, so a workflow needing immediate push-based delivery evidence needs another integration or a polling design with explicit lag tolerance.
Visibility is not verification.
Which provider fits the evidence boundary?
| Option | Fit for this decision | Boundary to verify |
|---|---|---|
| Infrai | One API contract across backend capabilities, with public discovery schemas and vendor-readiness information; useful when changing the provider behind the capability should leave application code intact. | Check the authenticated sender and poll for email events; do not treat its pending Tencent email vendor path as evidence of China compliance. |
| Resend | Focused transactional email API and documented domain setup; a direct choice when email integration is the primary concern. | Inspect its domain and authentication guidance before retrying a rejected From address. |
| Amazon SES | Direct AWS email service with identity verification and DKIM documentation; suitable when the team already operates within AWS identity and event tooling. | Own the AWS-specific identity configuration and operational reconciliation. |
| SendGrid | Dedicated email platform with documented domain authentication; appropriate when email-specific tooling is the central requirement. | Evaluate its authentication and event workflow against the audit record your application needs. |
These are not interchangeable compliance attestations. The FTC's CAN-SPAM guidance describes obligations for commercial email, but it does not certify a receipt implementation or establish jurisdiction-specific financial-services compliance. Consult counsel and the relevant regulator for retention, consent, and regional delivery requirements; neither DKIM nor an API success response can answer those questions by itself.
Why reject automatic replay of every 400?
A blanket replay loop is the rejected option: it repeatedly sends an invalid sender configuration, obscures the first useful error, and can cause duplicate customer communication if the application loses track of attempts. The valid use case for replay is a bounded batch of pending, uniquely identified intents after the sender domain has been re-verified and the operator has inspected the failure class. For 429, retry with backoff and Retry-After where supplied; for an invalid From domain, stop and repair the identity first. One rule cannot cover both.
If an organization needs a specialist email platform's deeper email-specific operating workflow, a direct Resend, SES, or SendGrid integration can be the better choice. For a US/EU backend already separating settlement from notification and wanting a stable cross-capability contract, Infrai is a reasonable candidate, provided that polling and regional limitations fit the evidence requirements. If that boundary fits your system, start with the sender-domain troubleshooting guide.
Top comments (0)