PROJECT SCOPE · MUSTHCORP/AGENT-VAULT

Email Gateway inside Agent Vault.
One scoped path for our internal tools and our AI agents.

This document scopes the Email Gateway feature inside Agent Vault: an in-binary email gateway that gives every internal tool, and every AI agent running alongside them, one allowlisted, audited path for transactional mail. It runs as its own Agent Vault instance dedicated to email, with an instance-level setting that hides the feature entirely on any vault deployment that doesn't enable it. Everything below is built fresh inside the fork's custom-feature area — SMTP ingress, the gateway HTTP surface, the canonical mapping layer, the MailerQ proxy, the CloudAMQP outbox, and the event consumers. Auth, audit, the UI shell, and the agent proxy come from Agent Vault itself.

The one-paragraph version

Email Gateway is a feature inside Agent Vault. Internal tools and AI agents both call one HTTP endpoint to send mail. The vault authenticates each caller (SMTP credentials, API key, or both depending on path), enforces per-tenant rules (allowed sender domains, validation, rate limiting), maps source-specific payloads from Sendy, Odoo, and similar services into the canonical MailerQ envelope, then either proxies to MailerQ or publishes to a CloudAMQP outbox queue. Delivery success and failure events flow back through dedicated queues into an email events processor. An instance-level setting lets any other vault deployment keep the feature off so it doesn't appear in the proxy or the UI. Auth, sessions, credentials, audit, alerts, and the operator UI shell come from Agent Vault. Everything else — SMTP ingress, the gateway HTTP surface, the policy and validation layer, the canonical mapping, the MailerQ proxy, the CloudAMQP outbox, the event consumers, the new schema, and the instance toggle — is new code in the fork's custom-feature area.

01 Why this is in scope

Four pressures converge here. Internal tools each ship their own email plumbing, which makes outages and deliverability everyone's problem. AI agents are coming online next to those tools and need a controlled send path. A standalone Musth-Mail-Gateway service would mean rebuilding auth, credentials, and audit that Agent Vault already provides. Email runs on its own dedicated instance, with a clean off-switch on every other deployment.

Cost

One control plane, not two

One vault binary, one deploy story, one set of operators. Every internal tool and every agent uses the same allowlist, the same audit log, the same alerting. Two perimeters becomes one.

Risk

Deliverability + agent send-loops

Today, every tool talks to its own SMTP setup, so a misconfigured sender or a runaway agent can land us on a blocklist. Centralising the send path lets us enforce sender domains, rate limits, and content rules before mail leaves the perimeter.

Reuse

Agent Vault already covers 80% of the parts

Auth, sessions, credentials, encrypted config, audit, alerts, the operator UI shell, and the agent proxy are all shipped by Agent Vault. The email gateway plugs straight into those primitives. A separate Musth-Mail-Gateway service would mean duplicating every one of them, plus a second deploy, a second on-call, and a second auth perimeter. That reuse is the core of why this lives inside the vault binary.

Isolation

One dedicated instance, opt-in elsewhere

A separate Agent Vault instance is scoped to email so credential ops, bounce traffic, and the inbound SMTP port don't share blast radius with our credential vaults. Other instances stay clean: an instance setting hides the feature from the proxy and the UI entirely.

02 What each audience sees

Three audiences, three experiences. The primary user is internal tooling; AI agents share the same path; operators run it from the existing vault UI.

Internal tool

Sendy, Odoo, and the rest

  • Authenticates over SMTP, API key, or both depending on the path.
  • Submits its native payload — the gateway normalises it into the MailerQ envelope.
  • Sender domain checked against the caller's allowed_sender_domains.
  • Logs and events traceable back to the originating request.
AI agent

Same path, tighter leash

Agents authenticate through the same gateway with their own API key issued from the vault, scoped to send and (optionally) MailerQ ops. Approval thresholds and rate ceilings live alongside the existing agent policy in the vault UI. Nothing custom on the agent side.

Operator / owner

One UI, more tabs

The existing Agent Vault operator UI grows new tabs for send log, allowed sender domains, API keys, MailerQ proxy operations, and CloudAMQP queue health. Login, sidebar, header, audit, and alerts are unchanged.

03 The shape of the build

A single new package inside the vault binary, deployed as its own Agent Vault instance. Other instances ship the same binary with the feature switched off. The full technical walkthrough lives on the build guide; this is the scope-level view.

