DEV Community

AlaricCross6851
AlaricCross6851

Posted on

Reliable Contact Routing with Custom-Domain DKIM, Suppression, and Pull-Based Email Events

Short answer: choose the email API whose custom-domain DKIM, suppression lookup, and cursor-based event polling can be proved in a staging incident drill; for a US/EU B2B SaaS contact flow without webhooks, the winning integration is the one that prevents a bad send and lets an operator reconstruct every routing decision.

A feature matrix won't tell you what page fires at 03:00. Start with the failure path: a prospect submits a contact form, the application selects a support queue, and a transactional acknowledgement must leave the authenticated domain without repeatedly mailing an address that has already bounced or complained. If delivery events arrive only by polling, the application also owns the cursor, deduplication, lag alarm, and replay behavior. That ownership is the real integration effort.

This is a bounded design exercise, not a claim about an incident I personally handled. I use a postmortem test because it forces a useful question before procurement: if the acknowledgement is missing and the queue assignment is wrong, can the responder establish what happened from durable records rather than a green dashboard?

Green proves nothing.

What should a US/EU SaaS team verify for custom-domain DKIM, suppression, and email event polling?

Verify the whole control loop, not the send call. Domain authentication must be observable before traffic is enabled; suppression must be checked before each logical send; the provider must expose stable event identifiers and a continuation mechanism that can be polled without webhooks; and the application must retain enough correlation data to connect a form submission, queue decision, provider message identifier, and later delivery state.

DKIM is necessary evidence that a domain authorized a message, but it is not proof that a mailbox accepted or displayed it. Treat DNS publication and provider verification as a deployment dependency. A release gate should refuse to enable the production sender until the expected selector is verified. During an incident, record the selector and sending domain used for the message; otherwise a recent DNS change can leave the responder comparing today's configuration with yesterday's send.

Suppression is a pre-send safety control. The application should normalize the recipient consistently, ask the chosen service whether the address is suppressed, and decline the send when the answer is positive. It should also keep the business outcome separate: the contact request still belongs in a support queue even when the acknowledgement cannot be sent. Conflating those actions turns an email delivery problem into lost customer intent.

Polling needs a harder contract than "list recent events." Ask how ordering works, whether a cursor survives retries, how long events remain available, what rate-limit response is documented, and whether event IDs are stable. I'm not sure what polling interval is right for your queue because that depends on measured arrival lag and the provider's limits; a useful acceptance test is more concrete: pause the worker, resume from the last committed cursor, and prove that every event is applied once at the business layer even when it is fetched more than once.

Commit the cursor.

Reconstruct the page before comparing APIs

Imagine the page says "contact acknowledgements stale" rather than "email is down." The responder needs four timestamps: form accepted, queue selected, send accepted, and terminal delivery event observed. A fifth field, the poll cursor committed after processing, explains whether the gap sits before submission, at the provider boundary, or inside the event worker.

One missing timestamp changes everything.

A useful incident record is append-only and keyed by an internal message ID. It stores the contact ID and region, but it should avoid copying message content or unnecessary personal data into logs. The send attempt records the idempotency key and returned provider message ID. Each fetched event records its provider event ID, type, provider occurrence time, ingestion time, and the cursor committed in the same durable operation. The dashboard can be wrong; this ledger still answers the question.

For the contact-routing scenario, define distinct failure domains. If queue selection fails, retain the form and route it to a review queue. If suppression blocks the acknowledgement, retain the selected queue and mark the mail outcome as intentionally skipped. If the send is accepted but polling falls behind, don't resend the welcome or acknowledgement email merely because a delivery event is absent. Alert on event age and cursor progress instead. A blind resend is how an observability gap becomes duplicate customer mail.

The alert should name the violated invariant: "oldest unprocessed event exceeds the service objective while cursor has not advanced," with the region and worker identity attached. HTTP 429 belongs in the worker's retry telemetry and should cause bounded backoff; HTTP 401 is a credential or configuration fault and should page differently. Those two statuses demand different operator actions, so collapsing both into "API error" wastes the first ten minutes.

Put suppression and idempotency ahead of the send

The preventative path can stay vendor-neutral. The adapter below deliberately exposes business capabilities rather than a vendor URL, which keeps queue routing, suppression policy, and retry rules testable without embedding one provider's object model throughout the service.

package mailflow

import (
    "context"
    "errors"
    "strings"
)

type Sender interface {
    IsSuppressed(ctx context.Context, recipient string) (bool, error)
    Send(ctx context.Context, m Message, idempotencyKey string) (string, error)
}

