> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paubox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Email client

> Full reference for the Paubox Go SDK Email API client: sending messages, batch sending, delivery tracking, dynamic templates, and error handling.

## Creating a client

```go theme={null}
client, err := paubox.New(apiKey, username, ...opts)
```

`paubox.New` accepts optional functional options:

| Option                     | Description                                    |
| -------------------------- | ---------------------------------------------- |
| `paubox.WithBaseURL(url)`  | Override the API base URL (useful for testing) |
| `paubox.WithHTTPClient(c)` | Supply a custom `*http.Client`                 |
| `paubox.WithTimeout(d)`    | Set a per-request timeout (default: 30 s)      |
| `paubox.WithRetry(cfg)`    | Configure retry behavior (see below)           |
| `paubox.WithUserAgent(s)`  | Append a string to the `User-Agent` header     |

### Retry configuration

By default the client retries `GET` requests up to 3 times on `429` and `5xx` responses, with exponential backoff between 500 ms and 30 s.

```go theme={null}
client, err := paubox.New(apiKey, username,
    paubox.WithRetry(paubox.RetryConfig{
        MaxAttempts:        4,
        WaitMin:            200 * time.Millisecond,
        WaitMax:            5 * time.Second,
        RetryNonIdempotent: false, // set true to also retry POST/PATCH
    }),
)
```

## Send a message

```go theme={null}
resp, err := client.SendMessage(ctx, &paubox.SendMessageRequest{
    Message:              msg,
    OverrideOpenTracking: true,  // optional
})
```

### Message fields

```go theme={null}
paubox.Message{
    Recipients: []string{"alice@example.com"},          // required; To recipients
    CC:         []string{"manager@example.com"},         // optional
    BCC:        []string{"audit@example.com"},           // optional
    Headers: paubox.MessageHeaders{
        From:             "sender@yourdomain.com",       // required
        Subject:          "Your results are ready",      // required
        ReplyTo:          "support@yourdomain.com",      // optional
        ListUnsubscribe:  "<mailto:unsub@yourdomain.com>", // optional
    },
    Content: paubox.MessageContent{
        PlainText: paubox.Ptr("Plain text body."),       // at least one required
        HTML:      paubox.Ptr("<p>HTML body.</p>"),
    },
    Attachments: []paubox.Attachment{
        {
            FileName:    "report.pdf",
            ContentType: "application/pdf",
            Content:     base64EncodedBytes, // base64-encoded file content
        },
    },
    AllowNonTLS:             false, // default false — enforce TLS
    ForceSecureNotification: paubox.Ptr(true), // optional
}
```

The `paubox.Ptr[T]` helper creates a pointer to any value, which is required for optional fields typed as `*T`:

```go theme={null}
paubox.Ptr("some string")  // returns *string
paubox.Ptr(true)           // returns *bool
```

### Response

```go theme={null}
type SendMessageResponse struct {
    SourceTrackingID string
    Data             SendMessageData
}
```

## Send a batch

Send up to 50 messages in a single API call. Each message gets its own tracking ID.

```go theme={null}
resp, err := client.SendBatch(ctx, &paubox.SendBatchRequest{
    Messages: []paubox.Message{
        {
            Recipients: []string{"alice@example.com"},
            Headers:    paubox.MessageHeaders{From: "f@yourdomain.com", Subject: "Hi Alice"},
            Content:    paubox.MessageContent{PlainText: paubox.Ptr("Hello Alice")},
        },
        {
            Recipients: []string{"bob@example.com"},
            Headers:    paubox.MessageHeaders{From: "f@yourdomain.com", Subject: "Hi Bob"},
            Content:    paubox.MessageContent{PlainText: paubox.Ptr("Hello Bob")},
        },
    },
})
if err != nil {
    log.Fatal(err)
}

for i, msg := range resp.Messages {
    fmt.Printf("[%d] tracking ID: %s\n", i, msg.SourceTrackingID)
}
```

## Check delivery status

```go theme={null}
disp, err := client.GetEmailDisposition(ctx, sourceTrackingID)
```

`disp.Data.Message.MessageDeliveries` is a slice of per-recipient records:

```go theme={null}
for _, d := range disp.Data.Message.MessageDeliveries {
    fmt.Printf("%s → %s (opened: %d)\n",
        d.Recipient,
        d.Status.DeliveryStatus,
        d.OpenedCount,
    )
}
```

Common `DeliveryStatus` values: `delivered`, `opened`, `failed`, `pending`.

## Dynamic templates

Templates use [Handlebars](https://handlebarsjs.com) syntax (`{{variable_name}}`).

### Create a template

```go theme={null}
tmpl, err := client.CreateTemplate(ctx, &paubox.CreateTemplateRequest{
    Name: "appointment-confirmation",
    Body: []byte(`<p>Hello {{first_name}}, your appointment is on {{date}} at {{time}}.</p>`),
})
```

### List templates

```go theme={null}
list, err := client.ListTemplates(ctx)
for _, t := range list.Templates {
    fmt.Println(t.ID, t.Name)
}
```

### Get a template

```go theme={null}
tmpl, err := client.GetTemplate(ctx, "template-id")
```

### Update a template

```go theme={null}
tmpl, err := client.UpdateTemplate(ctx, "template-id", &paubox.UpdateTemplateRequest{
    Name: "appointment-confirmation-v2",
    Body: []byte(`<p>Updated body for {{first_name}}.</p>`),
})
```

### Delete a template

```go theme={null}
_, err = client.DeleteTemplate(ctx, "template-id")
```

### Send a templated message

```go theme={null}
resp, err := client.SendTemplatedMessage(ctx, &paubox.SendTemplatedMessageRequest{
    TemplateName: "appointment-confirmation",
    TemplateValues: map[string]any{
        "first_name": "Jane",
        "date":       "2024-03-15",
        "time":       "2:00 PM",
    },
    Message: paubox.TemplatedMessage{
        Recipients: []string{"jane@example.com"},
        Headers: paubox.MessageHeaders{
            From:    "appointments@yourclinic.com",
            Subject: "Your appointment is confirmed",
        },
    },
})
```

## Error handling

All methods return a typed `error`. Use `errors.As` to inspect details and `errors.Is` to match sentinels:

```go theme={null}
resp, err := client.SendMessage(ctx, req)
if err != nil {
    // Match a specific condition
    if errors.Is(err, paubox.ErrUnauthorized) {
        log.Fatal("invalid API key or username")
    }
    if errors.Is(err, paubox.ErrRateLimit) {
        log.Fatal("rate limited — back off and retry")
    }

    // Inspect the full error
    var apiErr *paubox.PauboxError
    if errors.As(err, &apiErr) {
        fmt.Printf("HTTP %d: %s — %s (request ID: %s)\n",
            apiErr.StatusCode, apiErr.Title, apiErr.Details, apiErr.RequestID)
    }

    log.Fatal(err)
}
```

### Sentinel errors

| Sentinel                 | HTTP status |
| ------------------------ | ----------- |
| `paubox.ErrBadRequest`   | 400         |
| `paubox.ErrUnauthorized` | 401         |
| `paubox.ErrForbidden`    | 403         |
| `paubox.ErrNotFound`     | 404         |
| `paubox.ErrRateLimit`    | 429         |
| `paubox.ErrServerError`  | 5xx         |