1
Request arrives
A service sends through SMTP or API.
2
Authenticate
SMTP credentials, API key, or the allowed method for that path. Backed by the vault's user and key store.
3
Validate + check domain
Required fields present, sender domain matched against allowed_sender_domains.
4
Map payload
Source-specific fields normalised into the canonical MailerQ envelope.
5
Dispatch + events
Proxy to MailerQ or publish to CloudAMQP outbox. Success / failure queues feed the events processor.
Lives in the fork

Custom code under internal/custom/email/ and web/src/custom/email/. Zero edits to upstream files in this scope.

All new code, custom area

SMTP ingress, gateway HTTP surface, mapping, MailerQ proxy, CloudAMQP outbox, event consumers, and the new schema all live under internal/custom/email/ and web/src/custom/email/.

In-binary, single deploy

One Agent Vault binary, deployed as a dedicated email instance. No separate service to operate or upgrade.

Off by default everywhere else

Instance flag AGENT_VAULT_CUSTOM_EMAILGATEWAY_ENABLED=false on every other deployment. Routes 404, UI tabs hidden, proxy passes through.

04 What comes from Agent Vault, what we build

Everything on the left is already shipped by the Agent Vault product and used as-is. Everything on the right is new code in the fork's custom-feature area for this scope.

From Agent Vault
  • Auth and session handling.
  • User and API key storage primitives.
  • Encrypted config and secret storage.
  • Audit log.
  • Alerting framework.
  • Operator UI shell (sidebar, header, navigation, tables, forms).
  • Agent proxy and the existing approvals surface.
  • Instance settings framework for custom-feature flags.
  • Database connection and migration pipeline.
Built in this scope
  • SMTP ingress component.
  • HTTP gateway endpoints (send + map paths).
  • Per-path auth selector: SMTP, API key, or both.
  • API key scope enforcement for MailerQ and CloudAMQP operations.
  • Request validation against the canonical message format.
  • Allowed sender domain enforcement at runtime.
  • Canonical mapping layer producing the MailerQ JSON envelope.
  • IP pool resolution from the new mailerq_ip_pools table when ips is absent.
  • MailerQ REST proxy with an allowlisted operation surface.
  • CloudAMQP outbox publisher with retry and failure handling.
  • Success queue consumer.
  • Failure queue consumer feeding an email events processor.
  • Correlation of queue events back to the originating request.
  • New tables: smtp_relay_users, smtp_relay_logs, smtp_relay_api_keys, mailerq_ip_pools.
  • Instance-level enable and live-send flags.
  • Environment configuration for MailerQ, CloudAMQP, and queue names.

05 Milestones

Sized S / M / L by code surface and unknowns, not calendar weeks.

Now · M

Foundation

Custom feature package scaffolded under internal/custom/email/ and web/src/custom/email/. New tables created: smtp_relay_users, smtp_relay_logs, smtp_relay_api_keys, mailerq_ip_pools. UI shell tabs hidden behind AGENT_VAULT_CUSTOM_EMAILGATEWAY_ENABLED. No live traffic.

Next · L

Core send path

API key auth wired in. Canonical message format validation. Allowed sender domain enforcement in runtime. Mapping layer for Sendy and Odoo payloads. MailerQ proxy with allowlisted operations. CloudAMQP outbox publisher. End-to-end through MailerQ in shadow mode behind _LIVE_SEND_ENABLED=false.

Then · M

Events + correlation

Success and failure queue consumers. Email events processor service. Correlation id end-to-end from request through to delivered / bounced. Operator UI: send log, mapping inspector, event timeline.

06 Canonical MailerQ message format

The mapping layer must produce a message in this structure. Field names match the MailerQ JSON envelope format. Three groups: top-level, tracking, and MIME.