type Ledger interface {
    WasSubmitted(ctx context.Context, idempotencyKey string) (bool, error)
    RecordSkipped(ctx context.Context, contactID, reason string) error
    RecordSubmitted(ctx context.Context, contactID, messageID string) error
}

type Message struct {
    From string
    To string
    Subject string
    Text string
}

type Contact struct {
    ID string
    Email string
    Queue string
}

func Acknowledge(ctx context.Context, s Sender, l Ledger, c Contact) error {
    recipient := strings.ToLower(strings.TrimSpace(c.Email))
    key := "contact-ack:" + c.ID

    sent, err := l.WasSubmitted(ctx, key)
    if err != nil || sent {
        return err
    }

    suppressed, err := s.IsSuppressed(ctx, recipient)
    if err != nil {
        return err
    }
    if suppressed {
        return l.RecordSkipped(ctx, c.ID, "suppressed")
    }

    messageID, err := s.Send(ctx, Message{
        From:    "support@example.com",
        To:      recipient,
        Subject: "We received your request",
        Text:    "Your request has been routed to our support team.",
    }, key)
    if err != nil {
        return err
    }
    if messageID == "" {
        return errors.New("send accepted without a message identifier")
    }
    return l.RecordSubmitted(ctx, c.ID, messageID)
}
Enter fullscreen mode Exit fullscreen mode

The boundary has an intentional catch: WasSubmitted followed by Send is not atomic. The provider-side idempotency key must make a retry harmless, and the acceptance test must prove that behavior. If a candidate has no idempotent send contract, place a transactional outbox and a single logical-send state machine in front of it; don't pretend a process-local mutex solves a crash between remote acceptance and local persistence.

Keep routing first. The acknowledgement is downstream of the durable contact record, so a mail failure cannot discard or misroute the request. This ordering is less glamorous than a delivery chart, but it protects the actual B2B workflow.

Compare integration effort with an incident drill

Score candidates using evidence from a small implementation, not brochure checkmarks. A shortlist might contain Amazon SES, Postmark, and Resend, but those names do not decide the architecture; apply the same drill to each and retain the results with the decision record. Public documentation is the starting point, while a staging run supplies the operational evidence.

Drill Evidence to retain Reject or redesign when
Authenticate the custom domain DNS records, verified selector, activation timestamp Production sending can start before verification
Submit the same logical mail twice One customer-visible message, stable correlation records Retries can create duplicate mail
Send to a suppressed recipient No send attempt, explicit skipped outcome Suppression is visible only after submission
Stop and restart polling Committed cursor, deduplicated event IDs, measured lag Restart loses events or re-applies side effects
Exercise US and EU paths Region-specific endpoint choice and data-flow record Region behavior is implicit or undocumented
Trigger rate limiting Bounded backoff, jitter, and a lag alert The worker retries tightly or silently stalls

Measure engineering hours for the adapter, domain setup, suppression path, poller, ledger, tests, runbook, and deployment controls. Count ongoing work too: credential rotation, DNS changes, schema changes, event-retention monitoring, and regional configuration reviews. A thin send SDK can still produce a large operational integration when the application must invent cursor recovery and suppression synchronization. Conversely, a broader API surface is not automatically harder if its semantics are explicit and the failure tests are easy to automate.

This comparison has limits. Pull-based events are a poor fit when the product requires near-immediate reactions and the documented polling limits cannot meet that objective; use a webhook-capable design or a message stream in that case, with signature verification, replay protection, and durable ingestion. A managed email API is also not suitable when policy requires full control of mail transfer infrastructure; operate an appropriate mail stack and accept the staffing burden. Stick with an existing provider when it already passes the drill and migration would only exchange familiar failure modes for unfamiliar ones.

SMS does not inherit the email design by analogy. Consent, opt-out handling, sender identity, and carrier rules need a separate review; CTIA's messaging interoperability and compliance material is a relevant US starting point. Don't let a shared SendMessage interface erase channel-specific policy.

The decision rule that survives handoff

Choose the candidate that passes the incident drill with the least application-owned machinery while meeting the required regional and latency boundaries. The decision record should state which team owns DKIM rotation, suppression behavior, poller lag, cursor recovery, and the contact-to-queue ledger, because an unowned control is already a future page.

No provider removes the need for an internal correlation model. The safest implementation keeps the contact request durable, makes each logical acknowledgement idempotent, blocks known suppressed recipients before sending, and treats polled events as replayable observations rather than commands to send again.

Ask what page fires. Then choose.

References

Top comments (0)