Email¶
Istota polls an IMAP inbox for incoming messages and sends replies via SMTP.
Receiving email¶
The email poller checks the configured IMAP folder (default: INBOX) at regular intervals. Routing precedence for incoming mail:
- Recipient plus-address:
bot+user_id@domainroutes directly to the specified user - Sender match: sender email matched against user
email_addressesconfig - Thread match:
Referencesheader matched againstsent_emailstable (emissary thread replies)
Attachments are downloaded to /Users/{user_id}/inbox/.
Email confirmation gate¶
Emails from untrusted senders require explicit user confirmation before processing. This applies to:
- Plus-addressed emails (
bot+user_id@domain) from senders not in the user's trusted list - Sender-match routed emails when
confirm_sender_matchis enabled (default: true)
When an email is gated, a confirmation prompt is posted to the user's alerts channel (Talk) asking them to approve, trust the sender, or discard the message. Trusted senders bypass the gate.
Trusted senders are configured at two levels:
- Config-time:
trusted_email_sendersin per-user config (supports fnmatch patterns like*@company.com) - Runtime: managed via Talk commands
!trust sender@example.com # add trusted sender
!untrust sender@example.com # remove trusted sender
!trust # list all trusted senders
Runtime trusted senders are stored in the database and checked alongside config-time patterns.
Suspicious email alerts¶
During task execution, if the agent detects suspicious content in an email (social engineering, prompt injection, exfiltration attempts), it writes an alert to a deferred JSON file. After task completion, the scheduler posts these alerts to the user's alerts channel in Talk.
Sending email¶
Outbound emails use SMTP. The SMTP_FROM address is plus-addressed as bot+user_id@domain so replies route back to the correct user.
Email output uses a deferred file pattern: Claude writes a JSON file to the temp dir, and the scheduler sends the email after task completion.
Emissary threads¶
When the bot sends an email on behalf of a user, the outbound message is tracked in the sent_emails table (Message-ID, recipient, user, conversation_token, plus an origin descriptor recording the surface+channel the thread started on). When external contacts reply, the email poller matches References headers against sent emails and creates a task routed by the user's email_reply_routing policy:
origin+thread(default) — deliver the reply to the conversation's origin surface (web/talk/ etc.) and mirror it back over email to the thread.origin— origin surface only.thread— email thread only.
The origin descriptor self-addresses the surface and channel, so a reply to a web-started thread lands back in the right web room rather than defaulting to Talk. Replies whose origin can't be recovered (plus-address / sender-match paths with no descriptor) fall back to the user's resolved Talk room.
The bot drafts a response and asks for confirmation. On approval, the task re-executes with confirmation_context injected, instructing it to send the draft rather than re-draft. Pending confirmations are auto-cancelled when the user sends a new message in the same conversation.
Configuration¶
[email]
enabled = true
imap_host = "imap.example.com"
imap_port = 993
imap_user = "istota@example.com"
imap_password = "app-password-here"
smtp_host = "smtp.example.com"
smtp_port = 587
# smtp_user = "" # defaults to imap_user
# smtp_password = "" # defaults to imap_password
poll_folder = "INBOX"
bot_email = "istota@example.com"
SMTP credentials fall back to IMAP credentials if not set.
Polling interval is controlled by email_poll_interval in [scheduler] (default 60s). Old processed emails are cleaned after email_retention_days (default 7).