-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathconfig.example.yaml
More file actions
313 lines (290 loc) · 17.6 KB
/
Copy pathconfig.example.yaml
File metadata and controls
313 lines (290 loc) · 17.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# Copy to config.yaml and edit for your deployment.
# Secrets should be set via env vars (E2A_*) — see README "Configuration".
env: "development" # Set to "production" to enforce TLS, HTTPS webhooks, and HMAC secret strength checks
# Optional. When set, users can register agents by slug (no DNS setup required)
# and the server provisions <slug>@<shared_domain>. Leave empty on a self-host
# that doesn't operate a shared mail domain — every agent then needs a custom
# domain that the user verifies via DNS. The shared domain itself becomes
# reserved (cannot be claimed as a custom domain).
shared_domain: ""
smtp:
listen_addr: ":2525"
domain: "example.com" # the domain whose MX record points at this relay
# tls_cert: "/path/to/cert.pem"
# tls_key: "/path/to/key.pem"
# CIDRs whose peers may present a PROXY protocol header (e.g. HAProxy in
# front of this listener). Empty = PROXY parsing off. Untrusted peers are
# never parsed — their real IP is always used for SPF.
# Caveat: a trusted peer that connects WITHOUT a header is given up to 5s
# to present one before the SMTP banner is written — so health checks
# against a PROXY-enabled listener must be TCP-connect-only (or tolerate
# the ~5s banner delay), and direct SMTP clients connecting from a trusted
# CIDR see a ~5s greeting delay. Proxied traffic (always has a header) is
# unaffected.
# List only the proxy's own address(es) — ideally a /32. A catch-all like
# "0.0.0.0/0" or "::/0" would let any peer spoof source IPs and is
# rejected at startup.
# proxy_trusted_cidrs: []
http:
listen_addr: ":8080"
public_url: "http://localhost:8080" # externally visible base URL (e.g. https://e2a.example.com); required for HITL magic-link emails
database:
url: "postgres://e2a:e2a@localhost:5433/e2a?sslmode=disable"
oauth:
google_client_id: ""
google_client_secret: ""
redirect_url: "http://localhost:3000/api/auth/callback"
# auth.md agent-identity token signing (Slice 5b). PEM-encoded RSA private
# key (PKCS#1 or PKCS#8); the public half is published at
# /.well-known/jwks.json so agents can verify e2a-minted JWTs. Empty leaves
# the agent-auth surface disabled (JWKS serves {"keys":[]}). Override via
# E2A_OAUTH_SIGNING_KEY. Never generated or persisted by e2a.
signing_key: ""
# kid advertised in the JWKS and stamped on every issued JWT (default "v1").
# Rotation: advertise a new kid, then retire the old after the longest token
# TTL. Override via E2A_OAUTH_SIGNING_KID.
signing_kid: "v1"
# — OpenID Connect login ————————————————————————————————————————————————————
# Optional, off by default. Lets any standards-compliant OIDC provider (such
# as TokenCanopy, WorkOS, or Auth0) sign an existing e2a user in through the
# Authorization Code flow with PKCE, state, and nonce. The provider's ID token
# must contain user_id_claim with an unchanged local users.id. e2a never
# provisions or email-matches a user from OIDC claims.
#
# The provider must register redirect_url for this confidential web client.
# When enabled, startup performs discovery at issuer_url and fails closed if
# discovery is unavailable or invalid. Legacy Google login remains available.
oidc:
enabled: false
issuer_url: "" # exact issuer/discovery base. Override: E2A_OIDC_ISSUER_URL
client_id: "" # registered OIDC client ID. Override: E2A_OIDC_CLIENT_ID
client_secret: "" # keep secret; used only at token endpoint. Override: E2A_OIDC_CLIENT_SECRET
redirect_url: "" # e.g. "https://e2a.example.com/api/auth/oidc/callback". Override: E2A_OIDC_REDIRECT_URL
user_id_claim: "" # TokenCanopy uses "e2a_user_id". Override: E2A_OIDC_USER_ID_CLAIM
# Enable with: E2A_OIDC_ENABLED=true
# — Internal user provisioning ———————————————————————————————————————————————
# Optional, off by default, internal-only (not part of the public /v1 API or
# the OpenAPI spec). When enabled, an external control plane can create e2a
# users idempotently via POST /api/internal/users/provision — keyed by its
# own external_ref — ahead of each user's first sign-in. Requests are
# authenticated by a shared HMAC over the request body
# (X-E2A-Internal-Signature), signed with the provisioning secret. The
# secret is env-only (E2A_PROVISIONING_SECRET), is deliberately separate
# from limits.internal_api_secret so each can be rotated independently, and
# must be set to the same value on both ends. Leave disabled for self-host —
# the endpoint 503s.
provisioning:
enabled: false
# Enable with: E2A_PROVISIONING_ENABLED=true
# Secret (env only, generate with `openssl rand -hex 32`): E2A_PROVISIONING_SECRET
signing:
# Generate with: openssl rand -hex 32
# Production refuses to start with this placeholder or with anything < 32 bytes.
hmac_secret: "change-me-in-production"
outbound_smtp:
# Upstream SMTP relay for outbound mail (HITL approval notifications,
# /api/v1/agents/{email}/test sends, etc.). The defaults below are
# empty (host: "") so the binary will refuse to send rather than
# silently route through a wrong host — set them to one of:
#
# - Local dev: `make docker-up` runs Mailpit on localhost:1025.
# Set host: "localhost", port: 1025, from_domain: "e2a.localhost".
# Captured mail appears at http://localhost:8025. No creds needed.
#
# - Production (e.g. AWS SES): set host: "email-smtp.us-east-1.amazonaws.com",
# port: 587, username/password from your IAM relay credentials,
# from_domain to a domain DKIM-signed by your upstream.
host: ""
port: 587
username: ""
password: ""
from_domain: "relay.example.com"
# Domain the provider stamps on outgoing Message-ID headers (SES:
# "<region>.amazonses.com"). Used to qualify the bare id SES returns in the
# SMTP 250 response so replies thread correctly. Leave empty to derive it
# from a standard SES host (email-smtp.<region>.amazonaws.com); set it
# explicitly only for non-standard endpoints.
# message_id_domain: "us-east-2.amazonses.com"
# Platform notification emails: HITL approval requests, plus webhook health
# alerts (an early warning when a webhook's deliveries start failing, and a
# notice when e2a auto-disables one). Both senders read the settings below.
notifications:
# Sender address for those emails. Optional — when empty, each kind sends
# from its own fixed local part on outbound_smtp.from_domain (approvals@
# and webhooks@, kept distinct so a time-boxed approval stays filterable
# apart from routine webhook mail), so self-hosted deployments need no
# configuration here. SETTING THIS COLLAPSES BOTH INTO ONE IDENTITY.
# If set, it must be an address your upstream is authorized to send as.
# If e2a holds a DKIM key for the address's domain (a domain registered
# with this server), each notification is DKIM-signed in-process for that
# domain; with no stored key it is sent unsigned.
# from_address: "support@send.your-company.example"
#
# Optional Reply-To for those emails. Set it when the sending domain is
# relay-only (no mailbox behind from_address) so replies land in a real
# support inbox — the same From-on-the-relay-domain + Reply-To pattern
# shared-domain agent sends use. Leave unset when from_address is itself
# a real mailbox — replies then follow the sender address either way
# (webhook mail emits no Reply-To; approval mail emits one pointing at
# its own sender, preserving its long-standing behaviour).
# reply_to: "support@your-company.example"
# Outbound delivery is always asynchronous and at-least-once. The send API
# durably persists the message and enqueues a River job in one transaction,
# returning status=accepted. A background worker submits it to the relay and
# records the terminal outcome (email.sent / email.failed webhooks and
# GET /v1/messages/{id}). Postgres-backed River is therefore mandatory.
# Custom-domain sender identity (decision 4 / Slice 4). When ses_region is set,
# verifying a domain registers an AWS SES sending identity via BYODKIM (reusing
# the per-domain DKIM key e2a already generates), and once SES confirms it,
# outbound mail from agents on that domain uses the agent's OWN address as the
# From header (DKIM-aligned → DMARC passes; replies reach the agent directly).
# Leave empty (the default) to disable: every domain's sending_status stays
# "none" and outbound keeps the relay "… via e2a" From — the fail-closed
# default for dev/self-host without SES. Needs AWS credentials in the ambient
# environment (env vars / instance role) with ses:CreateEmailIdentity,
# ses:TagResource (CreateEmailIdentity supplies the required
# e2a-managed=sender-identity-v1 tag),
# ses:PutEmailIdentityDkimSigningAttributes,
# ses:PutEmailIdentityMailFromAttributes, ses:GetEmailIdentity,
# ses:DeleteEmailIdentity, ses:ListEmailIdentities. (Provisioning calls
# both Put operations when refreshing an existing identity — a role missing
# either permission fails closed and River retries.) For least privilege,
# require aws:RequestTag/e2a-managed=sender-identity-v1 on Create/Tag and
# aws:ResourceTag/e2a-managed=sender-identity-v1 on Delete/Put. Before upgrading
# an existing SES-enabled install, follow the audited legacy-tag adoption steps
# in docs/design/sender-identity-mailfrom.md; untagged identities are deliberately
# treated as foreign and are never updated or deleted.
# Override with E2A_SENDER_IDENTITY_SES_REGION.
sender_identity:
ses_region: "" # e.g. "us-east-1"
# Two-phase blue/green rollout switch for the versioned sender-identity job
# lanes. Phase 1: deploy the new release with this true — it produces the
# LEGACY job kinds (consumable by the previous release, so rollback strands
# no teardown work) while consuming both lanes. This is job-lane
# compatibility only: before a pre-ownership binary overlaps, follow the
# design's tag-conditioned IAM setup and sender-identity mutation freeze.
# Phase 2: once this release is the stable rollback target, flip to false
# (config-only deploy) to switch producers to the versioned v2 lane.
# Single-instance deployments have no overlap and can leave this false.
legacy_job_compat: false
# Operator-managed recipient-volume ramp for newly verified custom sender
# domains. Disabled by default for self-hosts. When enabled, the schedule is
# snapshotted on a domain's first eligible external send. A UTC day advances
# only after provider-accepted recipient volume reaches 50% of that day's cap;
# the higher limit starts the following UTC day. Existing verified domains are
# persistently exempt; operator reset procedures are in docs/runbooks/sending-ramp.md.
# This policy is visible read-only in the domain API and cannot be disabled by
# end users. start_daily must be at least 50, the API's per-message recipient
# maximum, so one accepted message can never be stranded permanently.
sending_ramp:
enabled: false
start_daily: 50
target_daily: 2000
ramp_days: 30
# Outbound delivery feedback (decision 9 / Slice 4b). When ses_configuration_set
# is set, outbound mail is tagged with X-SES-CONFIGURATION-SET so SES publishes
# delivery/bounce/complaint events to an SNS topic; e2a's public endpoint
# POST /webhooks/ses consumes them (SNS signature verified,
# fail-closed) to drive messages.delivery_status, fire
# email.delivered/bounced/complained, and auto-suppress hard-bounced/complained
# recipients. sns_topic_arns is the allow-list of SNS topics that endpoint
# accepts — EMPTY = reject everything (so the endpoint is inert until ops wires
# the topic). Both empty (the default) disables delivery feedback entirely.
# Override with E2A_DELIVERY_SES_CONFIGURATION_SET and E2A_DELIVERY_SNS_TOPIC_ARNS
# (comma-separated). Setup: create an SES configuration set with an SNS event
# destination (delivery+bounce+complaint), then subscribe e2a's endpoint to that
# topic and list its ARN here.
delivery_feedback:
ses_configuration_set: "" # e.g. "e2a-delivery"
sns_topic_arns: [] # e.g. ["arn:aws:sns:us-east-2:123456789012:e2a-ses-events"]
# Per-user resource caps applied when a user has no row in the
# account_limits table. Hosted-service deployments overwrite these per
# user via an external provisioner (the e2a-billing sidecar, an admin
# tool, manual SQL). Self-host operators who do not run any provisioner
# rely solely on these defaults — values below are intentionally high so
# the limits subsystem is effectively off until you opt in.
#
# To convert a self-host into a paywall-aware install, set these to your
# "free" shape and write the upgraded caps into account_limits from
# whatever billing system you run.
limits:
plan_code: "default"
max_agents: 1000000
max_domains: 1000000
max_messages_month: 1000000000
max_storage_bytes: 1125899906842624 # 1 PiB — effectively unbounded
cache_ttl_seconds: 60
# Shared HMAC secret used to authenticate /api/internal/limits/invalidate.
# Set this on both the e2a server and the external limits provisioner
# (e.g. a hosted-service billing sidecar). Leave empty for self-host
# without a provisioner — the endpoint will 503. Generate with:
# openssl rand -hex 32. Override with E2A_INTERNAL_API_SECRET.
internal_api_secret: ""
# URL the OSS server POSTs (HMAC-signed) when a user deletes their
# account, so an external billing service can cancel their
# subscription before the cascade drops the user row. Empty
# disables the call (self-host without billing). Example for a
# docker compose deployment alongside the hosted billing sidecar:
# billing_hook_url: "http://billing:9000/api/internal/billing/cancel"
billing_hook_url: ""
# Per-user budget shared by authenticated message, conversation, and webhook
# reads across every agent client and dashboard session on the account. The
# default allows 240 requests per minute; tune this for the combined workload,
# not for one polling client. Rate limits are in-memory and apply per process.
rate_limits:
poll_per_minute: 240
# — Trash (soft delete) ———————————————————————————————————————————————————————
# How many days a soft-deleted resource (agent inbox or message) stays
# restorable in the trash before it is purged permanently. The public API
# documents "30 days by default (deployment-configurable)" — this is the
# knob. Minimum 1; live message data is otherwise retained indefinitely.
# Override with
# E2A_TRASH_RETENTION_DAYS.
trash:
retention_days: 30
# — Metrics (Prometheus) ——————————————————————————————————————————————————————
# Off by default. When enabled, the server swaps its telemetry backend from
# structured log lines to Prometheus and binds a SEPARATE listener serving
# GET /metrics — deliberately never mounted on the public API handler. The
# default bind is loopback-only; point your Prometheus scraper (or the
# e2a-prober host) at it over a private network of your choosing. Metric
# catalog, aggregation guidance, and SLO targets: docs/observability.md.
# Override with E2A_METRICS_ENABLED / E2A_METRICS_LISTEN_ADDR /
# E2A_METRICS_BUILD.
metrics:
enabled: false
listen_addr: "127.0.0.1:9091"
build: "unknown" # release/image tag; attached to every sample
# — Outbound footer ———————————————————————————————————————————————————————————
# Optional footer appended to all SMTP-egress outbound mail at composition
# time (inside the DKIM-signed body, above the managed-unsubscribe line, and
# counted against the composed-size ceiling). Fully inert when enabled: false
# (the default). Not applied to self-send loopback delivery or to non-standard
# account classes (internal/system/demo). Useful for hosted branding or
# self-host AI-disclosure notices.
#
# Per-account gating reads account_limits.outbound_footer_enabled: an account
# WITH a row gets the footer only when that column is true (written by
# whatever provisions the row); default_enabled covers accounts with NO row.
# text is appended to the plain-text part after a blank line + the RFC 3676
# signature separator ("-- " with a trailing space, so clients trim it on reply);
# html is an operator-trusted fragment appended verbatim to the HTML part when
# the message has one. Both empty = no-op. Override the master switch with
# E2A_OUTBOUND_FOOTER_ENABLED.
# outbound_footer:
# enabled: false # master switch
# default_enabled: false # applies to accounts with NO account_limits row
# text: "" # plain-text footer
# html: "" # HTML fragment appended to the HTML part
# — Content screening (piguard) ——————————————————————————————————————————————
# The Gemini LLM-as-detector layer is enabled by setting the environment
# variable GEMINI_API_KEY (or GOOGLE_API_KEY) to a Google AI Studio key.
# When the variable is absent the Gemini detector is silently skipped and
# the dependency-free heuristics detector runs alone. It only screens INBOUND
# mail (internal/relay); outbound agent-mail screening is heuristics-only.
#
# Obtain a key: https://aistudio.google.com/apikey
# Model default: gemini-3.1-flash-lite (change via GEMINI_EVAL_MODEL env var)
# Kill-switch: set E2A_GEMINI_DETECTOR_ENABLED=false to disable Gemini even when
# a key is configured (e.g. to isolate whether Gemini or heuristics drove a given
# block/review outcome, or to roll back without removing the credential).