Top-level
FieldTypeRequiredNotes
recipientStringRequiredEnvelope RCPT TO address.
envelopeStringOptionalEnvelope MAIL FROM. Defaults to from address.
priorityIntegerOptionalHigher = higher priority. Default resolved from the vault's default_priority setting if absent.
ipsArrayOptionalSending IP addresses. If absent, resolve via mailerq_ip_pools using the from-address domain.
tagsArrayOptionalString labels for filtering and reporting.
campaign_idStringOptionalCampaign identifier for tracking.
Tracking (optional object)
FieldTypeNotes
tracking.userIdString
tracking.endpointIdString
tracking.contextString
tracking.visibleBoolean
MIME object
FieldTypeRequiredNotes
mime.toStringRequiredSame as recipient.
mime.from.addressStringRequiredFrom header address.
mime.from.nameStringOptionalDefaults to from address.
mime.replyto.nameStringOptionalDefaults to from name.
mime.replyto.addressStringOptionalDefaults to from address.
mime.subjectStringOptional
mime.textStringConditionalRequired if no HTML content block.
mime.contentArrayConditionalRequired if no text. Each block: type: "html", content field.
MIME headers (all optional)
Header
mime.headers.List-Unsubscribe
mime.headers.List-Unsubscribe-Post
mime.headers.Feedback-ID
Mapping rules the layer must apply
  • If priority is absent, resolve from the vault's default_priority setting (no hard-coded fallback in the mapping layer).
  • If envelope is absent, set it to the from address.
  • If ips is absent, resolve the IP pool from mailerq_ip_pools using the domain extracted from the from address.
  • If mime.from.name is absent, use the from-address string.
  • If mime.replyto is absent, set name and address to match mime.from.
  • If both text and the HTML content block are absent, reject the request.

Per-vault settings (tunables, not hard-coded)

No defaults are baked into the mapping layer or the runtime. Every tunable below is configured per vault (or per setup, where it makes sense) and resolved at request time from the vault that the authenticated caller belongs to.

Message defaults
  • default_priority
  • default_envelope_strategy
  • default_tags
  • default_campaign_id
Sending rules
  • allowed_sender_domains
  • ip_pool_strategy
  • live_send_enabled
  • inbound_smtp_enabled
Rate + approvals
  • send_rate_limit
  • burst_ceiling
  • approve_over_rate_threshold
  • agent_send_requires_approval
Where it lives

Per-vault settings persist in the vault's own settings store. The mapping layer and runtime always read the configured value for the authenticated caller's vault — never a literal in code. Empty / unset values fall through to the instance-level default declared in environment configuration, not to a hard-coded constant.

Infra config (env-only, not per-vault)

These are infrastructure connection details, not behaviour knobs. They live in environment variables or deployment secrets on the email instance — never in the database, never in the per-vault settings UI.

MailerQ
  • MailerQ base URL.
  • MailerQ credentials.
CloudAMQP
  • Connection details.
  • Exchange / routing key settings.
Queues
  • Outbox queue name.
  • Success queue name.
  • Failure queue name.
Split rule

Only the items above stay in env. Instance enable flag is AGENT_VAULT_CUSTOM_EMAILGATEWAY_ENABLED

Everything that controls send behaviour — priority defaults, allowed sender domains, IP pool strategy, rate limits, approval thresholds, auth mode, live-send toggle — lives in the per-vault settings UI above, not here. Env values only act as the instance-level fallback when a vault setting is unset.

07 Risks and mitigations

None are blockers. Each has a mitigation built into the plan.

Deliverability

Bad sender, blocked IP

Sender domains enforced against allowed_sender_domains. IP pools resolved per from-domain through mailerq_ip_pools. Bounce traffic isolated on the dedicated email instance.

Agents

Runaway send loops

Per-key rate limits enforced before publish. Approval thresholds reuse the existing agent policy surface. Failure queue backlog triggers operator alert.

Isolation

Email blast radius into credential vaults

Dedicated Agent Vault instance for email. Instance setting hides the feature entirely on every other deployment. Inbound SMTP behind a sub-flag.

Upstream

Conflict with upstream Infisical

Zero edits to upstream files in this scope. All new code lives under internal/custom/ and web/src/custom/. Upstream syncs continue to flow through the existing release-to-staging workflow.

Broker

CloudAMQP behaviour under load

Outbox publish has retry and failure handling defined. Queue connectivity surfaced through gateway health checks. Shadow mode runs end-to-end before live send.

Rollout

Switching every tenant at once

Not how this lands. Two env flags — enable and live-send — run shadow first, then flip one vault at a time. Anything goes wrong, flip back.

08 Access model

Five actor types, five surfaces. Every cell shows the auth method and what's allowed. Auth, sessions, and the user model all come from Agent Vault. The gateway adds the new SMTP, API key, and scope checks on top.

