arizukocomponents › emaid

emaid

What it is

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.

Why it exists

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.

The loop

  1. Poll IMAP INBOX for messages newer than the last seen UID.
  2. Parse 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.
  3. Look up the thread row by Message-ID or References. Hit → reuse the existing chat_jid. Miss → mint a new one.
  4. POST /v1/messages to gated, signed with CHANNEL_SECRET.
  5. On outbound, gated calls back to POST /v1/send. emaid hands the body to SMTP with In-Reply-To + References set from the thread row.

How it fits

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.

Sender authentication

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:

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.

Standalone usage

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.

Verb support

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.

Go deeper