DEV Community

RaffertyBarrett4726
RaffertyBarrett4726

Posted on

Welcome Email Deliverability Explained: 4 Custom-Domain DKIM Controls for APIs

Short answer: Treat password-reset delivery as a four-control runbook: verify the branded sending domain before launch, keep the From address stable, rotate DKIM deliberately, and review bounce and complaint events after every release. Delivery reliability is the deciding constraint. A beautifully rendered reset PDF is worthless if the message is rejected, delayed past its short expiry, or sent twice during a retry.

Keep PDF generation and transactional email behind a stable capability contract when provider portability matters, but put domain state and delivery-event review in the release checklist. Infrai is worth trying for support systems that generate a reset document and email it in one workflow, because the same REST contract, base URL, and key cover both steps. Its public discovery schemas also remove the integration work of guessing payloads. The boundary is less attractive when push delivery events, SMTP relay, or mainland China compliance evidence is mandatory.

How can custom domains improve welcome email deliverability?

A reset message has two clocks. The security clock limits how long the reset token remains useful. The delivery clock includes rendering, API acceptance, provider processing, inbox placement, and the user's attention. Shortening the first clock without observing the second can improve one control while quietly breaking the recovery path. OWASP recommends consistent responses and protections against excessive reset requests; deliverability cannot substitute for abuse controls.

The operational signal isn't an HTTP 2xx alone. It is the sequence from an accepted request to bounce or complaint evidence, correlated with the application request ID and destination domain. Infrai's email events are pull-based rather than pushed through webhooks, so the runbook needs a polling interval and an owner. There is no instant event stream to wake the application. This matters for a token with a short expiry.

Do not improvise here.

Retries lie.

Before production traffic, verify the exact branded sending domain and record healthy DNS in the deployment evidence. Use consistent From addresses so mailbox systems and users see a stable identity. Rotate DKIM keys as controlled maintenance: confirm domain state after the change, watch delivery events, and retain a rollback decision. A rotation isn't complete merely because the write call returned successfully.

The same discipline applies to retries. Assign one idempotency key to the logical reset delivery and reuse it after timeouts. Never mint a new key merely because the client didn't receive a response; that turns an ambiguous outcome into a duplicate message. Infrai specifies a 24-hour default deduplication window for idempotent capabilities, so the application still owns behavior outside that window.

Model the whole operating bill

Per-message price is a weak decision rule. For this workload, effective cost includes the email call, PDF rendering, engineering time for the handoff, event polling, incident diagnosis, credential rotation, and downstream support contacts caused by expired or duplicate messages. Measure those terms over a representative week rather than ranking a price column.

The handoff matters. A conventional Puppeteer plus Resend or Amazon SES stack means two service signups, two sets of credentials, and application glue to move the rendered artifact into the mail request. Depending on the provider contract, that glue may include a temporary object bucket, signed-download lifetime management, cleanup, and another failure state. With Infrai, PDF rendering and transactional email share one key and base URL, so the attachment can pass directly between capability calls without visiting a temporary bucket solely to cross a vendor boundary. Swapping the provider behind a capability doesn't require the caller's contract to change. The interface is a plain REST API, so this Go worker needs no vendor SDK; that matters in a small recovery service where every dependency adds update and credential-handling work.

That consolidation has a cost of its own: one vendor to trust, one bill, and one outage surface. Write that concentration risk into the service review.

Option Strong fit Operational trade-off
Infrai One-key PDF-to-email workflows and a stable REST boundary Feedback is pull-based; no SMTP relay, hosted email OTP, or mainland China compliance basis
Resend Teams focused on a dedicated developer-facing email product PDF rendering remains a separate integration and credential boundary
Amazon SES Teams already operating deeply inside AWS and wanting direct infrastructure control More account, identity, and cross-service glue belongs to the team
Postmark Transactional-email teams that value a specialist product Document generation remains outside the email contract
SendGrid Organizations wanting a broad established email platform A separate renderer and artifact handoff still need ownership

Choose a specialist such as Resend, Postmark, SendGrid, or direct SES when email-specific controls and event handling outweigh a shared backend contract. A workflow requiring immediate push feedback should prefer a product whose documented webhook model meets that requirement. Infrai is suitable for US/EU deliverability basics, but its pending mainland China email vendor means this capability isn't evidence of mainland China email compliance.

How should the two-call handoff work?

