arizuko › components › emaid
emaid is the email channel adapter. It polls IMAP for inbound messages, sends outbound via SMTP, and keeps a local SQLite table mapping email threads (Message-ID + References) to their chat_jid so replies preserve the conversation. From gated’s point of view it is a normal channel adapter; the JID prefix is email:.
Each inbound message is verified against DMARC results in the upstream MTA’s Authentication-Results header before it reaches the agent. Verified mail rides verb=message; the rest rides verb=untrusted so route rules can quarantine it.
Email is the lowest-common-denominator inbound: every customer, every vendor, every ticket system can reach an arizuko agent if it has an SMTP address. Without emaid there is no IMAP poll loop, no thread table, no sender authentication — the agent has no way to receive email at all.
The split between verb=message and verb=untrusted exists because unauthenticated email is a forgery surface: an attacker who can spoof a reply-to-bot must not escalate to verb=mention. Sender authentication has to live at the channel boundary, before routing.
INBOX for messages newer than the last seen UID.Authentication-Results. DMARC pass on the configured authserv-id (plus optional From-domain allowlist) → verb=message. Anything else → verb=untrusted, optional [UNVERIFIED] subject prefix. EMAIL_STRICT_AUTH=1 drops untrusted mail outright.Message-ID or References. Hit → reuse the existing chat_jid. Miss → mint a new one./v1/messages to gated, signed with CHANNEL_SECRET.POST /v1/send. emaid hands the body to SMTP with In-Reply-To + References set from the thread row.mail server (Gmail, Fastmail, …)
| IMAP IDLE / poll
v
emaid (parse Authentication-Results, classify verb)
| POST /v1/messages (signed by CHANNEL_SECRET)
v
gated
| POST /v1/send (callback to emaid)
v
emaid (SMTP submit; In-Reply-To = thread root)
|
v
mail server → recipient
Inputs: IMAP traffic on the configured mailbox; HTTP callbacks from gated. Outputs: SMTP submissions; signed POSTs to gated. Hard deps: a reachable gated, an IMAP+SMTP account, and a trusted upstream MTA whose authserv-id is set in EMAIL_TRUSTED_AUTHSERV.
Library: emersion/go-msgauth/authres. emaid does not run its own DMARC validator; it trusts the MTA that delivered the mail. The operator pins which MTA via EMAIL_TRUSTED_AUTHSERV (fail-closed default — unset value rejects every message). Three classification levers:
EMAIL_TRUSTED_AUTHSERV — required. authserv-id whose DMARC verdict to trust (e.g. mx.google.com).EMAIL_TRUSTED_DOMAINS — optional allowlist of From-domains. When set, DMARC pass alone is not enough; the sender domain must also match.EMAIL_STRICT_AUTH — truthy (true/1/yes/on) drops untrusted mail with no agent turn. Default is false — untrusted mail still reaches the store with verb=untrusted.EMAIL_UNVERIFIED_SUBJECT_PREFIX — truthy adds an [UNVERIFIED] prefix to the subject when the message lands as untrusted.The gateway will not promote an untrusted reply-to-bot to verb=mention (mention-promotion spec). Operators route verb=untrusted to a quarantine sub-group.
emaid is a plain Go binary. It needs a reachable gated, an IMAP+SMTP account, and a writable directory for its thread store.
export ROUTER_URL=http://gated:8080
export CHANNEL_SECRET=$(grep ^CHANNEL_SECRET .env | cut -d= -f2)
export LISTEN_ADDR=:8080
export DATA_DIR=/srv/data/arizuko_demo
export EMAIL_IMAP_HOST=imap.fastmail.com EMAIL_IMAP_PORT=993
export EMAIL_SMTP_HOST=smtp.fastmail.com EMAIL_SMTP_PORT=587
export EMAIL_ACCOUNT=bot@example.com EMAIL_PASSWORD=...
export EMAIL_TRUSTED_AUTHSERV=mx.example.com
./emaid
GET /health returns 503 when IMAP login fails or the poll loop is erroring; 200 once a successful poll completes.
Email has no native reactions, no edit, no delete. emaid implements send and fetch_history; every other Socializer verb returns chanlib.ErrUnsupported so the agent can pick a different action instead of silently dropping the call.
emaid/README.md — full env var table, verb support, file map.SECURITY.md — trust model; why untrusted senders cannot escalate.verb=untrusted to a quarantine sub-group.