Documentation
¶
Index ¶
- Constants
- Variables
- func AgentSuppressionAddedHook(outbox webhookpub.Outbox) identity.AgentSuppressionTxHook
- func InsertReplayDelivery(ctx context.Context, pool *pgxpool.Pool, eventID, webhookID, eventType string, ...) (string, error)
- func NewOutboundDeliverer(sender *outbound.Sender) outboundsend.Deliverer
- func NewOutboundRampGate(store *sendramp.Store, schedule sendramp.Schedule, enabled bool, ...) outboundsend.RampGate
- func NewOutboundSendStore(store *identity.Store, outbox webhookpub.Outbox, ...) outboundsend.Store
- func NormalizeAndValidateLabelList(raw []string, op string) ([]string, error)
- func StripAgentSelfAliases(addrs []string, agentEmail string) []string
- func ValidateDomain(domain string) (string, error)
- func ValidateRecipients(groups ...[]string) error
- func ValidateWebhookURL(rawURL string) error
- type API
- func (a *API) ApproveInboundReviewCore(ctx context.Context, userID string, msg *identity.ReviewMessageMeta) *OutboundError
- func (a *API) ApprovePendingCore(ctx context.Context, userID, messageID, expectedAgentEmail string, ...) (*identity.Message, *OutboundError)
- func (a *API) AuthenticatePrincipal(r *http.Request) (*identity.Principal, error)
- func (a *API) AuthenticateUser(r *http.Request) (*identity.User, error)
- func (a *API) DeleteUserDataCore(ctx context.Context, user *identity.User) (*identity.DeleteUserDataResult, error)
- func (a *API) DeliverOutbound(ctx context.Context, user *identity.User, agent *identity.AgentIdentity, ...) (*OutboundResult, *OutboundError)
- func (a *API) DownloadLimitAllow(key string) (bool, time.Duration, int, int, int)
- func (a *API) ExportUserDataCore(ctx context.Context, userID string) (*identity.UserExport, error)
- func (a *API) HoldForApprovalCore(ctx context.Context, agent *identity.AgentIdentity, req outbound.SendRequest, ...) (*identity.Message, error)
- func (a *API) PollLimitAllow(key string) (bool, time.Duration, int, int, int)
- func (a *API) RegLimitAllow(key string) (bool, time.Duration, int, int, int)
- func (a *API) RegisterRoutes(r *mux.Router)
- func (a *API) RejectInboundReviewCore(ctx context.Context, userID, reason string, msg *identity.ReviewMessageMeta) *OutboundError
- func (a *API) RejectPendingCore(ctx context.Context, userID, messageID, expectedAgentEmail, reason string) (*identity.Message, *OutboundError)
- func (a *API) SendLimitAllow(key string) (bool, time.Duration)
- func (a *API) SendTestCore(ctx context.Context, agent *identity.AgentIdentity) (*OutboundResult, *OutboundError)
- func (a *API) SetAPIURL(u string)
- func (a *API) SetApprovalSigner(s *approvaltoken.Signer)
- func (a *API) SetBillingHookURL(s string)
- func (a *API) SetDomainTeardownHook(h func(ctx context.Context, tx pgx.Tx, domain string) error)
- func (a *API) SetEnforcer(e limits.Enforcer)
- func (a *API) SetIdempotencyStore(s *idempotency.Store)
- func (a *API) SetInternalAPISecret(s string)
- func (a *API) SetManagedUnsubscribeIssuer(i ManagedUnsubscribeIssuer)
- func (a *API) SetMetrics(m telemetry.Metrics)
- func (a *API) SetNotifyEnqueuer(e NotifyEnqueuer)
- func (a *API) SetOAuthProvider(p fosite.OAuth2Provider)
- func (a *API) SetOAuthStorage(s *oauth.Storage)
- func (a *API) SetOIDCAuth(oa *auth.OIDCAuth)
- func (a *API) SetOutboundEnqueuer(e OutboundEnqueuer)
- func (a *API) SetOutbox(o webhookpub.Outbox)
- func (a *API) SetPollRateLimit(perMinute int)
- func (a *API) SetPoolForEvents(p *pgxpool.Pool)
- func (a *API) SetSigner(s *agentauth.Signer)
- func (a *API) SetSubscriberStore(s *webhook.SubscriberStore)
- func (a *API) SetUsageStore(s *usage.Store)
- func (a *API) SetWebSocketHub(h WebSocketHub)
- func (a *API) UnsubscribeLimitAllow(key string) (bool, time.Duration, int, int, int)
- func (a *API) WWWAuthenticateChallenge(r *http.Request) string
- type AcceptIdemCompleter
- type ApproveIdemCompleter
- type ApproveOverrides
- type DNSRecordCheck
- type DeliveryStatusJSON
- type EventView
- type ForwardRequest
- type LimitsCaps
- type LimitsInfo
- type LimitsUsage
- type ManagedUnsubscribeIssuer
- type NotifyEnqueuer
- type OAuthClientPublicMetadata
- type OAuthError
- type OAuthMetadata
- type OAuthRegisterRequest
- type OAuthRegisterResponse
- type OutboundEnqueuer
- type OutboundError
- type OutboundResult
- type ReplayEvent
- type WebSocketHub
Constants ¶
const ( // MaxLabelLength bounds a single label's length to keep the GIN // index entries small and prevent multi-KB tags from being smuggled // into the array. MaxLabelLength = 64 // MaxLabelsPerOp caps the per-request add/remove list size. // Modeled on Gmail's 100/100 cap. Bigger than AgentMail's // (unspecified) but defensive enough that one PATCH can't try // to set thousands of labels. MaxLabelsPerOp = 50 // LabelSystemPrefix marks server-applied system labels. User // writes that try to set a label starting with this prefix are // rejected with 400 — the namespace is reserved so future system // labels (auto-tagged, hitl-approved, …) don't collide with user // tags. LabelSystemPrefix = "e2a:" )
Label validation constants. Public so tests can reference them.
const MaxAddressLen = 320
MaxAddressLen bounds a single email-address-bearing request string — a recipient item in to/cc/bcc, a reply_to override, or an agent email — as the FULL string (optional display name + <addr>), so a multi-megabyte display name can't ride in on an otherwise-valid address. 320 is the classic 64+1+255 addr-spec ceiling with the display-name form held to the same budget. Counted in Unicode code points (runes), NOT bytes, to match the OpenAPI maxLength semantics of the /v1 request schemas (JSON Schema counts code points; Huma validates with utf8.RuneCountInString). The /v1 schemas declare the same value declaratively; this runtime check is the shared backstop for every recipient list that reaches the send path.
Variables ¶
var ( ErrEventNotFound = errEventNotFound ErrEventExpired = errEventExpired )
Exported sentinels for the events lookup.
Functions ¶
func AgentSuppressionAddedHook ¶
func AgentSuppressionAddedHook(outbox webhookpub.Outbox) identity.AgentSuppressionTxHook
AgentSuppressionAddedHook returns the transactional event hook used by both authenticated management and public unsubscribe insertion paths.
func InsertReplayDelivery ¶
func InsertReplayDelivery(ctx context.Context, pool *pgxpool.Pool, eventID, webhookID, eventType string, messageID *string, envelope []byte) (string, error)
InsertReplayDelivery wraps insertReplayDelivery, scheduling one redelivery.
func NewOutboundDeliverer ¶
func NewOutboundDeliverer(sender *outbound.Sender) outboundsend.Deliverer
NewOutboundDeliverer builds the outboundsend.Deliverer adapter for main.go.
func NewOutboundRampGate ¶
func NewOutboundRampGate(store *sendramp.Store, schedule sendramp.Schedule, enabled bool, clocks ...func() time.Time) outboundsend.RampGate
NewOutboundRampGate adapts the durable sendramp store to the worker-owned gate contract. The schedule is snapshotted by Store on the first eligible send; config changes therefore affect only domains that have not armed yet.
func NewOutboundSendStore ¶
func NewOutboundSendStore(store *identity.Store, outbox webhookpub.Outbox, usageTracker usage.UsageTracker) outboundsend.Store
NewOutboundSendStore builds the outboundsend.Store adapter for main.go.
func NormalizeAndValidateLabelList ¶
NormalizeAndValidateLabelList runs each entry through normalizeAndValidateLabel, dedups within the slice, and rejects if the slice is empty after trimming or exceeds the per-op cap. The `op` argument is used only to shape the error message.
func StripAgentSelfAliases ¶
StripAgentSelfAliases is the exported seam over stripAgentSelfAliases so the v1 httpapi reply/forward builders reuse the same self-alias stripping.
func ValidateDomain ¶
validateDomain runs the IDNA "Lookup" profile against a user-supplied domain string. Lookup is the strictest of the four IDNA profiles and is what DNS resolvers themselves apply when converting a name into a wire-format query — it rejects whitespace, control characters, invalid label combinations, and over-length names; converts Unicode IDN to Punycode along the way. We additionally require at least one period because IDNA accepts bare labels like "localhost" which aren't legal as a user-claimable domain.
Returns the ASCII-normalized form on success so callers can persist the canonical wire-format (e.g. "xn--e1afmkfd.xn--p1ai" for "пример.рф") instead of the raw input. ValidateDomain is the exported seam over validateDomain so the v1 httpapi layer reuses the exact IDN/punycode normalization + label-length checks instead of replicating the security-relevant parsing. Returns the normalized (Punycode) form.
func ValidateRecipients ¶
validateRecipients ensures every entry in the joined To/CC/BCC slices is a syntactically valid RFC 5322 address. We use net/mail.ParseAddress rather than a custom regex because it handles bare local@domain, display-name forms ("Bob Smith <bob@x.com>"), quoted local parts, and IDN domains uniformly. Semantic validity (the mailbox actually exists, the user can receive) is checked downstream by the SMTP relay on a per-recipient basis — that's the right layer for best-effort delivery. The API layer's job is only to reject syntactic garbage that could never route through SMTP at all (no @, whitespace, etc.).
Returns the first invalid address found, with a wrapped parser error suitable for surfacing to the caller. Empty slices are not an error here — handlers already enforce "at least one recipient" separately with a more specific message. ValidateRecipients is the exported seam over validateRecipients so the v1 httpapi layer reuses the same RFC 5322 recipient check.
func ValidateWebhookURL ¶
ValidateWebhookImageURL — see ValidateWebhookURL.
ValidateWebhookURL checks that a webhook URL is safe to call (SSRF protection).
Types ¶
type API ¶
type API struct {
// contains filtered or unexported fields
}
func (*API) ApproveInboundReviewCore ¶
func (a *API) ApproveInboundReviewCore(ctx context.Context, userID string, msg *identity.ReviewMessageMeta) *OutboundError
ApproveInboundReviewCore releases a held INBOUND message to its agent's inbox (status pending_review → review_approved, now readable) and fires email.review_approved — the inbound analogue of ApprovePendingCore. There is no SES send and no draft edit: an inbound hold is a screening decision, not a draft.
msg is the dispatch view the account-scoped handler already resolved via GetReviewMessage (ownership + tenant isolation proven there); userID is the reviewing owner. The store transition is a compare-and-set on status='pending_review' AND agent_id, so a concurrent reviewer or the TTL sweep racing this call results in ErrNotPendingReview (409), never a double release.
func (*API) ApprovePendingCore ¶
func (a *API) ApprovePendingCore(ctx context.Context, userID, messageID, expectedAgentEmail string, ovr ApproveOverrides, idemCompleteTx ApproveIdemCompleter) (*identity.Message, *OutboundError)
ApprovePendingCore is the HTTP-free core of the HITL approve→send: it verifies the held message (ownership-scoped + pending + optional expected-agent-email match + domain-verified), then runs ApproveAndSend with the shared send callback (self-send loopback / SES), records usage, and publishes the approved event. Both the legacy handler and the v1 layer call it. expectedAgentEmail (when non-empty) must equal the message's agent's email — mirrors the legacy verifyURLAgentEmail URL guard. On a nil-error return the local loopback or async enqueue has committed; the idempotency key must be Completed (cached), never Released. For queued delivery idemCompleteTx is invoked inside the approve-and-enqueue transaction.
func (*API) AuthenticatePrincipal ¶
AuthenticatePrincipal is the scope-aware seam (Slice 5a): same credential resolution as AuthenticateUser, but returns the full principal (user + scope + bound agent) so the v1 layer can enforce the hard scope ceiling. There is still exactly one place credentials are checked.
func (*API) AuthenticateUser ¶
authenticateUser extracts and validates the bearer credential from the request, returning the owning user.
Dispatch is by token prefix:
- ate2a_ → OAuth access token (fosite-validated via the configured provider). Rejected if missing, revoked, expired, or the provider isn't wired.
- anything else (typically e2a_, but we accept legacy unprefixed keys too) → API key (api_keys table).
If no Authorization header is present, falls back to the session cookie used by the web dashboard. AuthenticateUser is the exported seam over authenticateUser so the v1 httpapi layer (internal/httpapi) reuses the exact same credential- resolution path (API key, OAuth bearer, session cookie) instead of forking a second one. There is one place credentials are checked.
func (*API) DeleteUserDataCore ¶
func (a *API) DeleteUserDataCore(ctx context.Context, user *identity.User) (*identity.DeleteUserDataResult, error)
handleDeleteUserData wipes the authenticated user and every record tied to them, in a single Postgres transaction. Used for the right-of-deletion flow (GDPR Art. 17, CCPA "Do Not Sell or Share").
Requires `?confirm=DELETE` in the query string as a guardrail against accidental clicks. The HTTP method (DELETE) plus this confirmation matches the pattern other destructive APIs use (Stripe's account close, GitHub's repo delete).
DeleteUserDataCore counts OAuth rows for the audit line, best-effort notifies the external billing hook, then runs the cascading delete and merges the counts. HTTP-free; serves DELETE /v1/account?confirm=DELETE.
func (*API) DeliverOutbound ¶
func (a *API) DeliverOutbound(ctx context.Context, user *identity.User, agent *identity.AgentIdentity, req outbound.SendRequest, msgType, replyToEmailMessageID string, referenced *identity.Message, idemCompleteTx AcceptIdemCompleter) (*OutboundResult, *OutboundError)
func (*API) DownloadLimitAllow ¶
DownloadLimitAllow exposes the per-IP attachment-download limiter (key = client ip). The download route is a raw capability-token endpoint outside the Huma rate-limit middleware, so it calls this directly. Returns the IETF snapshot.
func (*API) ExportUserDataCore ¶
handleExportUserData returns a complete dump of the authenticated user's data as JSON. Used for the right-of-access flow (GDPR Art. 15, CCPA equivalent). Output is deterministic-ordered (created_at) so a caller diffing exports across time gets stable results.
ExportUserDataCore assembles the full user-data export (store export + OAuth connections). HTTP-free; serves GET /v1/account/export.
func (*API) HoldForApprovalCore ¶
func (a *API) HoldForApprovalCore(ctx context.Context, agent *identity.AgentIdentity, req outbound.SendRequest, msgType, replyToEmailMessageID string) (*identity.Message, error)
HoldForApprovalCore is the HTTP-free core of the HITL hold: it persists the pending outbound message, fires the async reviewer notification, and publishes the pending-approval event, returning the held message. Both the legacy handler and the v1 httpapi layer call it so there is exactly one hold-and-notify path (api-v1-redesign — outbound extraction).
func (*API) PollLimitAllow ¶
PollLimitAllow exposes the per-user read limiter (key = user id) and RegLimitAllow the per-IP registration limiter (key = client ip), each returning the IETF RateLimit snapshot so the v1 httpapi middleware can stamp RateLimit-Limit/Remaining/Reset. Both share the SAME buckets as the legacy handlers, so a caller is counted once across either surface.
func (*API) RegLimitAllow ¶
func (*API) RegisterRoutes ¶
func (*API) RejectInboundReviewCore ¶
func (a *API) RejectInboundReviewCore(ctx context.Context, userID, reason string, msg *identity.ReviewMessageMeta) *OutboundError
RejectInboundReviewCore drops a held INBOUND message (status pending_review → review_rejected; it stays hidden from the agent, raw payload retained for forensics) and fires email.review_rejected. Compare-and-set semantics as in ApproveInboundReviewCore.
func (*API) RejectPendingCore ¶
func (a *API) RejectPendingCore(ctx context.Context, userID, messageID, expectedAgentEmail, reason string) (*identity.Message, *OutboundError)
RejectPendingCore is the HTTP-free core of HITL reject: optional expected-agent-email match (mirrors the legacy URL guard), then RejectPending + publish. Shared by the legacy handler and the v1 layer.
func (*API) SendLimitAllow ¶
SendLimitAllow exposes the per-agent outbound rate limiter so the v1 httpapi layer shares the *same* token bucket as the legacy handlers (a caller hitting the limit via either path is counted once). key = agent id.
func (*API) SendTestCore ¶
func (a *API) SendTestCore(ctx context.Context, agent *identity.AgentIdentity) (*OutboundResult, *OutboundError)
SendTestCore accepts (or HITL-holds) a platform test email to the agent's own address. HTTP-free; shared by the legacy handler and the v1 layer. The caller has already authed, resolved + owned the agent, domain-verified, rate-limited, and run the message-send cap.
The message is PLATFORM-originated (From/envelope = noreply@<from_domain>) and traverses the real external SMTP → inbound route back to the agent's public address — that round-trip is the endpoint's purpose, so it must never route through the agent self-send loopback. Delivery is queue-first like every other send: a durable msg_ resource + outbound_send job are committed before any provider I/O (status=accepted), and the terminal outcome arrives via GET /v1/messages/{id} and the email.sent / email.failed events.
func (*API) SetAPIURL ¶
SetAPIURL overrides the API/issuer base URL (default: publicURL). Set it when the programmatic API + MCP are served on a different host than the web app — that host becomes the OAuth issuer and the base for the token/ registration/revocation/jwks endpoints, while authorization_endpoint and the login/consent pages stay on publicURL. A no-op for the empty string so callers can pass an unset config value safely.
func (*API) SetApprovalSigner ¶
func (a *API) SetApprovalSigner(s *approvaltoken.Signer)
SetApprovalSigner wires in the magic-link signer after construction so callers (and tests) that don't need HITL magic-link endpoints don't have to know about it. When nil, handleApproveMagicLink / handleRejectMagicLink respond with 404.
func (*API) SetBillingHookURL ¶
SetBillingHookURL wires in the URL of an external billing service's user-event endpoint. When the user deletes their account, the API HMAC-signs a JSON payload and POSTs it there so the billing service can cancel the corresponding Stripe subscription. When empty (the self-host default), no hook fires — appropriate for deployments without a billing service. The same internal_api_secret is reused for the signature.
func (*API) SetDomainTeardownHook ¶
SetDomainTeardownHook wires the per-domain SES deprovision enqueue run in the account-delete transaction (decision 4 / Slice 4). Optional.
func (*API) SetEnforcer ¶
SetEnforcer wires in the resource-limits enforcer. When nil (the default) every check passes — handlers behave as if every user has unlimited capacity. The cmd/e2a runtime always sets it; tests that don't care about limits omit it and continue to work as before.
func (*API) SetIdempotencyStore ¶
func (a *API) SetIdempotencyStore(s *idempotency.Store)
SetIdempotencyStore enables Idempotency-Key processing on the outbound /send and /reply endpoints. When nil (the default) the header is silently ignored — keeps the agent package usable in environments that don't have postgres wired or want to disable the feature. The cmd/e2a runtime always sets it.
func (*API) SetInternalAPISecret ¶
SetInternalAPISecret wires in the shared HMAC secret used to authenticate the /api/internal/limits/invalidate endpoint. When empty (default), that endpoint returns 503 — self-host operators who don't run a billing provisioner never need to configure it.
func (*API) SetManagedUnsubscribeIssuer ¶
func (a *API) SetManagedUnsubscribeIssuer(i ManagedUnsubscribeIssuer)
func (*API) SetMetrics ¶
SetMetrics wires the slice 10 observability backend. Default is telemetry.NoOp; production passes telemetry.NewLog() or a real counter backend.
func (*API) SetNotifyEnqueuer ¶
func (a *API) SetNotifyEnqueuer(e NotifyEnqueuer)
SetNotifyEnqueuer wires the HITL-notification accept-tx enqueuer. When nil, holdForApproval still persists the pending message but enqueues no notification — useful for tests that don't want the SMTP traffic, and for deployments without a configured relay / public URL.
func (*API) SetOAuthProvider ¶
func (a *API) SetOAuthProvider(p fosite.OAuth2Provider)
SetOAuthProvider wires in the fosite-backed OAuth provider. When nil, /oauth2/* endpoints return 404 (matches the SetApprovalSigner / SetNotifyEnqueuer pattern of "optional capability, silently absent when not wired"). Operators who don't want OAuth enabled simply don't call this.
func (*API) SetOAuthStorage ¶
SetOAuthStorage wires in the OAuth storage handle separately from the provider. The consent handler needs Pool() to begin a pgx tx that spans the agent-create (identity pkg) and the auth-code insert (fosite → oauth pkg). Provider-only callers (e.g. /token) don't need it, but it's required for /consent to work; setting one without the other is a misconfiguration the consent handler surfaces as a 503.
func (*API) SetOIDCAuth ¶
SetOIDCAuth wires optional OIDC browser login. NewOIDCAuth returns nil when disabled, so callers may invoke this unconditionally and leave the routes genuinely absent from deployments that do not opt in.
func (*API) SetOutboundEnqueuer ¶
func (a *API) SetOutboundEnqueuer(e OutboundEnqueuer)
SetOutboundEnqueuer wires the mandatory queue-first outbound pipeline. Tests may leave it unset to verify that a miswired process fails closed before provider I/O.
func (*API) SetOutbox ¶
func (a *API) SetOutbox(o webhookpub.Outbox)
SetOutbox wires the slice-4 transactional outbox. Used by the outbound /send handler for the email.sent event (post-side-effect: PublishBestEffortTx). Other trigger sites (HITL) continue to use the legacy publisher; they will migrate in a future slice if/when their handlers gain transactional plumbing.
func (*API) SetPollRateLimit ¶
SetPollRateLimit sets the per-user poll budget to perMinute requests per minute (config: rate_limits.poll_per_minute). Values <= 0 are ignored and keep the built-in default.
func (*API) SetPoolForEvents ¶
SetPoolForEvents wires the raw pgxpool.Pool used by the events handlers. Kept separate from the store so a future refactor can route through a higher-level abstraction without changing the handler signatures.
func (*API) SetSigner ¶
SetSigner wires the RS256 agent-token signer (Slice 5b). Optional — when nil or disabled, /.well-known/jwks.json serves an empty key set and the agent-identity token paths (later sub-slices) report "not enabled".
func (*API) SetSubscriberStore ¶
func (a *API) SetSubscriberStore(s *webhook.SubscriberStore)
SetSubscriberStore wires the subscriber-store dependency after NewAPI. Same optional-setter convention as SetEnforcer / etc.
func (*API) SetUsageStore ¶
SetUsageStore wires in the usage store used by handleGetMyLimits to surface the user's current counts (agents, domains, messages this month, storage bytes) alongside the resolved caps. Separate from the usage.UsageTracker (which is for recording events) so the dashboard read path can stay alive even when usage-tracking is otherwise off.
func (*API) SetWebSocketHub ¶
func (a *API) SetWebSocketHub(h WebSocketHub)
func (*API) UnsubscribeLimitAllow ¶
UnsubscribeLimitAllow exposes the distinct per-IP managed-unsubscribe limiter. Mailbox providers centralize link scanning and one-click requests, so this has a provider-friendly budget and never consumes attachment quota.
func (*API) WWWAuthenticateChallenge ¶
WWWAuthenticateChallenge is the v1-surface seam (internal/httpapi) for the RFC 6750 challenge. The legacy mux had the auth error in hand at the 401 site (writeAuthError); the v1 layer rejects via the canonical envelope and reaches this from a response wrapper that only knows the status was 401, so we re-run the same credential resolution to recover the typed error and classify the challenge identically (expired vs invalid). 401s are rare, so the extra resolution is cheap, and routing it through the one auth path keeps the surfaces from diverging.
type AcceptIdemCompleter ¶
AcceptIdemCompleter completes the idempotency key inside the delivery tx with the exact result the wire will render. External sends cache 202/accepted; terminal local loopback caches 200/sent. nil when the request carries no Idempotency-Key (or no idempotency store is wired).
type ApproveIdemCompleter ¶
ApproveIdemCompleter completes an approval's idempotency key inside the async approve-and-enqueue transaction. It receives the resolved message so the httpapi layer can cache the exact SendResultView (including edited, method, and sent_as) that the live request returns.
type ApproveOverrides ¶
type ApproveOverrides = approveRequest
ApproveOverrides are the optional reviewer edits applied on approve (exported alias of the internal body type so the v1 httpapi layer can build them).
type DNSRecordCheck ¶
checkDomainRecords runs the three per-record probes plus the TXT ownership check. In dev mode, all checks short-circuit to "found" / true so domain verification flows can be exercised without real DNS.
Probe semantics:
- TXT: any TXT record contains the verification token (ownership proof)
- MX: any MX record points at smtpDomain (mail routing)
- SPF: any apex TXT record begins with v=spf1 (case-insensitive) and contains smtpDomain as a substring (case-insensitive). Advisory only — SPF does not gate domain verification.
- DKIM: when dkimSelector + dkimPublicKey are present, looks up "{selector}._domainkey.{domain}" and matches the stored public key — "found" on a match, "mismatch" when a key is published but doesn't match (see classifyDKIM), "missing" when none is present. Domains without a stored keypair report "deferred" — these are pre-migration rows that the next claim would key.
DNSRecordCheck is the exported diagnostic from CheckDomainRecords.
func CheckDomainRecords ¶
func CheckDomainRecords(domain, smtpDomain, verificationToken, dkimSelector, dkimPublicKey string, production bool) DNSRecordCheck
CheckDomainRecords is the exported seam over checkDomainRecords so the v1 httpapi layer reuses the exact DNS-probe logic for domain verification.
type DeliveryStatusJSON ¶
type DeliveryStatusJSON = deliveryStatusJSON
DeliveryStatusJSON is the exported per-event delivery roll-up.
type EventView ¶
type EventView = eventView
EventView is the exported event wire shape (alias of the internal type, so the JSON contract is identical).
func GetEventForUser ¶
func GetEventForUser(ctx context.Context, pool *pgxpool.Pool, userID, eventID string) (*EventView, error)
GetEventForUser wraps the internal getEvent query.
func ListEventsForUser ¶
func ListEventsForUser(ctx context.Context, pool *pgxpool.Pool, userID, eventType, agentID, conversationID, messageID string, since, until *time.Time, cursorCreatedAt time.Time, cursorID string, limit int) ([]EventView, error)
ListEventsForUser wraps the internal listEvents query.
type ForwardRequest ¶
type ForwardRequest struct {
To []string `json:"to"`
CC []string `json:"cc,omitempty"`
BCC []string `json:"bcc,omitempty"`
Body string `json:"text,omitempty"`
HTMLBody string `json:"html,omitempty"`
ConversationID string `json:"conversation_id,omitempty"`
Attachments []outbound.Attachment `json:"attachments,omitempty"`
}
ForwardRequest is the JSON body for /v1/agents/{email}/messages/{id}/forward.
type LimitsCaps ¶
type LimitsInfo ¶
type LimitsInfo struct {
PlanCode string `json:"plan_code"`
Limits LimitsCaps `json:"limits"`
Usage LimitsUsage `json:"usage"`
UpgradeURL string `json:"upgrade_url"`
} // @name LimitsInfo
LimitsInfo is the response body for GET /v1/account. It bundles the user's resolved caps with their current usage so the dashboard renders the "you're using X of Y" surface in one round trip. plan_code and upgrade_url come straight from the limits row (opaque to OSS — written by whatever provisions the row); when no row exists, plan_code is the operator default and upgrade_url is empty.
type LimitsUsage ¶
type ManagedUnsubscribeIssuer ¶
type ManagedUnsubscribeIssuer interface {
Ready() error
Issue(ctx context.Context, userID, agentID, recipient string) (string, error)
}
ManagedUnsubscribeIssuer is deliberately narrow: it receives only the final canonical recipient and returns a stored, absolute public capability URL.
type NotifyEnqueuer ¶
type NotifyEnqueuer interface {
EnqueueNotifyTx(ctx context.Context, tx pgx.Tx, messageID string) (int64, error)
}
NotifyEnqueuer inserts the HITL approval-notification job (QueueNotify) in the hold accept-tx, so the reviewer's email is enqueued atomically with the pending_review row (docs/design/hitl-notify-river.md). Satisfied by *hitlnotify.Jobs. When nil (notifier unconfigured), HoldForApprovalCore persists the hold on the plain path and sends no notification.
type OAuthClientPublicMetadata ¶
type OAuthClientPublicMetadata struct {
ClientID string `json:"client_id"`
ClientName string `json:"client_name"`
RedirectURIs []string `json:"redirect_uris"`
Scopes []string `json:"scopes"`
ClientIDIssuedAt int64 `json:"client_id_issued_at"`
// AccountEligible reports whether the consent screen may offer account
// (workspace-admin) scope for this client: true iff "account" is on the
// client's registered scope ceiling (which DCR writes only when the
// redirect is account-eligible AND the client requested it). Drives the
// consent UI (the Account option is disabled otherwise); fosite re-checks
// granted ⊆ registered at grant time, so this is UX, not the security
// boundary.
AccountEligible bool `json:"account_eligible"`
}
OAuthClientPublicMetadata is the subset of an oauth_clients row we surface to anonymous readers. The consent page (web/) fetches this so it can render the friendly client_name beside the requested scope. No secrets are present in this struct; secret_hash and internal bookkeeping fields are deliberately omitted.
type OAuthError ¶
type OAuthError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
}
OAuthError is the RFC 7591 §3.2.2 / RFC 6749 §5.2 JSON error body. Used for DCR-side errors and direct (non-redirected) authorize errors.
type OAuthMetadata ¶
type OAuthMetadata struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
RegistrationEndpoint string `json:"registration_endpoint"`
RevocationEndpoint string `json:"revocation_endpoint"`
ResponseTypesSupported []string `json:"response_types_supported"`
ResponseModesSupported []string `json:"response_modes_supported"`
GrantTypesSupported []string `json:"grant_types_supported"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported"`
ScopesSupported []string `json:"scopes_supported"`
// JWKSURI points at the public key set agents verify e2a-minted JWTs
// against (Slice 5b). Always present once a signer surface exists; the
// endpoint serves {"keys":[]} when no signing key is configured.
JWKSURI string `json:"jwks_uri,omitempty"`
// AgentAuth is the auth.md agent-identity discovery block (Slice 5b-2).
// Present only when the agent-identity surface is enabled (a signing key is
// configured) — advertising endpoints that 501 would mislead clients.
AgentAuth *agentAuthMetadata `json:"agent_auth,omitempty"`
// RFC 9207 §3 — advertises that authorize responses carry `iss`
// (we emit it manually in writeAuthorizeRedirect since fosite
// v0.49 doesn't ship native RFC 9207 support).
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
}
OAuthMetadata is the RFC 8414 authorization-server metadata document served at /.well-known/oauth-authorization-server. Field names use snake_case per §2 of the RFC. Only the fields e2a actually advertises are present — omitting an OPTIONAL field (introspection_endpoint, jwks_uri, …) signals to clients that the feature isn't supported.
type OAuthRegisterRequest ¶
type OAuthRegisterRequest struct {
ClientName string `json:"client_name"`
RedirectURIs []string `json:"redirect_uris"`
GrantTypes []string `json:"grant_types,omitempty"`
ResponseTypes []string `json:"response_types,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
Scope string `json:"scope,omitempty"`
}
OAuthRegisterRequest is the RFC 7591 §2 client metadata POSTed to /oauth2/register. Unknown fields are tolerated (forward-compat with RFC 7591 extensions) but ignored.
type OAuthRegisterResponse ¶
type OAuthRegisterResponse struct {
ClientID string `json:"client_id"`
ClientIDIssuedAt int64 `json:"client_id_issued_at"`
ClientName string `json:"client_name"`
RedirectURIs []string `json:"redirect_uris"`
GrantTypes []string `json:"grant_types"`
ResponseTypes []string `json:"response_types"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method"`
Scope string `json:"scope"`
}
OAuthRegisterResponse is the RFC 7591 §3.2.1 success envelope. Echoes the metadata the server stored (after defaults applied) plus the assigned client_id and issuance timestamp. No client_secret — public clients only.
type OutboundEnqueuer ¶
type OutboundEnqueuer interface {
EnqueueSendTx(ctx context.Context, tx pgx.Tx, messageID string) (int64, error)
}
OutboundEnqueuer is the accept-tx's handle on the shared River client — it inserts the outbound_send job in the same transaction as the message row. *outboundsend.Jobs satisfies it. Main always injects it; a nil value fails closed before provider I/O.
type OutboundError ¶
type OutboundError struct {
Status int
Code string
Msg string
// 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
}
OutboundError carries an HTTP status + message so both the legacy handler (http.Error) and the v1 httpapi layer (error envelope) render it consistently. Code is the machine-branchable code the v1 layer uses.
func (*OutboundError) Error ¶
func (e *OutboundError) Error() string
type OutboundResult ¶
type OutboundResult struct {
Held bool
PendingMessageID string
ApprovalExpiresAt *time.Time
MessageID string // the e2a msg_ id (GET-able) for every send method
ProviderMessageID string // provider/SES id when sent via smtp
SentAs string // "own_address" | "relay" (decision 4)
Method string // "smtp" | "loopback"
// Status is the send progression the wire maps to `status`. Empty on the
// synchronous path (the caller renders "sent"); "accepted" on the async path
// (durably persisted + queued; the terminal outcome arrives via email.sent /
// email.failed). See async-message-pipeline.md, slice C.
Status string
}
OutboundResult is the HTTP-free outcome of DeliverOutbound.
type ReplayEvent ¶
type ReplayEvent struct {
EventType string
MessageID *string
Envelope []byte
MatchedWebhookIDs []string
}
ReplayEvent is the exported snapshot used to schedule a redelivery.
func LoadReplayEvent ¶
func LoadReplayEvent(ctx context.Context, pool *pgxpool.Pool, userID, eventID string) (*ReplayEvent, error)
LoadReplayEvent wraps loadEventForReplay (ownership-scoped). Returns ErrEventNotFound / ErrEventExpired for the respective cases.