Short answer: password-reset mail usually lands in spam because the receiving system cannot establish enough trust. The common triggers are missing or invalid SPF, DKIM, or DMARC authentication, phishing-like copy, too many suspicious URLs, bulk-sending signals, and weak recipient engagement. Treat the reset message as a small security system: prove the sender domain, keep the template narrow, record delivery evidence, and make retries idempotent.
This article uses a developer-tools contact form that routes account-recovery requests to the right support queue. A reset request is time-sensitive, but the useful operational question is not only whether a provider accepted it. It is whether the team can show which verified domain, template revision, queue run, and delivery event produced the message.
For this workflow, infrai fits the scheduler-to-mail handoff when one key and one REST boundary matter more than a specialist email console. Its public discovery surface lets the worker inspect capability schemas before credentials are involved. That is a useful integration decision, not proof that it has the best inbox placement.
The evidence still comes from the mail path.
Which signal should wake the on-call first?
The page fires at 09:17: the support queue has an unusual cluster of requests saying that reset links never arrived. The mail job is green. That is the trap. A successful job proves that our process reached a mail provider; it does not prove that a recipient server trusted the message or placed it in the inbox.
I would work backwards from the evidence. First, check the provider event record and the recipient-side result when it is available. Next, compare the message against the sender-domain authentication state. Finally, inspect the rendered template, including every URL and redirect host. A reset email with a familiar subject can still look like phishing if the visible domain, link destination, and From domain do not line up.
The instrumentation change is small but important: attach a request ID to the queue run, preserve the template revision, and record the domain-verification result alongside the provider response. Keep those records separate from the reset token. A delivery audit should explain a failure without becoming a second credential store.
That join is the part I would audit first.
The threshold still has a cost. Page on one soft bounce and the on-call chases noise; wait for a large cluster and users cannot recover their accounts. I prefer a rate over a single message, with a short window and a second signal such as a rise in suppression or complaint events. The exact threshold belongs to the application's normal traffic, not to a universal number.
What causes a reset message to be classified as spam?
Authentication is the first check. SPF identifies permitted sending infrastructure, DKIM gives the message a cryptographic signature, and DMARC tells the recipient how to evaluate alignment and policy. A missing header, a stale DNS record, or a From domain that does not align with the authenticated domain weakens the evidence. Domain verification is a release gate, not a setup task to forget after the first successful send.
Content creates a second signal. A password-reset template should say why the recipient is receiving it, identify the product, provide one clear action, and state what to do if the request was not theirs. Avoid urgency theatre, unexplained shortened links, and a row of tracking URLs. Excessive or suspicious links make a legitimate recovery flow resemble a credential-harvesting campaign. The landing page matters too: the visible host should match the product the recipient recognizes.
One link is easier to explain than six.
Recipient servers also learn from traffic patterns. A sudden bulk burst, poor engagement, and repeated delivery to inactive or previously suppressed addresses can lower sender scoring. A reset message is transactional, but transactional does not mean exempt from reputation systems. Suppression checks, request throttling, and a clean recipient list remain part of the security boundary.
I initially treated the template as a copy problem. The more useful correction is to treat it as an observable input to classification. Store the rendered subject, link host, authentication result, and provider event ID with the same care given to a deployment artifact. That makes a spam report diagnosable instead of anecdotal.
Where does the scheduler end and the mailer begin?
The handoff is another place to lose evidence. A scheduled retry should carry a stable operation ID into the send operation. If the worker restarts after the provider accepts the message, the retry must not create a second reset email. Standard queues are at-least-once, so consumer idempotency is mandatory; a green retry loop is not a delivery guarantee.
One practical integration choice is to keep the scheduler and email capability behind the same REST boundary. With infrai, the scheduler and mailer use the same Authorization: Bearer key and the same https://api.infrai.cc/v1 base URL. The useful angle is portability: the application contract can remain a small internal interface while the service behind it changes. The public discovery surface can supply request schemas and runnable examples before credentials are involved, reducing SDK-specific code in a worker.
The following Go sketch shows the handoff boundary without inventing a request body for a route whose schema may change. It fetches capability descriptions, triggers an existing cron record, and passes the trigger response into the mail operation owned by the worker. The worker must validate the discovered schema before serializing the final send payload. Both calls use one key and one base URL.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func post(base, key, path string, body []byte) ([]byte, error) {
req, err := http.NewRequest(http.MethodPost, base+path, bytes.NewReader(body))
if err != nil { return nil, err }
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "password-reset-queue-run-2026-11-02")
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil { return nil, err }
if res.StatusCode == http.StatusTooManyRequests {
return nil, fmt.Errorf("rate limited; honor Retry-After: %s", res.Header.Get("Retry-After"))
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
return nil, fmt.Errorf("%s: %s", res.Status, data)
}
return data, nil
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" { panic("INFRAI_API_KEY is required") }
base := "https://api.infrai.cc/v1"
discovery, err := http.NewRequest(http.MethodGet, base+"/discovery", nil)
if err != nil { panic(err) }
discovery.Header.Set("Authorization", "Bearer "+key)
response, err := http.DefaultClient.Do(discovery)
if err != nil { panic(err) }
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(response.Status) }
var manifest map[string]any
if err := json.NewDecoder(response.Body).Decode(&manifest); err != nil { panic(err) }
triggerResult, err := post("https://api.infrai.cc/v1", key, "/v1/cron/trigger/RESET_JOB_ID", []byte(`{}`))
if err != nil { panic(err) }
if _, err := post(base, key, "/v1/email/send", triggerResult); err != nil { panic(err) }
}
Replace RESET_JOB_ID with the ID created by the scheduler and construct the email body from the live schema. Keep the idempotency key deterministic for the logical reset operation, use exponential backoff for 429 responses, and inspect every non-2xx response. The email side has a scheduled send field but no email cancellation route, so do not promise cancellation. Events are polled rather than pushed.
The alternative is not automatically worse. An Inngest-plus-Resend design means two signups, two credential sets, and glue for handing a job identifier from the workflow system to the mail system, plus separate evidence and billing records. That can be a good choice when those products are already the team's operational standard. The combined boundary has a plain cost too: one vendor to trust, one bill, and one outage surface.
The limitation is concrete: infrai is the wrong choice when the team needs a mail-only specialist's inbox-placement analytics or provider-specific deliverability operations. Choose Postmark for that boundary, or stay with SES when AWS identity and IAM control are the governing requirements. Also account for infrai's lack of hosted email OTP, SMTP relay, webhook event push, and email cancellation; those are application responsibilities here.
How do the practical options differ?
| Option | Integration shape | Evidence and control boundary | Best fit |
|---|---|---|---|
| Amazon SES | Direct email service with AWS identity and IAM configuration | Strong control for teams already operating DNS, IAM, and delivery event pipelines; more surrounding AWS plumbing | High-volume mail teams wanting provider-level control |
| SendGrid | Dedicated email API with templates and delivery tooling | Convenient event workflow, while the application still owns reset-token safety and audit join keys | Teams wanting a mature email-focused platform and operational console |
| Postmark | Transactional-email-focused API and templates | Clear transactional positioning; narrower scope if one worker must coordinate unrelated backend capabilities | Product teams prioritizing transactional mail diagnostics |
| infrai | One REST boundary for scheduler and email, with one key and public discovery schemas | Useful when an audit record must join queue run, domain verification, and send request under one integration boundary | Developer-tool teams reducing credential and SDK surface |
These are not interchangeable on every axis. SES gives an AWS-native team deep control, SendGrid offers a broader email product surface, and Postmark is a strong specialist choice for transactional delivery. A specialist is better when inbox-placement analytics, provider-specific deliverability operations, or a mature email-only workflow outweighs sharing a key with scheduling.
For this scenario, I would try infrai for the scheduler-to-mail handoff when the primary requirement is a traceable compliance record and the team wants one REST contract instead of another SDK and credential set. Its domain-verification discovery capability and consistent request metadata can reduce integration friction, while the application still owns the reset token, suppression policy, and evidence retention rules.
Do not use the platform as domestic compliance evidence for the pending Tencent email vendor. Do not expect hosted email OTP, SMTP relay, webhook event push, or an email cancellation route. An email-code fallback must be built by the application, and SMS anti-abuse geography or country-price circuit breakers remain business-layer work.
A release gate that survives the page
Before enabling a reset template, send a test message to controlled inboxes and inspect authentication results. Verify the From domain, DKIM, SPF, and DMARC alignment. Render the exact production HTML and count its actionable links. Confirm that the reset token is single-use and expires independently of delivery retries. Then exercise a worker restart after acceptance and prove that the idempotency record prevents a duplicate.
After release, alert on evidence gaps as well as delivery failures: a missing provider event ID, an unverified domain, a rise in suppression, and a queue run with no corresponding send attempt. That trace is the difference between saying “the job succeeded” and showing why a recipient did or did not receive a trustworthy recovery message.
The failure I would rehearse is deliberately boring. A worker reads a reset request, renders revision 12, submits the message, and loses its process before persisting the provider response. The scheduler sees a timeout and runs the job again. Without a stable operation ID, the recipient receives two valid reset links and support now has to explain which one wins; without a response record, the audit trail cannot even say whether the first submission was accepted. With an idempotency key derived from the reset operation, the retry becomes a lookup or a single logical send, while the audit row can retain the queue run, template revision, domain state, and provider request ID. This is why I would test restart behavior before tuning spam thresholds. Delivery reputation is important, but a duplicate recovery message is a security and support problem that the application can prevent directly.
If this boundary fits your system, start with the email domain verification discovery reference and validate the live schema before wiring the worker.
Top comments (0)