Repository navigation
feat(chat): the assistant's own email address, over IMAP and SMTP - #314
Merged
Merged
Conversation
Paste a bot token from @Botfather in Settings > Telegram and the assistant answers there through getUpdates long polling: no Matterbridge, no public address. It answers only people let in (one click from the settings page), transcribes voice notes, holds files for the assistant to import, and keeps everything Matterbridge rooms do (namespace routing, approving by reply). channels.py is the adapter layer: Matterbridge and Telegram are adapters with their own thread, lease and status, and bridge.handle serves both. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194jTK9wbki29B4LcS7aKHG
…at channel Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194jTK9wbki29B4LcS7aKHG
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194jTK9wbki29B4LcS7aKHG
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194jTK9wbki29B4LcS7aKHG
People let in email or forward things to the assistant's mailbox; it answers in the same thread and imports what's attached. Mail is answered only from allowed senders whose provider vouched for From (Authentication-Results); strangers are held on the settings page until let in. Forwarded text is marked as third-party data. Plus addresses and mail routing rules pick the namespace. Settings > Assistant email, with a Check button. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdrxhwvmLjFsfzQZ5pRJGC
…to claude/project-thread-sr9ptd # Conflicts: # CHANGELOG.md # threat-model.yaml
…-sr9ptd # Conflicts: # CHANGELOG.md # README.md
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194jTK9wbki29B4LcS7aKHG
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194jTK9wbki29B4LcS7aKHG
…to claude/project-thread-sr9ptd # Conflicts: # CHANGELOG.md # fastapi_backend/app/domain/ai_tools.py # threat-model.yaml
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194jTK9wbki29B4LcS7aKHG
…tside text (#309) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdrxhwvmLjFsfzQZ5pRJGC
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194jTK9wbki29B4LcS7aKHG
…to claude/project-thread-sr9ptd # Conflicts: # CHANGELOG.md # fastapi_backend/app/domain/settings.py # threat-model.yaml
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdrxhwvmLjFsfzQZ5pRJGC
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdrxhwvmLjFsfzQZ5pRJGC
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194jTK9wbki29B4LcS7aKHG
The cloud-model journey picks OpenAI from Settings › LLM provider's cloud providers (#334) instead of typing the address and model, and both baselines come from fresh runs: computer 3 clicks + 2 keys (was 5 + 40), phone 5 taps + 1 key + 1 swipe (was 7 + 39 + 2). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bmpwkzx9nwb1vcDxNMagtu (cherry picked from commit d1d6cfd)
Owner
Author
|
Clicks and keystrokes per feature failed on 14d9948 in the phone run's I ported #350's fix into this PR as 1dbe789: the journey now picks OpenAI from the presets, and the baselines are refreshed. When #350 merges, that commit makes no further change here. Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194jTK9wbki29B4LcS7aKHG
…d-sr9ptd # Conflicts: # fastapi_backend/app/domain/bridge.py # fastapi_backend/app/domain/settings.py # threat-model.yaml
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdrxhwvmLjFsfzQZ5pRJGC
The open-room check moves from Matterbridge to Channel.outside, so Slack without a user list still makes changes ask first, as on main. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdrxhwvmLjFsfzQZ5pRJGC
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdrxhwvmLjFsfzQZ5pRJGC
17 of 19 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Adeel · project thread
Description
Before: the assistant could be reached in Chat, chat rooms (Matterbridge), Telegram and Slack, but not by email. Email only came in as an import source.
After: Settings › Assistant email: enter an address and its app password, turn on Answer email, then email it. You show under Wrote to it, not let in. Click Let in, and from then on the assistant answers your email in the same thread (
In-Reply-To/References). It imports attachments into the namespace it picks; one it would have to create waits for approval. A forwarded email is held whole as an.emlto import. Its text reaches the model marked as third-party data (#302), and once the model has read it, changes ask first, the import included (#309). Approving by replying "yes",/clearand/compactwork as in chat rooms. Answers list the excerpts they cite, with links back into Lens, and are plain text. Check the mailbox signs in to IMAP and SMTP without reading or sending anything.Who it answers: only addresses in People it answers whose provider vouched for the From address. That means DMARC passed, or DKIM or SPF passed for the sender's domain, read from the topmost
Authentication-Resultsheader (or the one signed by Trust checks by). Strangers get nothing back and wait on the settings page. Auto-replies, bounces, mailing lists and its own mail are never answered. Replies go only to From, never Reply-To.Which namespace: a plus address (
lens+pods@…), then Where mail goes rules (from: *@acme.com = acme,subject: newsletter = skip, the grammar ofmail_routes.py), then the same picking as an open chat room.How:
app/domain/mailbox.pyis achannels.Channeladapter next to Matterbridge, Telegram and Slack. The lease means one process reads it, everypoll_seconds(30 by default). It reads with the IMAP code of email sources (feeds.connect,_select,_messages, newread), with a UIDVALIDITY-keyed cursor that starts at UIDNEXT, so old mail isn't answered. It sends withsmtplib, usingAuto-Submitted: auto-replied(RFC 3834).bridge.handlegains four channel hooks. The first is a channel-set namespace. The second isChannel.acts(act_on): the import tool runs without asking when imports are automatic and the namespace exists. The third isChannel.outside, which says where a message's text came from outside Lens: any room with no user list, as before (the default), and forwarded mail. The fourth is a cited-sources footer for channels that ask for one.convert._messageis now publicparse_email, andmail_routesgains a one-line rule grammar. Endpoints:GET /settings/mailbox,POST /settings/mailbox/test, plus the usualPUT /settings/mailbox. The password is a sealed secret.Motivation and Context
Adeel asked for a dedicated IMAP email address for the agent, so people can email or forward things to it.
Types of changes
!in the title)Checklist:
tests/api/test_mailbox.py(11, against newtests/fake_smtp.pyand an extendedtests/fake_imap.py) andtests/domain/test_mailbox.py(4).docs/accordingly: newdocs/assistant-email.md, plus links fromdocs/chat-rooms.md, the README and the mkdocs nav.mkdocs build --strictis clean.## UnreleasedinCHANGELOG.md.Done questions (AGENTS.md)
/settings/mailbox,/settings/mailbox/test). The assistant can change them with approval (change_settings mailbox), and the mailbox is itself a way for any agent or person to reach the assistant.imaplibandsmtplib), and servers are guessed from the address.make openapiregeneratedopenapi.jsonand the client, and the threat-model YAML hash was recomputed and validated.Authentication-Resultsand DMARC. No new dependencies.mailbox.pydocstring and the docs): IMAP IDLE, a copy of answers in Sent, autoconfig for more providers, letting in whole domains, and answers as audio attachments.🤖 Generated with Claude Code
https://claude.ai/code/session_01VdrxhwvmLjFsfzQZ5pRJGC