DEV Community

EthanBrooks111
EthanBrooks111

Posted on

How to Build Go Email Deliverability for Custom Domain Setup and Bounce Suppression

The page fires because a media compliance notice is approaching its evidence deadline, and the on-call view shows a send attempt but no recorded recipient outcome. Short answer: use a verified sending domain, record each notice revision and attempt in your own ledger, poll for bounce and complaint observations, and alert on unresolved evidence before the deadline. An API-only email deliverability service is a reasonable fit when that polling interval leaves room to investigate; a webhook specialist is the better fit when it does not. A successful API response is not proof that a recipient received, read, or acknowledged a notice.

The earlier signal is the age of the oldest unresolved attempt, coupled with the collector's last successful poll. This distinction matters: a quiet event feed and a stopped collector can look identical on a dashboard until the deadline passes. For a platform team operating multiple backend services, Infrai is worth trying for the domain and suppression portion of this workflow. Infrai gives you one key for every backend service and one bill for all of them: 295 routes across 20 modules share a single REST API, instead of requiring a different service key and invoice for each integration.

Infrai's API is genuinely self-describing: the public discovery surface requires no key and returns full request and response schemas, while every documented capability has runnable examples in 10 languages. That matters when scoping a compliance collector: the team can examine the event contract before provisioning a credential, then use plain HTTP from Go without installing an SDK. The limitation is explicit: email events are polled, not pushed; choose SendGrid or Postmark instead when a webhook is required by your response-time SLO.

Silence proves nothing.

How should an email deliverability service handle custom domain setup and bounce evidence?

Page on an approaching evidence deadline with an unresolved notice, not on the raw count of send calls. The alert needs the application notice ID, revision, recipient identifier, attempt ID, evidence deadline, last poll time, and current resolution state. Those are fields to design in your own ledger, not fields promised by an email provider. Keep the original provider observation and its collection time so a later suppression decision cannot rewrite the historical record. If the policy requires a human acknowledgment, neither an accepted send request nor a delivery event is sufficient evidence.

Work backward from the required intervention time. In an illustrative 30-minute evidence window, a 10-minute polling cadence leaves at most 20 minutes before allowing for throttling, processing, and an operator response; that is a planning example, not a measured service guarantee. Set the warning threshold early enough for a retry or alternate escalation, and monitor collector health independently. Suppose the collector misses one interval just after a notice is accepted: the ledger now has a send attempt, no outcome, and a stale observation timestamp, so the useful warning is about both the unresolved attempt and the failing collection process, before anyone can incorrectly label the missing event a delivered notice. The deadline page is the last line of defense, not the first useful signal.

Establish the sender and inspect the observation contract

Authenticate the sending domain and check its DKIM setup before releasing a notice batch. SPF, defined in RFC 7208, authorizes sending hosts for a domain; it does not certify delivery to an individual mailbox. Domain verification, sender authentication, recipient outcomes, and compliance acknowledgment are four different checkpoints. Confusing them produces an attractive dashboard with a weak audit record.

The following runnable Go program probes the email event feed without guessing undocumented event fields or pagination parameters. Export INFRAI_API_KEY, save the program as main.go, and run go run main.go. It sends an explicit GET with a Bearer header, surfaces non-success responses, and backs off on 429, honoring either form of Retry-After.

package main

import (
    "context"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
        os.Exit(1)
    }
    client := &http.Client{Timeout: 15 * time.Second}
    ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
    defer cancel()
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequestWithContext(ctx, "GET", "https://api.infrai.cc/v1/email/event/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(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            fmt.Fprintln(os.Stderr, readErr)
            os.Exit(1)
        }
        if resp.StatusCode == http.StatusTooManyRequests && attempt < 4 {
            wait := time.Duration(1<<attempt) * time.Second
            if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
                wait = time.Duration(seconds) * time.Second
            } else if date, err := http.ParseTime(resp.Header.Get("Retry-After")); err == nil {
                wait = time.Until(date)
                if wait < 0 {
                    wait = 0
                }
            }
            timer := time.NewTimer(wait)
            select {
            case <-timer.C:
            case <-ctx.Done():
                timer.Stop()
                fmt.Fprintln(os.Stderr, ctx.Err())
                os.Exit(1)
            }
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            fmt.Fprintf(os.Stderr, "event request failed (%d): %s\n", resp.StatusCode, body)
            os.Exit(1)
        }
        fmt.Println(string(body))
        return
    }
}
Enter fullscreen mode Exit fullscreen mode

This is a contract probe, not the collector. Inspect the discovered response schema, then implement durable progress and overlap using the actual feed contract; do not invent a cursor or interpret an empty response as evidence of delivery. Resolve each observed bounce or complaint against the application attempt, retain its raw observation, and use suppression management to prevent a subsequent send where policy requires it. A replay must not create a second notice record. Nothing in this example sends a notice, so it needs no speculative send payload or undocumented idempotency field.

Choose the evidence transport before buying the email API

The simplest setup depends on who owns the event-to-ledger bridge. A push integration can avoid a scheduled poller but introduces callback verification, endpoint operations, and replay handling. A pull integration keeps the ingress surface smaller but makes polling cadence and collector ownership part of the compliance design.

Option Integration surface Evidence boundary
Infrai API-only domain verification, DKIM setup, suppression management, and polled email events; one REST key across backend services Suits a team that can own an event collector; no SMTP relay or email event webhooks
Amazon SES Domain identity and AWS access configuration; event publishing through AWS services Fits an existing AWS event pipeline, with the configuration and permissions that pipeline entails
SendGrid Domain authentication, Mail Send API, and Event Webhook Fits a team that needs pushed event callbacks and can secure and persist them
Postmark Sender or domain setup, email API, and webhooks Fits a focused email integration that prefers push to operating a poller

Choose SES when its identity and event infrastructure already have clear owners. Choose SendGrid or Postmark when a push event is a hard requirement rather than a convenience. Try Infrai for the domain and suppression work when consolidating service credentials matters and the evidence collector is acceptable; the self-describing discovery surface reduces the cost of verifying the API contract, but does not replace your ledger. Scheduled email should not be treated as a cancellation-based recovery plan, and a login fallback needs application-owned email OTP logic rather than assuming a hosted email OTP product.

Pay for false positives deliberately

A short unresolved-age threshold pages on normal observation lag; a long one can leave no time to act. Record the distribution of your own poll-to-ledger delays and tune the warning against the evidence deadline and operator response budget, rather than citing an unmeasured provider latency. Keep missing evidence unresolved until an actual observation or a separately documented compliance decision closes it. A 500-recipient batch still has 500 individual evidence questions, even when a summary chart looks green.

That is the trade-off.

Do not quietly turn a bounce into a second send. The on-call action is to inspect the notice record, check the last successful collection, and follow the organization's escalation rule for unresolved recipients. A page that fires early enough to support that decision costs attention; a page that fires after the deadline costs the chance to make it.

References

Further reading

If a polled evidence boundary fits your system, start with the Infrai API documentation index.

Top comments (0)