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
}
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
package api
import (
"net/http"
"runtime/debug"
)
Expose build metadata for debugging deploys
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
default from: 'noreply@example.com'
def welcome_email(user)
@user = user
ActionMailer advanced patterns for transactional emails
package files
import (
"context"
"time"
Presigned S3 upload URLs (AWS SDK v2)
Share this code
Here's the card — post it anywhere.