nullclaw-channel-imap-connector is a companion repository for running
bidirectional email as a channels.external plugin in nullclaw.
It keeps IMAP/SMTP transport code, HTML sanitization, attachment handling, and
mailbox-specific helper workflows out of nullclaw core while still using the
generic ExternalChannel JSON-RPC/stdio contract.
nullclaw-plugin-imap-connectorLong-lived JSON-RPC external channel plugin for inbound IMAP polling and outbound SMTP delivery.imap_connector/cli.pyOne-shot mailbox CLI for listing, reading, searching, moving, replying, and sending emails outside the channel runtime.scripts/email_tracker.pySQLite-backed delegated-thread tracker for unknown sender workflows.skill/Companion skill instructions for using the CLI and tracker from Codex/nullclaw.
python3 -m pip install -r requirements.txt
chmod +x ./nullclaw-plugin-imap-connector{
"channels": {
"external": {
"accounts": {
"mailbox": {
"runtime_name": "imap_connector",
"transport": {
"command": "/absolute/path/to/nullclaw-channel-imap-connector/nullclaw-plugin-imap-connector",
"timeout_ms": 10000
},
"config": {
"imap": {
"host": "imap.example.com",
"port": 993,
"username": "agent@example.com",
"password": "YOUR_APP_PASSWORD",
"tls": true,
"folder": "INBOX"
},
"smtp": {
"host": "smtp.example.com",
"port": 587,
"username": "agent@example.com",
"password": "YOUR_APP_PASSWORD",
"tls": true
},
"from_address": "agent@example.com",
"allow_from": ["*@your-company.com"],
"poll_interval_secs": 60,
"max_body_length": 50000,
"attachment_save_dir": "workspace/docs",
"attachment_extensions": [".pdf", ".docx"],
"attachment_max_bytes": 20971520,
"state_file": "imap-connector-state.json"
}
}
}
}
}
}- polls IMAP for unseen mail
- emits
inbound_messagenotifications with a stable per-sendersession_key - wraps email body in untrusted markers before it reaches the agent
- stores a reply target token in
chat_id, so normal channel replies become threaded SMTP replies - suppresses outbound delivery when the reply starts with
[NO_REPLY] - saves allowed attachments to disk and includes the saved paths in the prompt
The CLI is separate from the long-lived channel runtime. Use it for mailbox operations that do not fit the generic channel contract.
Example:
echo '{"command":"list_emails","args":{"folder":"INBOX","limit":10},"credentials":{"imap":{"host":"imap.example.com","port":993,"username":"agent@example.com","password":"YOUR_APP_PASSWORD","tls":true}}}' | python3 -m imap_connector.cliSupported commands:
list_folderslist_emailsread_emailsearch_emailsmove_emailmark_readcreate_folderget_threadsend_emailreply_email
python3 -m pytest