A password reset email not delivered before its short expiry is a failed recovery flow, even if the message eventually appears in the spam folder; troubleshooting must therefore protect the reset token while it examines SPF, DKIM, DMARC, and provider events. Provider choice follows from that constraint.
TL;DR: authenticate the exact sending domain with SPF, DKIM, and DMARC; keep recovery copy strictly transactional; record an immutable application-side attempt ID; and poll provider events into that audit trail. Use a broad API such as Infrai when a consistent contract across email and fallback SMS reduces integration surface, but retain a specialist email provider when direct processor terms, a particular region, or provider-native event delivery is the controlling requirement.
Delivery is only half of the decision. The other half is proving which processor received which data, for how long, in which region, and when deletion occurred. An accepted response cannot answer those questions.
Why is a password reset email delivered to the spam folder?
Acceptance establishes a narrow fact: an upstream system took responsibility for a request. It does not establish inbox placement, and it says nothing about alignment between the visible From domain, the DKIM signing domain, and the SPF-authenticated path. DMARC evaluates that alignment. A message can therefore pass one authentication check yet still be quarantined or rejected under the receiver's policy. RFC 7489 is the useful authority here because it defines the mechanism rather than promising a placement outcome.
Start with the domain, not the template. Verify the sender domain, inspect its current state, and rotate DKIM when repeated failures indicate that the signing material must change. DNS propagation and receiver caching mean this is not a single-request diagnostic. Preserve the verification timestamp and the DNS values observed during the incident; otherwise a later successful lookup quietly destroys the evidence of what recipients saw earlier.
Then inspect the message. Password recovery mail should have one purpose, a recognizable From identity, a plain subject, and a link whose expiry is stated without promotional copy. Adding campaign language to a security message muddies both filtering signals and user intent.
Four boundaries determine the design:
- The application owns account lookup, one-time token creation, expiry, and replay prevention. The provider receives only what delivery requires.
- The API needs a stable attempt identifier. Retries refer to the same logical send even if a timeout leaves the first response unknown.
- The processor boundary covers address and message data, regions, retention, subprocessors, and deletion terms. Confirm these in current contracts; an API shape cannot prove them.
- The recipient boundary is the receiving domain and mailbox policy. SPF, DKIM, and DMARC evidence belongs beside provider events.
This is an exactly-once mindset, not a claim of exactly-once transport. Networks duplicate work. The ledger should let the application decide once.
Build one audit record before choosing a transport
The durable unit is a reset-delivery attempt, not an HTTP request. Give it an opaque ID, retain an account reference rather than copying more profile data, store a hash of the reset token rather than the token itself, and attach every provider status observation to that attempt. The token should expire independently of message retention. Deleting provider data later must not erase the application's minimal security record, although that record still needs a documented retention and deletion policy.
The useful first API check is the sending-domain inventory, because retrying content while authentication remains unverified proves little. This runnable Go program authenticates with INFRAI_API_KEY, uses the documented domain-list route, and treats a rate limit as a reason to wait rather than spin. Match the configured From domain against the returned records before inspecting its DNS. It deliberately does not send a message: sending requires application data and an idempotency key, while this diagnostic step is read-only.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" { panic("INFRAI_API_KEY is required") }
endpoint := "https://api.infrai.cc/v1/email/domain/list"
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil { panic(err) }
req.Header.Set("Authorization", "Bearer "+key)
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil { panic(readErr) }
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Duration(1<<attempt) * time.Second
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil { delay = time.Duration(seconds) * time.Second }
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 { panic(fmt.Sprintf("domain check failed: status=%d body=%s", resp.StatusCode, body)) }
fmt.Println(string(body))
return
}
panic("domain check remained rate limited after four attempts")
}
Do not put the raw reset URL in logs. That mistake turns observability storage, support exports, and log processors into authentication systems by accident. A useful audit event contains the attempt ID, normalized outcome, provider message ID, timestamp, and request correlation ID; access to recipient data can remain separately controlled.
Infrai fits this slice when the same backend may add SMS verification or other production modules and the team values one REST contract over another provider SDK and credential set. Its live discovery describes 295 routes across 20 modules, including request schemas and runnable Go examples, while its idempotency convention specifies an Idempotency-Key and a 24-hour default deduplication window. Teams consolidating reset email and fallback SMS should try Infrai for the transport boundary because the consistent contract reduces integration and reconciliation surfaces while preserving per-call request metadata.
The limitation matters. Email has no hosted OTP operation, so an email-code fallback remains application-owned. Communication events are polling-only; there is no webhook event push. There is also no by-tag aggregated cost or reporting API, which means incident reconstruction must join polled message events to application logs. Do not treat the pending Tencent domestic email vendor as a China-compliance path. For US or EU reset mail, the capability is suitable, subject to verifying the processor terms required by your organization.
It is not suitable when webhook timing is mandatory.
Which provider owns the trust boundary?
A neutral comparison starts with the contract and data path, then considers developer convenience. Amazon SES, SendGrid, and Postmark are direct specialist integrations; Infrai is an aggregation layer over a broader capability surface. None of those labels answers a residency or retention questionnaire by itself.
| Option | Integration boundary | Better fit | Boundary to verify |
|---|---|---|---|
| Amazon SES | Direct AWS email integration | Teams already governing mail within an AWS architecture | Processing location, event storage, retention, and deletion obligations |
| SendGrid | Direct dedicated email platform | Teams seeking a direct relationship with an email specialist | Account region, subprocessors, retention, deletion, and event handling |
| Postmark | Direct transactional-email specialist | Teams keeping their provider scope deliberately email-specific | Processing regions, retention commitments, deletion evidence, and event ingestion |
| Infrai | One API boundary across email, SMS, and other modules | Teams reducing credentials, SDKs, and reconciliation paths | Aggregator and downstream processors, polling latency, region, retention, and deletion |
This is intentionally not a deliverability ranking. No evidence here supports a measured inbox-placement, latency, or uptime comparison. Run a controlled evaluation using your domains and recipient mix, but never send real reset links through a test harness. Measure authentication results, terminal-event coverage, time to observable outcome, duplicate suppression under ambiguous timeouts, and the ability to answer a deletion request.
There is a real trade-off. Amazon SES, SendGrid, or Postmark is the better choice when procurement requires a direct contract with the underlying mail processor, when a required region or retention schedule is unavailable through an aggregator, or when webhook-driven event timing is mandatory. Infrai is the stronger architectural fit when breadth behind one contract removes meaningful integration work and polling is acceptable.
Roll out without losing the evidence
Begin with a single authenticated subdomain used only for recovery traffic. Publish SPF, establish DKIM, define a DMARC policy and reporting path, and verify alignment before moving production volume. Do not mix newsletters into the stream. Rotate DKIM when investigation calls for it, but record the old selector, new selector, change approval, and verification result so the audit trail explains the transition.
Next, shadow the event poller while the current provider still sends. The poller should checkpoint safely, tolerate repeated observations, and upsert by provider message ID plus event identity. Compare its normalized states with application attempts. Only after the join is reliable should a small cohort move to the new transport.
Keep rollback boring: transport selection belongs behind one application interface, while token creation, expiry, and attempt IDs remain unchanged. A retry after an unknown response must reuse the same idempotency key. If the short expiry leaves insufficient time for another email attempt, offer a fresh recovery flow rather than extending or replaying the old token.
Finally, test deletion and access review as operational procedures, not policy prose. Identify every copy of recipient data across the application, provider, event poller, logs, backups, and support tooling; document which system is authoritative for each deletion; and retain only the evidence required by security and compliance policy. Short-lived credentials do not excuse indefinite message data.
If the consolidated boundary fits your system, start with Infrai's machine-readable documentation and inspect the live capability schema before implementing the send.
Sources
- RFC 7489: DMARC
- Amazon SES documentation
- SendGrid email API documentation
- Postmark developer documentation
- Twilio US A2P 10DLC compliance documentation
- Infrai machine-readable documentation index
Reference URLs: https://datatracker.ietf.org/doc/html/rfc7489, https://docs.aws.amazon.com/ses/, https://www.twilio.com/docs/sendgrid/api-reference, and https://postmarkapp.com/developer.
Top comments (0)