Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/fixtures/errors/batch_hitl_unsupported.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"error":{"code":"batch_hitl_unsupported","message":"batch send is not available for agents with HITL enabled","request_id":"req_fixture"}}
1 change: 1 addition & 0 deletions api/fixtures/errors/duplicate_recipient.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"error":{"code":"duplicate_recipient","message":"same recipient address appears in more than one batch item","request_id":"req_fixture","details":{"address":"alice@example.com","item_indices":[3,17]}}}
1 change: 1 addition & 0 deletions api/fixtures/errors/too_many_messages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"error":{"code":"too_many_messages","message":"too many messages in batch","request_id":"req_fixture","details":{"max_messages":100,"provided":101}}}
493 changes: 491 additions & 2 deletions api/openapi.yaml

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,16 @@ or the state first); `rate_limited`, `idempotency_in_flight`, and 5xx
| `unauthorized` | 401 | Missing or invalid credentials (REST and the WebSocket handshake). |
| `forbidden` | 403 | Authenticated but not allowed (key scope, cross-tenant access). |
| `blocked_by_policy` | 403 | **Experimental.** The outbound message was blocked by the agent's outbound policy gate. |
| `batch_hitl_unsupported` | 403 | Batch send refused because the agent has HITL enabled (`outbound.gate.action=review` or `outbound.scan.sensitivity != off`). Use single-send per recipient or disable HITL on the agent. |
| **Validation** | | |
| `invalid_request` | 400 / 422 | The canonical input-validation code — malformed (400) or semantically invalid (422). `error.details` carries the per-field list. |
| `invalid_cursor` | 400 | Bad pagination cursor — drop it and re-fetch from the start. |
| `invalid_filter` | 400 | Bad list-filter parameter (messages/conversations/events). |
| `invalid_domain`, `invalid_slug`, `invalid_recipient`, `invalid_attachment`, `invalid_template`, `invalid_event_type`, `invalid_webhook_url`, `invalid_expires_at`, `invalid_scope` | 400 | Field/resource-specific refinements of `invalid_request`. |
| `reserved_domain` | 400 | The domain is reserved by the deployment (e.g. the shared domain). |
| `too_many_recipients` | 400 | Send/reply/forward recipient count over the cap. |
| `too_many_messages` | 400 | Batch-send `messages[]` count over the per-request cap (100). Distinct from `too_many_recipients` which caps recipients within one message. |
| `duplicate_recipient` | 400 | Batch-send: the same recipient address appears in the `to` set of more than one item. Deduplicate the input; e2a does NOT silently drop duplicates. |
| `template_render_failed`, `template_rendered_empty` | 400 | Template send: rendering failed / produced an empty body. |
| `recipient_suppressed` | 422 | A recipient is on the account-wide or exact sending-agent suppression list — un-suppress or drop it. |
| **Not found / gone** | | |
Expand Down Expand Up @@ -200,6 +203,7 @@ every `/v1` operation not listed here is covered by the GA freeze.
| `deleteAgentSuppression` | `DELETE /v1/agents/{email}/suppressions/{address}` | Agent suppressions |
| `deleteTemplate` | `DELETE /v1/templates/{id}` | Templates |
| `getAgentProtection` | `GET /v1/agents/{email}/protection` | Protection config |
| `getBatch` | `GET /v1/batches/{batch_id}` | Batch send |
| `getReview` | `GET /v1/reviews/{id}` | Reviews |
| `getStarterTemplate` | `GET /v1/starter-templates/{alias}` | Starter templates |
| `getTemplate` | `GET /v1/templates/{id}` | Templates |
Expand All @@ -209,6 +213,7 @@ every `/v1` operation not listed here is covered by the GA freeze.
| `listTemplates` | `GET /v1/templates` | Templates |
| `putAgentProtection` | `PUT /v1/agents/{email}/protection` | Protection config |
| `rejectReview` | `POST /v1/reviews/{id}/reject` | Reviews |
| `sendBatch` | `POST /v1/agents/{email}/batches` | Batch send |
| `updateTemplate` | `PATCH /v1/templates/{id}` | Templates |
| `validateTemplate` | `POST /v1/templates/validate` | Templates |

Expand Down
644 changes: 644 additions & 0 deletions docs/design/batch-send.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions internal/agent/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,11 @@ type OutboundError struct {
// Details carries optional code-specific structured context across the
// agent/httpapi boundary. It stays transport-neutral to avoid a package cycle.
Details map[string]any
// RetryAfter is the Retry-After seconds hint for retryable errors
// (429 rate_limited, 503 limits_unavailable). Zero means "no hint";
// the httpapi layer copies non-zero values to both the Retry-After
// header and the details' retry_after_seconds field.
RetryAfter int
}

func (e *OutboundError) Error() string { return e.Msg }
Expand Down
Loading