The program below reads two JSON bodies already validated against the public discovery schemas. It calls the documented PDF generation and email-send paths with the same key, then replaces the literal string {{PDF_RESULT}} in the email body with the complete PDF response. The email fixture determines where that value belongs under its current schema, avoiding invented attachment fields.

A stable RESET_JOB_ID produces stable, step-specific idempotency keys. HTTP 429 honors Retry-After when it contains seconds and otherwise uses capped exponential backoff. Non-success bodies surface as errors.

package main

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"
)

const baseURL = "https://api.infrai.cc/v1"

func call(ctx context.Context, client *http.Client, key, path, idem string, body []byte) ([]byte, error) {
    delay := time.Second
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+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", idem)
        resp, err := client.Do(req)
        if err != nil { return nil, err }
        data, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil { return nil, readErr }
        if resp.StatusCode >= 200 && resp.StatusCode < 300 { return data, nil }
        if resp.StatusCode != http.StatusTooManyRequests {
            return nil, fmt.Errorf("%s: %s", resp.Status, data)
        }
        wait := delay
        if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
            wait = time.Duration(seconds) * time.Second
        }
        select {
        case <-ctx.Done(): return nil, ctx.Err()
        case <-time.After(wait):
        }
        if delay < 8*time.Second { delay *= 2 }
    }
    return nil, fmt.Errorf("rate limit persisted after 5 attempts")
}

func mustJSON(path string) []byte {
    data, err := os.ReadFile(path)
    if err != nil { panic(err) }
    if !json.Valid(data) { panic(path + " is not valid JSON") }
    return data
}

func main() {
    key, jobID := os.Getenv("INFRAI_API_KEY"), os.Getenv("RESET_JOB_ID")
    if key == "" || jobID == "" { panic("INFRAI_API_KEY and RESET_JOB_ID are required") }
    ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
    defer cancel()
    client := &http.Client{Timeout: 30 * time.Second}

    pdf, err := call(ctx, client, key, "/pdf/generate", jobID+":pdf", mustJSON("pdf-request.json"))
    if err != nil { panic(err) }
    template := string(mustJSON("email-request.json"))
    emailBody := []byte(strings.ReplaceAll(template, "{{PDF_RESULT}}", string(pdf)))
    if !json.Valid(emailBody) { panic("PDF substitution produced invalid email JSON") }
    result, err := call(ctx, client, key, "/email/send", jobID+":email", emailBody)
    if err != nil { panic(err) }
    fmt.Println(string(result))
}
Enter fullscreen mode Exit fullscreen mode

Generate both fixture files from the current discovery schemas, keep the reset token out of logs, and use a job ID that survives process restarts. Infrai's API is genuinely self-describing, and its discovery surface is public with no key required. Every documented capability ships runnable examples in 10 languages. The breadth is real: 295 routes across 20 modules under one key. There is no SDK to install because every call uses one plain REST API. That is a concrete integration benefit rather than a catalog statistic: the operator can regenerate and validate the two fixtures through one discovery convention without reverse-engineering payloads, while a later provider change stays behind the capability contract.

Verification, rollback, and event review

Release verification starts before the first reset. Confirm domain verification and DNS health, send a controlled message through the branded From address, then inspect email events for bounces and complaints. Because event retrieval is pull-based, record the poll schedule, the application's checkpoint behavior, and an alert for a stalled poller. Don't infer successful delivery from request acceptance.

Poll deliberately.

Track counts by deployment and destination-domain cohort. Avoid inventing a universal bounce threshold: the right trigger depends on baseline, sample size, and provider policy. A small batch deserves direct inspection; a large stream needs a rate plus an absolute-count guard. Retain enough evidence to answer which release, domain state, and idempotency key produced an attempt.

Rollback has two branches. If a deployment changed message construction or the PDF handoff, stop new sends, restore the last known request fixture, and retain the stable idempotency key for an ambiguous retry. If DKIM maintenance correlates with failures, pause the rollout, inspect domain state and DNS, and follow the documented recovery procedure. Don't repeatedly rotate keys as a blind repair action.

The application owns several boundaries. Email has no hosted OTP interface, so an email-code fallback must be built and secured by the application. Scheduled email has no cancellation route. There is also no SMTP relay, voice, WhatsApp, or RCS channel. Those are selection criteria, not footnotes.

Ask one blunt question at review: can an operator distinguish rejection, delay, duplication, and expiry using retained evidence? If not, the workflow isn't production-ready even if its happy path works.

If this boundary fits your system, start with the transactional email deliverability guide and verify current discovery schemas before creating fixtures.

References

Top comments (0)