go 45 lines · 1 tab

Email delivery via HTTP provider with context, timeout, and idempotency

Leah Thompson Jan 2026
1 tab
package mail

import (
  "context"
  "bytes"
  "encoding/json"
  "errors"
  "net/http"
  "time"
)

type Payload struct {
  From    string `json:"from"`
  To      string `json:"to"`
  Subject string `json:"subject"`
  Text    string `json:"text"`
}

func Send(ctx context.Context, client *http.Client, endpoint, apiKey, idemKey string, p Payload) error {
  b, err := json.Marshal(p)
  if err != nil {
    return err
  }

  cctx, cancel := context.WithTimeout(ctx, 3*time.Second)
  defer cancel()

  req, err := http.NewRequestWithContext(cctx, http.MethodPost, endpoint, bytes.NewReader(b))
  if err != nil {
    return err
  }
  req.Header.Set("Content-Type", "application/json")
  req.Header.Set("Authorization", "Bearer "+apiKey)
  req.Header.Set("Idempotency-Key", idemKey)

  resp, err := client.Do(req)
  if err != nil {
    return err
  }
  defer resp.Body.Close()
  if resp.StatusCode < 200 || resp.StatusCode >= 300 {
    return errors.New("email provider returned non-2xx")
  }
  return nil
}
1 file · go Explain with highlit

For transactional email, the reliability problem is usually latency and retries, not MIME formatting. I prefer an HTTP email provider because requests are easy to bound with context.WithTimeout and easier to observe than raw SMTP. The code below builds a JSON payload, sets Authorization and an Idempotency-Key, and sends it through a tuned http.Client. The idempotency key is important: if a job retries after a timeout, the provider can dedupe the send instead of delivering duplicates. I also treat non-2xx responses as errors and avoid logging the full email body to prevent leaking PII. In production, this call lives in a background worker and the API request returns quickly, but the same pattern applies: timeouts, explicit headers, and clear error handling keep email from turning into a tail-latency trap.


Related snips

Share this code

Here's the card — post it anywhere.

Email delivery via HTTP provider with context, timeout, and idempotency — share card
Link copied