Documentation
¶
Index ¶
- type Config
- type DatabaseConfig
- type DeliveryFeedbackConfig
- type HTTPConfig
- type InboundConfig
- type LimitsConfig
- type OAuthConfig
- type OIDCConfig
- type OutboundSMTPConfig
- type RateLimitsConfig
- type SMTPConfig
- type SenderIdentityConfig
- type SendingRampConfig
- type SigningConfig
- type TrashConfig
- type WebhookConfig
- type WebhookFanoutConfig
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
SMTP SMTPConfig `yaml:"smtp"`
HTTP HTTPConfig `yaml:"http"`
Database DatabaseConfig `yaml:"database"`
OAuth OAuthConfig `yaml:"oauth"`
OIDC OIDCConfig `yaml:"oidc"`
Signing SigningConfig `yaml:"signing"`
OutboundSMTP OutboundSMTPConfig `yaml:"outbound_smtp"`
Inbound InboundConfig `yaml:"inbound"`
WebhookFanout WebhookFanoutConfig `yaml:"webhook_fanout"`
Webhook WebhookConfig `yaml:"webhook"`
SenderIdentity SenderIdentityConfig `yaml:"sender_identity"`
DeliveryFeedback DeliveryFeedbackConfig `yaml:"delivery_feedback"`
SendingRamp SendingRampConfig `yaml:"sending_ramp"`
Limits LimitsConfig `yaml:"limits"`
RateLimits RateLimitsConfig `yaml:"rate_limits"`
Trash TrashConfig `yaml:"trash"`
Env string `yaml:"env"` // "development" or "production"
// (e.g. "agents.example.com"), users can register agents with just a
// slug and get `<slug>@<shared_domain>` provisioned without DNS
// setup. Empty disables slug registration — every agent must use a
// custom domain that the user owns and verifies. The shared domain
// itself is reserved: it cannot be claimed as a custom domain.
SharedDomain string `yaml:"shared_domain"`
}
func (*Config) IsProduction ¶
func (*Config) Validate ¶
Validate enforces invariants that must hold before the server starts. In production mode the placeholder HMAC secret, an empty secret, and secrets shorter than the hash output length are hard rejected — running with any of these weakens approval tokens and derived encryption keys and approve HITL messages.
type DatabaseConfig ¶
type DatabaseConfig struct {
URL string `yaml:"url"`
}
type DeliveryFeedbackConfig ¶
type DeliveryFeedbackConfig struct {
SESConfigurationSet string `yaml:"ses_configuration_set"`
SNSTopicARNs []string `yaml:"sns_topic_arns"`
}
DeliveryFeedbackConfig controls outbound delivery feedback (decision 9 / Slice 4b). When SESConfigurationSet is set, outbound mail is tagged so SES publishes delivery/bounce/complaint events; SNSTopicARNs is the fail-closed allow-list of SNS topics the public notifications endpoint accepts (empty = reject all). Both empty (the default) disables the feature: no event header, the endpoint rejects everything. Override with E2A_DELIVERY_SES_CONFIGURATION_SET and E2A_DELIVERY_SNS_TOPIC_ARNS (comma-separated).
type HTTPConfig ¶
type HTTPConfig struct {
ListenAddr string `yaml:"listen_addr"`
// PublicURL is the externally visible base URL of the *web app* — the
// domain that serves the dashboard, HITL magic-link pages, and the
// OAuth login/consent UI. Absolute links in notification emails and the
// OAuth authorization_endpoint are built from it. Example:
// "https://e2a.example.com". If empty, features that need absolute URLs
// gracefully degrade.
PublicURL string `yaml:"public_url"`
// APIURL is the externally visible base URL of the *programmatic API* —
// the host the SDKs/MCP target (e.g. "https://api.e2a.dev"). It is the
// OAuth issuer identity and the base for the token/registration/
// revocation/jwks endpoints, so it should match the host the MCP
// resource is served from (RFC 9728: clients expect the issuer to live
// with the API). Defaults to PublicURL when unset, so single-host
// deployments and self-hosters need not set it.
APIURL string `yaml:"api_url"`
}
type InboundConfig ¶
type InboundConfig struct {
Mode string `yaml:"mode"`
}
InboundConfig selects the inbound processing model (inbound-message-pipeline- river.md). Mode="sync" (the default) is the historical path: the SMTP session runs parse/screen/persist/deliver inline before 250. Mode="async" opts into the queue-first River pipeline: the session durably accepts the raw MIME to inbound_intake + enqueues a processing job atomically before 250, and the internal/inboundprocess worker does the processing off the SMTP critical path. Override with E2A_INBOUND_MODE. Any value other than "async" is treated as "sync" (fail-safe to the unchanged path).
type LimitsConfig ¶
type LimitsConfig struct {
PlanCode string `yaml:"plan_code"`
MaxAgents int `yaml:"max_agents"`
MaxDomains int `yaml:"max_domains"`
MaxMessagesMonth int `yaml:"max_messages_month"`
MaxStorageBytes int64 `yaml:"max_storage_bytes"`
// CacheTTLSeconds controls how long resolved Limits are cached
// in-process. The cache covers the account_limits read only; current
// usage counts are always live. Set to 0 to disable caching
// (recommended for tests that mutate account_limits and want
// immediate visibility).
CacheTTLSeconds int `yaml:"cache_ttl_seconds"`
// InternalAPISecret is the shared HMAC secret the external limits
// provisioner (e.g. the hosted billing sidecar) uses to authenticate
// to /api/internal/limits/invalidate. When empty (the self-host
// default), that endpoint returns 503 — no provisioner, no
// invalidation. Must be set to the same value on both ends.
InternalAPISecret string `yaml:"internal_api_secret"`
// BillingHookURL is the URL the OSS server POSTs to when a user
// deletes their account, so the external billing service (e.g.
// the hosted billing sidecar's /api/internal/billing/cancel) can
// cancel the user's Stripe subscription. Empty disables the call
// — appropriate for self-host without billing. The same
// InternalAPISecret signs the POST body.
BillingHookURL string `yaml:"billing_hook_url"`
}
LimitsConfig is the operator-configured fallback applied to any user who does not yet have a row in account_limits. The hosted billing sidecar populates rows for paying customers; self-hosted operators who do not run a billing service rely on these defaults for every user. Defaults below intentionally lean generous so a self-host that never touches the limits subsystem is not accidentally throttled.
Hosted-service operators who want every brand-new signup capped to a "free" shape should set these to the Free-tier numbers — the sidecar will then overwrite them on upgrade.
type OAuthConfig ¶
type OAuthConfig struct {
GoogleClientID string `yaml:"google_client_id"`
GoogleClientSecret string `yaml:"google_client_secret"`
RedirectURL string `yaml:"redirect_url"`
// SigningKey is the PEM-encoded RSA private key (PKCS#1 or PKCS#8) used to
// sign auth.md agent-identity JWTs + access tokens (Slice 5b). The public
// half is published at /.well-known/jwks.json. Empty ⇒ the agent-auth
// surface is disabled (JWKS serves an empty set). Supplied via
// E2A_OAUTH_SIGNING_KEY; never generated or persisted by e2a.
SigningKey string `yaml:"signing_key"`
// SigningKID is the key id advertised in the JWKS and stamped on every
// issued JWT (E2A_OAUTH_SIGNING_KID; default "v1"). Rotation advertises a
// new kid, then retires the old after the longest token TTL.
SigningKID string `yaml:"signing_kid"`
}
type OIDCConfig ¶
type OIDCConfig struct {
// Enabled turns the feature on. Override with E2A_OIDC_ENABLED.
Enabled bool `yaml:"enabled"`
// IssuerURL is the exact expected ID-token issuer and discovery base URL.
IssuerURL string `yaml:"issuer_url"`
// ClientID and ClientSecret identify this confidential e2a web client.
ClientID string `yaml:"client_id"`
ClientSecret string `yaml:"client_secret"`
// RedirectURL is the registered absolute callback URL.
RedirectURL string `yaml:"redirect_url"`
// UserIDClaim names the ID-token claim containing an existing users.id.
UserIDClaim string `yaml:"user_id_claim"`
}
OIDCConfig enables a generic OpenID Connect Authorization Code login for existing e2a users. It is off by default; when disabled no OIDC routes are registered. The provider must include UserIDClaim in its ID token and the claim must name an existing users.id. OIDC login never provisions users.
type OutboundSMTPConfig ¶
type OutboundSMTPConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Username string `yaml:"username"`
Password string `yaml:"password"`
FromDomain string `yaml:"from_domain"`
// RequireTLS fails the send if STARTTLS can't be negotiated, instead
// of silently relaying in cleartext (a network attacker can strip the
// STARTTLS capability from the server's EHLO to force this). Pointer
// so an unset value can default to true in production while staying
// off for dev relays (e.g. Mailpit on :1025 with no TLS). Regardless
// of this flag, PLAIN auth is never sent over a cleartext connection.
RequireTLS *bool `yaml:"require_tls"`
// MessageIDDomain is the domain the upstream provider stamps on the
// Message-ID header of relayed mail (SES: "<region>.amazonses.com").
// SES's SMTP 250 response returns the assigned id BARE — without this
// domain — so the relay appends it to make the captured id match the
// on-wire Message-ID; a mismatch there breaks In-Reply-To/References
// threading on replies to the agent's own outbound. Empty (the default)
// derives it from a standard SES Host (email-smtp.<region>.amazonaws.com);
// set it explicitly for non-standard endpoints (e.g. VPC endpoints).
MessageIDDomain string `yaml:"message_id_domain"`
}
type RateLimitsConfig ¶
type RateLimitsConfig struct {
// PollPerMinute is the shared per-user budget for the authenticated
// read (polling) endpoints: list/get messages, conversations, and
// webhooks. The bucket is keyed per USER and shared by every reader
// the account runs — each agent's polling loop plus the dashboard,
// whose thread view fetches message bodies individually. Size it for
// the whole account, not a single client. Default 240.
PollPerMinute int `yaml:"poll_per_minute"`
}
RateLimitsConfig tunes server-side request rate limits. A zero value means "use the built-in default", so configs that omit the section keep current behavior.
type SMTPConfig ¶
type SenderIdentityConfig ¶
type SenderIdentityConfig struct {
SESRegion string `yaml:"ses_region"`
}
SenderIdentityConfig controls custom-domain sender identity (decision 4 / Slice 4). When SESRegion is set (e.g. "us-east-1"), domain verification registers an SES BYODKIM sending identity and, once verified, outbound mail uses the agent's own address as From. Empty (the default) disables it: sending_status stays "none" and outbound uses the relay From — the fail-closed default for dev/self-host without SES. Override SESRegion with E2A_SENDER_IDENTITY_SES_REGION.
type SendingRampConfig ¶
type SendingRampConfig struct {
Enabled bool `yaml:"enabled"`
StartDaily int `yaml:"start_daily"`
TargetDaily int `yaml:"target_daily"`
RampDays int `yaml:"ramp_days"`
}
SendingRampConfig is an operator-owned safety policy for newly verified custom sender domains. It is intentionally not user-configurable through the public API. Values are snapshotted when a domain first sends, so later config changes do not reshape an in-flight ramp.
type SigningConfig ¶
type SigningConfig struct {
HMACSecret string `yaml:"hmac_secret"`
}
type TrashConfig ¶
type TrashConfig struct {
// RetentionDays is how many days a soft-deleted resource (agent inbox
// or message) stays restorable in the trash before the janitor purges
// it permanently. This is the knob behind the API contract's "30 days
// by default (deployment-configurable)" wording — the default is 30.
// Minimum 1 (a sub-day trash would break the restore promise the
// stable API documents); the server refuses to start on a lower value.
// Override with E2A_TRASH_RETENTION_DAYS.
RetentionDays int `yaml:"retention_days"`
}
TrashConfig tunes the soft-delete (trash) subsystem.
type WebhookConfig ¶
type WebhookConfig struct {
InternalSinkURL string `yaml:"internal_sink_url"`
}
WebhookConfig carries webhook-delivery settings other than the fan-out engine choice. InternalSinkURL, when set, names a single trusted internal sink URL (the e2a-prober's /sink) that is EXEMPT from the production HTTPS-required + SSRF private-IP delivery guards. It must exactly match the probe webhook's registered URL. Safe: it is a server-operator config value (never attacker input), matched by exact string equality, and the probe webhook is created by the privileged prober `seed`, not the public registration API (which rejects http:// + private hosts). Override with E2A_WEBHOOK_INTERNAL_SINK_URL. Empty (the default) disables the exemption.
type WebhookFanoutConfig ¶
type WebhookFanoutConfig struct {
Mode string `yaml:"mode"`
}
WebhookFanoutConfig selects the webhook fan-out execution model (webhook-fanout- river-migration.md). Mode="legacy" (the default) drains webhook_events → webhook_subscriber_deliveries via the in-process webhookpub.OutboxWorker (LISTEN/NOTIFY + poll + SKIP-LOCKED lease). Mode="river" opts into the River pipeline: PublishTx/PublishBestEffortTx enqueue a webhook_fanout job in the event's tx and the webhookpub.FanOutWorker does the match/insert/enqueue off the drain loop. Override with E2A_WEBHOOK_FANOUT_MODE. Any value other than "river" is treated as "legacy" (fail-safe to the unchanged path). Wired in Slice 2; unused under legacy.