Skip to content

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:

  1. Recipient plus-address: bot+user_id@domain routes directly to the specified user
  2. Sender match: sender email matched against user email_addresses config
  3. Thread match: References header matched against sent_emails table (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_match is 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_senders in 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).

Per-user email settings

# [users.alice] block in config.toml — DB row populated by `istota user ensure` wins
email_addresses = ["alice@example.com"]
trusted_email_senders = ["*@company.com", "boss@other.com"]
alerts_channel = "room789"  # Talk room for confirmations/alerts