Actor →
Surface ↓
Internal tool (SMTP) Internal tool / AI agent (API) Operator Vault owner External tenant
SMTP ingress
New SMTP listener in the custom area.
Yes
SMTP credentials from smtp_relay_users.
N/A
HTTP services hit the API instead.
No No No
HTTP gateway endpoints
Send + map paths.
Optional
If a path declares it, SMTP creds accepted alongside API key.
Yes
API key from smtp_relay_api_keys, scoped.
Diagnostics
Operator API key issued from the vault.
No No
MailerQ proxy
Allowlisted operations only.
No Scoped
API key with the MailerQ-operations scope.
Read
Operator UI surfaces approved ops.
Full
Owner-level access through vault UI.
No
CloudAMQP outbox
Publish side.
Via SMTP
SMTP path can land in the outbox.
Yes
API key with the CloudAMQP-operations scope.
Read
Queue health in operator UI.
Full
Owner can drain / requeue.
No
Instance settings
Enable and live-send flags.
No No No Yes
Only the vault owner toggles the feature.
No
Allowed Conditional / scoped Not allowed or not applicable

09 New views

Wireframe sketches, not final designs. Every screen lives inside the existing Agent Vault UI shell; this section shows the new surfaces the email feature introduces. The first row is the operator and owner surfaces inside the dedicated email instance. The second row is what tenant vaults and the instance owner see. Everything else (login, sidebar, header, audit, alerts) is unchanged.

Operator
Email · Send log
All callers ▾ Last 24h ▾ Status: all ▾ Search recipient or message id…
TimeCallerToStatusid
14:02sendyjane@…delivereda1b…
14:01odooops@…queuedc2d…
13:58sendyboom@…failede3f…
13:55odoolead@…in flight7g8…
Backed by the new smtp_relay_logs table plus the gateway's correlation id.
Per-vault
Vault › Email settings
ProfileEmailAPI keysLogs
acme.com, mail.acme.com
2 ▾
From domain ▾
500
500 ▾
Enabled ▾
Enabled ▾
Every value here is a per-vault tunable. No defaults are hard-coded in the mapping layer or runtime.
Operator
Email · API keys
User: sendy-bot ▾ + Issue key
LabelPrefixScopesLast used
sendy-sendak_8f…send, mailerq2 min ago
odoo-sendak_b1…send, amqp14:00
old-cronak_c3…sendrevoked
Issued from smtp_relay_api_keys. Scopes gate MailerQ and CloudAMQP operations.
Owner
MailerQ proxy · Operations
Approved ops
7
Inflight
3
Error rate (1h)
0.4%
GET /api/jobsopen
GET /api/queuesopen
GET /api/resultsopen
Allowlisted MailerQ admin paths only. Everything else returns 404 at the gateway boundary.
Operator
CloudAMQP · Queues
outbox healthy
14 ready · 3 unacked · publish rate 8/s
success healthy
consumed by email events processor
failure backlog
42 ready · investigate before draining
Connectivity surfaced through gateway health checks; deep operations stay in CloudAMQP console.
Operator
MailerQ · IP pools
DomainPoolIPs
acme.comtransactional3edit
mail.acme.commarketing5edit
internal.musthinternal2edit
Backs the mailerq_ip_pools resolution rule. Used when a request omits ips.
Owner
Settings · Custom features
Email gateway
AGENT_VAULT_CUSTOM_EMAILGATEWAY_ENABLED
Inbound SMTP
…_INBOUND_SMTP_ENABLED · sub-flag
Live send
…_LIVE_SEND_ENABLED · off = shadow mode
Instance owner only. Off-by-default on every other Agent Vault deployment.
Existing UI
Approvals (extended)
agent · billing-bot
just now
actionemail.send
tojane@acme.com
fromops@acme.com
over rate+18 / hr cap
Approve Reject
Same approval surface vaults already use. Email sends slot in as another action type.

10 Build process

Five steps, in order. The build lives entirely inside the fork's custom-feature area, ships through the existing staging → main flow, and never edits an upstream file.

1

Open the build sub-issue

Single tracking issue under the existing parent. Links the build guide, this scope page, and the touch-point table for custom paths.

2

Scaffold the custom package

Create internal/custom/email/ and web/src/custom/email/. Wire through internal/server/custom_wiring.goinitCustom. Migrations for the new tables (smtp_relay_users, smtp_relay_logs, smtp_relay_api_keys, mailerq_ip_pools).

3

Ship in phased PRs

Foundation → core send path → events + UI. Each PR lands behind AGENT_VAULT_CUSTOM_EMAILGATEWAY_ENABLED and the live-send sub-flag.

4

Let upstream syncs flow in

The existing release-to-staging workflow continues. Custom-aware sync review catches any touch-point drift before merge to staging.

5

Shadow then live

Run shadow mode end-to-end. Flip _LIVE_SEND_ENABLED one tenant at a time, watch the success queue, expand from there.