config

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Index

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"
	// SharedDomain enables slug-based agent registration. When set
	// (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 Load

func Load(path string) (*Config, error)

func (*Config) IsProduction

func (c *Config) IsProduction() bool

func (*Config) Validate

func (c *Config) Validate() error

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 SMTPConfig struct {
	ListenAddr string `yaml:"listen_addr"`
	Domain     string `yaml:"domain"`
	TLSCert    string `yaml:"tls_cert"`
	TLSKey     string `yaml:"tls_key"`
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL