courier 1.1.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. courier-1.1.15/LICENSE +21 -0
  2. courier-1.1.15/PKG-INFO +264 -0
  3. courier-1.1.15/README.md +224 -0
  4. courier-1.1.15/courier/__init__.py +3 -0
  5. courier-1.1.15/courier/__main__.py +3160 -0
  6. courier-1.1.15/courier/_claude_command.py +320 -0
  7. courier-1.1.15/courier/app_password.py +99 -0
  8. courier-1.1.15/courier/auth_setup.py +130 -0
  9. courier-1.1.15/courier/browser_auth.py +400 -0
  10. courier-1.1.15/courier/config.py +879 -0
  11. courier-1.1.15/courier/gmail_auth.py +55 -0
  12. courier-1.1.15/courier/identity.py +210 -0
  13. courier-1.1.15/courier/imap_client.py +1595 -0
  14. courier-1.1.15/courier/local_cache.py +456 -0
  15. courier-1.1.15/courier/logging_setup.py +87 -0
  16. courier-1.1.15/courier/markdown_render.py +49 -0
  17. courier-1.1.15/courier/mcp_protocol.py +89 -0
  18. courier-1.1.15/courier/mcp_server.py +166 -0
  19. courier-1.1.15/courier/models.py +695 -0
  20. courier-1.1.15/courier/oauth2.py +164 -0
  21. courier-1.1.15/courier/oauth2_config.py +178 -0
  22. courier-1.1.15/courier/query_parser.py +547 -0
  23. courier-1.1.15/courier/resources.py +242 -0
  24. courier-1.1.15/courier/sieve_filter.py +249 -0
  25. courier-1.1.15/courier/smtp_client.py +437 -0
  26. courier-1.1.15/courier/smtp_transport.py +210 -0
  27. courier-1.1.15/courier/tools.py +744 -0
  28. courier-1.1.15/courier/workflows/__init__.py +1 -0
  29. courier-1.1.15/courier/workflows/calendar_mock.py +164 -0
  30. courier-1.1.15/courier/workflows/invite_parser.py +342 -0
  31. courier-1.1.15/courier/workflows/meeting_reply.py +260 -0
  32. courier-1.1.15/courier.egg-info/PKG-INFO +264 -0
  33. courier-1.1.15/courier.egg-info/SOURCES.txt +75 -0
  34. courier-1.1.15/courier.egg-info/dependency_links.txt +1 -0
  35. courier-1.1.15/courier.egg-info/entry_points.txt +2 -0
  36. courier-1.1.15/courier.egg-info/requires.txt +20 -0
  37. courier-1.1.15/courier.egg-info/top_level.txt +1 -0
  38. courier-1.1.15/pyproject.toml +100 -0
  39. courier-1.1.15/setup.cfg +4 -0
  40. courier-1.1.15/tests/test_app_password.py +46 -0
  41. courier-1.1.15/tests/test_auth_setup.py +7 -0
  42. courier-1.1.15/tests/test_browser_auth.py +300 -0
  43. courier-1.1.15/tests/test_chain.py +866 -0
  44. courier-1.1.15/tests/test_cli_send.py +1221 -0
  45. courier-1.1.15/tests/test_config.py +1058 -0
  46. courier-1.1.15/tests/test_copy.py +392 -0
  47. courier-1.1.15/tests/test_gmail_auth.py +35 -0
  48. courier-1.1.15/tests/test_identity.py +263 -0
  49. courier-1.1.15/tests/test_imap_client.py +2074 -0
  50. courier-1.1.15/tests/test_imap_client_drafts.py +151 -0
  51. courier-1.1.15/tests/test_imap_client_threading.py +672 -0
  52. courier-1.1.15/tests/test_infrastructure.py +375 -0
  53. courier-1.1.15/tests/test_install_claude_command.py +63 -0
  54. courier-1.1.15/tests/test_local_cache.py +525 -0
  55. courier-1.1.15/tests/test_markdown_render.py +76 -0
  56. courier-1.1.15/tests/test_models.py +139 -0
  57. courier-1.1.15/tests/test_oauth2_config.py +191 -0
  58. courier-1.1.15/tests/test_query_parser.py +451 -0
  59. courier-1.1.15/tests/test_resources.py +291 -0
  60. courier-1.1.15/tests/test_search_formatters.py +194 -0
  61. courier-1.1.15/tests/test_send_draft.py +231 -0
  62. courier-1.1.15/tests/test_server.py +177 -0
  63. courier-1.1.15/tests/test_sieve_filter.py +230 -0
  64. courier-1.1.15/tests/test_smtp_client_composition.py +491 -0
  65. courier-1.1.15/tests/test_smtp_transport.py +226 -0
  66. courier-1.1.15/tests/test_status_command.py +267 -0
  67. courier-1.1.15/tests/test_tools.py +624 -0
  68. courier-1.1.15/tests/test_tools_attachments.py +630 -0
  69. courier-1.1.15/tests/test_tools_compose.py +389 -0
  70. courier-1.1.15/tests/test_tools_html_export.py +694 -0
  71. courier-1.1.15/tests/test_tools_link_extraction.py +512 -0
  72. courier-1.1.15/tests/test_tools_orchestration.py +113 -0
  73. courier-1.1.15/tests/test_tools_read.py +149 -0
  74. courier-1.1.15/tests/test_tools_reply.py +531 -0
  75. courier-1.1.15/tests/test_tools_reply_drafting.py +163 -0
  76. courier-1.1.15/tests/test_trash.py +91 -0
  77. courier-1.1.15/tests/test_utils.py +222 -0
courier-1.1.15/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Weiwu Zhang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,264 @@
1
+ Metadata-Version: 2.4
2
+ Name: courier
3
+ Version: 1.1.15
4
+ Summary: Email toolkit for AI assistants and command-line scripting
5
+ Author: Weiwu Zhang
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/overseers-desk/courier
8
+ Project-URL: Issues, https://github.com/overseers-desk/courier/issues
9
+ Project-URL: Author, https://www.linkedin.com/in/weiwuzhang/
10
+ Keywords: email,imap,mcp,ai,cli,gmail,automation
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: imapclient>=3.0.0
22
+ Requires-Dist: markdown-it-py>=3.0.0
23
+ Requires-Dist: python-dotenv>=1.0.0
24
+ Requires-Dist: requests>=2.32.0
25
+ Requires-Dist: sievelib>=1.5
26
+ Requires-Dist: typer>=0.15.0
27
+ Provides-Extra: mcp
28
+ Requires-Dist: flask>=3.1.0; extra == "mcp"
29
+ Requires-Dist: mcp>=1.0.0; extra == "mcp"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.25.0; extra == "dev"
34
+ Requires-Dist: ruff>=0.11.0; extra == "dev"
35
+ Requires-Dist: black>=23.0.0; extra == "dev"
36
+ Requires-Dist: isort>=5.10.0; extra == "dev"
37
+ Requires-Dist: mypy>=0.982; extra == "dev"
38
+ Requires-Dist: pre-commit>=2.19.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # ![Courier](docs/logo.png)
42
+
43
+ [![CI](https://github.com/overseers-desk/courier/actions/workflows/code_checks.yml/badge.svg)](https://github.com/overseers-desk/courier/actions/workflows/code_checks.yml)
44
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
45
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
46
+
47
+ Give your script or AI assistant access to your email.
48
+
49
+ ## Contents
50
+
51
+ - [What your AI can do with it](#what-your-ai-can-do-with-it)
52
+ - [Installation](#installation)
53
+ - [Configuration](#configuration)
54
+ - [Quick test](#quick-test)
55
+ - [CLI usage](#cli-usage)
56
+ - [Claude Code (terminal-based Claude)](#claude-code-terminal-based-claude)
57
+ - [MCP server](#mcp-server)
58
+ - [Scripting and automation](#scripting-and-automation)
59
+ - [Faster searches](#faster-searches)
60
+ - [Connection handling](#connection-handling)
61
+ - [Security](#security)
62
+ - [License](#license)
63
+
64
+ Courier connects to your existing mailbox on Gmail, Outlook, Fastmail, or any IMAP provider. It does not create new email addresses or route mail through a third-party service.
65
+
66
+ Commandline users, script authors, and AI assistants can search, read, download, reply, send, and organize email. Two interfaces serve different environments: a CLI that outputs JSON (for terminal-based agents, scripts, and automation) and an MCP server (for web-based AI chats and MCP clients). Both expose the same operations.
67
+
68
+ ## What your AI can do with it
69
+
70
+ - Courier runs on your machine, with no service of ours between you and your mailbox.
71
+ - Keep banking, OTPs, and sensitive senders out of the LLM's view, using per-mailbox Sieve rules.
72
+ - Reply from the right alias, using our identity feature.
73
+ - Gmail-style queries (`from:alice newer:3d is:unread`) work on any IMAP provider.
74
+ - An optional local `mu` index makes archive-grade searches near-instant.
75
+ - Search every mailbox in one call with `-A`.
76
+ - Drafts on your server by default; `--send` is opt-in.
77
+ - Handle a meeting invite (parse the `.ics`, draft an RSVP).
78
+ - Chain searches and reads in one call: `courier search foo search bar read -f INBOX -u 42`.
79
+ - Move, flag, or archive messages.
80
+ - Download attachments, export messages as HTML, extract links.
81
+
82
+ ## Installation
83
+
84
+ See [INSTALLATION.md](docs/INSTALLATION.md) for Homebrew, Debian/Ubuntu (.deb), Fedora/RHEL (.rpm), and source installs.
85
+
86
+ ## Configuration
87
+
88
+ Copy the sample and fill in your credentials:
89
+
90
+ ```bash
91
+ cp examples/config.sample.toml ~/.config/courier/config.toml
92
+ ```
93
+
94
+ A small config has three top-level named-entity tables: an `[imap.NAME]` mailbox, an `[smtp.NAME]` outgoing endpoint, and an `[identity.NAME]` describing one sendable address pointing at the IMAP block:
95
+
96
+ ```toml
97
+ [smtp.gmail]
98
+ host = "smtp.gmail.com"
99
+ port = 587
100
+
101
+ [imap.personal]
102
+ host = "imap.gmail.com"
103
+ port = 993
104
+ username = "you@gmail.com"
105
+ # For Gmail, generate this at https://myaccount.google.com/apppasswords
106
+ password = "abcdefghijklmnop"
107
+ default_smtp = "gmail"
108
+
109
+ [identity.personal]
110
+ imap = "personal"
111
+ address = "you@gmail.com"
112
+ ```
113
+
114
+ (Smaller is also valid: `[imap.*]` alone reads but cannot send; `[identity.*]` with `bcc` plus `[smtp.*]` sends but cannot read.)
115
+
116
+ For Gmail, the simpler path is the app-password example above. The alternative is OAuth2, which needs a Google Cloud project set up through Google's developer console (a much messier path); if you have already done that, the same `[imap.NAME]` block carries the OAuth2 keys instead of `password`:
117
+
118
+ ```toml
119
+ [imap.personal]
120
+ host = "imap.gmail.com"
121
+ port = 993
122
+ username = "you@gmail.com"
123
+ client_id = "YOUR_CLIENT_ID"
124
+ client_secret = "YOUR_CLIENT_SECRET"
125
+ refresh_token = "YOUR_REFRESH_TOKEN"
126
+ default_smtp = "gmail"
127
+ ```
128
+
129
+ `[smtp.NAME]` blocks declare named SMTP endpoints. When the block omits credentials, courier inherits them from the `[imap.NAME]` block in scope at send time, the right shape for Gmail and Fastmail where IMAP and SMTP share one credential. When the block carries its own `username` and `password`, courier uses those, the right shape for AWS SES and similar smarthosts where one IAM SMTP user serves many From addresses.
130
+
131
+ Sending requires at least one `[identity.NAME]` block pointing at the `[imap.NAME]`. A block with no identities is read-only for sending; drafting and reading still work. This is a valid state, not an error. Declaring identities explicitly avoids the registration-handle hazard, where an IMAP login (e.g. a Gmail handle that is not an intended sender) could otherwise become a sendable identity by accident.
132
+
133
+ `courier config-check` validates the config (cross-references, identity addresses, send-route resolution) without performing any IMAP or SMTP traffic. The same warnings surface on `courier`, `courier --help`, `courier status`, and `courier list`.
134
+
135
+ Gmail OAuth2 setup requires a Google Cloud project with the Gmail API enabled. See [GMAIL_SETUP.md](docs/GMAIL_SETUP.md) for the full walkthrough.
136
+
137
+ For multi-account configs (multiple `[imap.*]` blocks, send-as through several identities, SES smarthost routing), see [docs/CONFIGURATION.md](docs/CONFIGURATION.md).
138
+
139
+ ## Quick test
140
+
141
+ With uv (any platform):
142
+
143
+ ```bash
144
+ uvx courier search "subject:invoice"
145
+ ```
146
+
147
+ No installation step; `uvx` runs it directly. To install permanently:
148
+
149
+ ```bash
150
+ uv tool install courier
151
+ ```
152
+
153
+ On Debian and Ubuntu, the default install path is the `.deb` package from the GitHub release (see [INSTALLATION.md](docs/INSTALLATION.md)). As an alternative for running from a clone without installing, on Ubuntu 25.04 or later the CLI dependencies are all in the standard repositories:
154
+
155
+ ```bash
156
+ sudo apt-get install python3-typer python3-dotenv python3-imapclient python3-requests
157
+ python3 -m courier search "subject:invoice"
158
+ ```
159
+
160
+ Courier looks for a config file at `~/.config/courier/config.toml`. Use `--config /path/to/config.toml` to point to a different location.
161
+
162
+ The MCP server (`courier mcp`) requires the `mcp` Python package, which is not in apt. Use `uv` or `pip` for that. Many users prefer the CLI to MCP because the latter loads 80+ tools into every conversation; the CLI is the lighter footprint.
163
+
164
+ ## CLI usage
165
+
166
+ Every command outputs JSON to stdout. Errors go to stderr. This makes Courier composable with `jq`, shell scripts, and AI agent skill definitions.
167
+
168
+ ```bash
169
+ # Look up several keywords in one invocation; each gets its own outer key
170
+ # in the result, so hits stay attributed to the keyword that matched them
171
+ courier search "from:alice" search 'subject:"hotel booking"' search "is:unread"
172
+
173
+ # What's unread in INBOX?
174
+ courier search "is:unread" --folder INBOX --limit 10
175
+
176
+ # Read an email
177
+ courier read -f INBOX -u 4523
178
+
179
+ # List and download attachments
180
+ courier attachments -f INBOX -u 4523
181
+ courier save -f INBOX -u 4523 --attachment itinerary.pdf -o /tmp/itinerary.pdf
182
+
183
+ # Export an HTML email as a standalone file (images embedded)
184
+ courier export -f INBOX -u 4523 -o /tmp/email.html
185
+ courier export -f INBOX -u 4523 -o /tmp/email.eml --raw
186
+
187
+ # Extract all links from several emails
188
+ courier links -f INBOX -u 4523 -u 4524 -u 4525
189
+
190
+ # Reply, saved to drafts by default; --send transmits via SMTP
191
+ courier reply -f INBOX -u 4523 -b "Thanks, confirmed."
192
+ courier reply -f INBOX -u 4523 -b "Invoice attached." --attach /tmp/invoice.pdf
193
+ courier reply -f INBOX -u 4523 -b "Thanks, confirmed." --send
194
+
195
+ # Compose a new message (--send requires --identity NAME, or
196
+ # --smtp NAME --from EMAIL; see docs/CONFIGURATION.md)
197
+ courier compose --to alice@example.com --subject "Meeting" \
198
+ -b "See attached." --send --identity work
199
+
200
+ # Send a draft
201
+ courier send-draft -f Drafts -u 4530
202
+
203
+ # Organize
204
+ courier move -f INBOX -u 4523 -t Archive
205
+ courier mark-read -f INBOX -u 4524
206
+ courier flag -f INBOX -u 4525
207
+ ```
208
+
209
+ Run `courier --help` for the full command list.
210
+
211
+ ## Claude Code (terminal-based Claude)
212
+
213
+ Run `courier install-claude-command` once after installation. This writes `~/.claude/commands/courier.md`, which tells Claude Code how to use the courier CLI for email tasks. After that, prompts like "find the booking confirmation from last week" or "reply to Alice's message" route through courier automatically.
214
+
215
+ ## MCP server
216
+
217
+ For AI environments that cannot run shell commands (Claude web, Cursor, or any MCP client):
218
+
219
+ ```bash
220
+ courier mcp
221
+ ```
222
+
223
+ This starts an MCP server exposing the same operations as tools. The MCP package is only imported when this subcommand runs, so the CLI stays lightweight.
224
+
225
+ ## Scripting and automation
226
+
227
+ Because every command returns JSON and uses non-zero exit codes on failure, Courier works as a building block in pipelines and cron jobs.
228
+
229
+ ```bash
230
+ # Forward all emails from a sender to another folder
231
+ courier search "from:sender@example.com" --folder INBOX \
232
+ | jq -r '.[].uid' \
233
+ | xargs -I{} courier move -f INBOX -u {} -t Forwarded
234
+
235
+ # Daily digest: save today's unread subjects to a file
236
+ courier search "is:unread" --folder INBOX \
237
+ | jq -r '.[].subject' > ~/daily-digest.txt
238
+
239
+ # Auto-acknowledge incoming invoices
240
+ courier search "is:unread subject:invoice" --folder INBOX \
241
+ | jq -r '.[].uid' \
242
+ | xargs -I{} courier reply -f INBOX -u {} -b "Received, processing." \
243
+ --send --identity work
244
+ ```
245
+
246
+ AI agents with skill/hook systems call Courier the same way: define a skill that runs a shell command and parses the JSON output.
247
+
248
+ ## Faster searches
249
+
250
+ Courier can answer `search` from a local Xapian index instead of IMAP, orders of magnitude faster, with transparent fallback to IMAP when the index can't serve the query. See [docs/LOCAL_CACHE.md](docs/LOCAL_CACHE.md) for the offlineimap+mu setup.
251
+
252
+ ## Connection handling
253
+
254
+ IMAP servers drop idle connections after 10-30 minutes. AI assistants work in bursts: a flurry of operations, then thinking time. Courier tracks connection age and reconnects transparently before operations fail. The default idle timeout is 300 seconds; set `idle_timeout` in the config to adjust.
255
+
256
+ ## Security
257
+
258
+ Courier accesses your email account. Store credentials outside your repository (environment variables, a secrets manager, or a config file in `.gitignore`). Use app-specific passwords or OAuth2 rather than your main account password. Restrict `allowed_folders` in the config to limit what the tool can see.
259
+
260
+ For per-message control over what reaches the LLM, point an `[imap.NAME]` block's `redact` field at a Sieve script. Matching messages have subject, body, and party addresses blanked before courier returns them, so banking notices, OTPs, and other sensitive content stay out of the model's context window. See [examples/work-only.sieve](examples/work-only.sieve) for a starting policy.
261
+
262
+ ## License
263
+
264
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,224 @@
1
+ # ![Courier](docs/logo.png)
2
+
3
+ [![CI](https://github.com/overseers-desk/courier/actions/workflows/code_checks.yml/badge.svg)](https://github.com/overseers-desk/courier/actions/workflows/code_checks.yml)
4
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
6
+
7
+ Give your script or AI assistant access to your email.
8
+
9
+ ## Contents
10
+
11
+ - [What your AI can do with it](#what-your-ai-can-do-with-it)
12
+ - [Installation](#installation)
13
+ - [Configuration](#configuration)
14
+ - [Quick test](#quick-test)
15
+ - [CLI usage](#cli-usage)
16
+ - [Claude Code (terminal-based Claude)](#claude-code-terminal-based-claude)
17
+ - [MCP server](#mcp-server)
18
+ - [Scripting and automation](#scripting-and-automation)
19
+ - [Faster searches](#faster-searches)
20
+ - [Connection handling](#connection-handling)
21
+ - [Security](#security)
22
+ - [License](#license)
23
+
24
+ Courier connects to your existing mailbox on Gmail, Outlook, Fastmail, or any IMAP provider. It does not create new email addresses or route mail through a third-party service.
25
+
26
+ Commandline users, script authors, and AI assistants can search, read, download, reply, send, and organize email. Two interfaces serve different environments: a CLI that outputs JSON (for terminal-based agents, scripts, and automation) and an MCP server (for web-based AI chats and MCP clients). Both expose the same operations.
27
+
28
+ ## What your AI can do with it
29
+
30
+ - Courier runs on your machine, with no service of ours between you and your mailbox.
31
+ - Keep banking, OTPs, and sensitive senders out of the LLM's view, using per-mailbox Sieve rules.
32
+ - Reply from the right alias, using our identity feature.
33
+ - Gmail-style queries (`from:alice newer:3d is:unread`) work on any IMAP provider.
34
+ - An optional local `mu` index makes archive-grade searches near-instant.
35
+ - Search every mailbox in one call with `-A`.
36
+ - Drafts on your server by default; `--send` is opt-in.
37
+ - Handle a meeting invite (parse the `.ics`, draft an RSVP).
38
+ - Chain searches and reads in one call: `courier search foo search bar read -f INBOX -u 42`.
39
+ - Move, flag, or archive messages.
40
+ - Download attachments, export messages as HTML, extract links.
41
+
42
+ ## Installation
43
+
44
+ See [INSTALLATION.md](docs/INSTALLATION.md) for Homebrew, Debian/Ubuntu (.deb), Fedora/RHEL (.rpm), and source installs.
45
+
46
+ ## Configuration
47
+
48
+ Copy the sample and fill in your credentials:
49
+
50
+ ```bash
51
+ cp examples/config.sample.toml ~/.config/courier/config.toml
52
+ ```
53
+
54
+ A small config has three top-level named-entity tables: an `[imap.NAME]` mailbox, an `[smtp.NAME]` outgoing endpoint, and an `[identity.NAME]` describing one sendable address pointing at the IMAP block:
55
+
56
+ ```toml
57
+ [smtp.gmail]
58
+ host = "smtp.gmail.com"
59
+ port = 587
60
+
61
+ [imap.personal]
62
+ host = "imap.gmail.com"
63
+ port = 993
64
+ username = "you@gmail.com"
65
+ # For Gmail, generate this at https://myaccount.google.com/apppasswords
66
+ password = "abcdefghijklmnop"
67
+ default_smtp = "gmail"
68
+
69
+ [identity.personal]
70
+ imap = "personal"
71
+ address = "you@gmail.com"
72
+ ```
73
+
74
+ (Smaller is also valid: `[imap.*]` alone reads but cannot send; `[identity.*]` with `bcc` plus `[smtp.*]` sends but cannot read.)
75
+
76
+ For Gmail, the simpler path is the app-password example above. The alternative is OAuth2, which needs a Google Cloud project set up through Google's developer console (a much messier path); if you have already done that, the same `[imap.NAME]` block carries the OAuth2 keys instead of `password`:
77
+
78
+ ```toml
79
+ [imap.personal]
80
+ host = "imap.gmail.com"
81
+ port = 993
82
+ username = "you@gmail.com"
83
+ client_id = "YOUR_CLIENT_ID"
84
+ client_secret = "YOUR_CLIENT_SECRET"
85
+ refresh_token = "YOUR_REFRESH_TOKEN"
86
+ default_smtp = "gmail"
87
+ ```
88
+
89
+ `[smtp.NAME]` blocks declare named SMTP endpoints. When the block omits credentials, courier inherits them from the `[imap.NAME]` block in scope at send time, the right shape for Gmail and Fastmail where IMAP and SMTP share one credential. When the block carries its own `username` and `password`, courier uses those, the right shape for AWS SES and similar smarthosts where one IAM SMTP user serves many From addresses.
90
+
91
+ Sending requires at least one `[identity.NAME]` block pointing at the `[imap.NAME]`. A block with no identities is read-only for sending; drafting and reading still work. This is a valid state, not an error. Declaring identities explicitly avoids the registration-handle hazard, where an IMAP login (e.g. a Gmail handle that is not an intended sender) could otherwise become a sendable identity by accident.
92
+
93
+ `courier config-check` validates the config (cross-references, identity addresses, send-route resolution) without performing any IMAP or SMTP traffic. The same warnings surface on `courier`, `courier --help`, `courier status`, and `courier list`.
94
+
95
+ Gmail OAuth2 setup requires a Google Cloud project with the Gmail API enabled. See [GMAIL_SETUP.md](docs/GMAIL_SETUP.md) for the full walkthrough.
96
+
97
+ For multi-account configs (multiple `[imap.*]` blocks, send-as through several identities, SES smarthost routing), see [docs/CONFIGURATION.md](docs/CONFIGURATION.md).
98
+
99
+ ## Quick test
100
+
101
+ With uv (any platform):
102
+
103
+ ```bash
104
+ uvx courier search "subject:invoice"
105
+ ```
106
+
107
+ No installation step; `uvx` runs it directly. To install permanently:
108
+
109
+ ```bash
110
+ uv tool install courier
111
+ ```
112
+
113
+ On Debian and Ubuntu, the default install path is the `.deb` package from the GitHub release (see [INSTALLATION.md](docs/INSTALLATION.md)). As an alternative for running from a clone without installing, on Ubuntu 25.04 or later the CLI dependencies are all in the standard repositories:
114
+
115
+ ```bash
116
+ sudo apt-get install python3-typer python3-dotenv python3-imapclient python3-requests
117
+ python3 -m courier search "subject:invoice"
118
+ ```
119
+
120
+ Courier looks for a config file at `~/.config/courier/config.toml`. Use `--config /path/to/config.toml` to point to a different location.
121
+
122
+ The MCP server (`courier mcp`) requires the `mcp` Python package, which is not in apt. Use `uv` or `pip` for that. Many users prefer the CLI to MCP because the latter loads 80+ tools into every conversation; the CLI is the lighter footprint.
123
+
124
+ ## CLI usage
125
+
126
+ Every command outputs JSON to stdout. Errors go to stderr. This makes Courier composable with `jq`, shell scripts, and AI agent skill definitions.
127
+
128
+ ```bash
129
+ # Look up several keywords in one invocation; each gets its own outer key
130
+ # in the result, so hits stay attributed to the keyword that matched them
131
+ courier search "from:alice" search 'subject:"hotel booking"' search "is:unread"
132
+
133
+ # What's unread in INBOX?
134
+ courier search "is:unread" --folder INBOX --limit 10
135
+
136
+ # Read an email
137
+ courier read -f INBOX -u 4523
138
+
139
+ # List and download attachments
140
+ courier attachments -f INBOX -u 4523
141
+ courier save -f INBOX -u 4523 --attachment itinerary.pdf -o /tmp/itinerary.pdf
142
+
143
+ # Export an HTML email as a standalone file (images embedded)
144
+ courier export -f INBOX -u 4523 -o /tmp/email.html
145
+ courier export -f INBOX -u 4523 -o /tmp/email.eml --raw
146
+
147
+ # Extract all links from several emails
148
+ courier links -f INBOX -u 4523 -u 4524 -u 4525
149
+
150
+ # Reply, saved to drafts by default; --send transmits via SMTP
151
+ courier reply -f INBOX -u 4523 -b "Thanks, confirmed."
152
+ courier reply -f INBOX -u 4523 -b "Invoice attached." --attach /tmp/invoice.pdf
153
+ courier reply -f INBOX -u 4523 -b "Thanks, confirmed." --send
154
+
155
+ # Compose a new message (--send requires --identity NAME, or
156
+ # --smtp NAME --from EMAIL; see docs/CONFIGURATION.md)
157
+ courier compose --to alice@example.com --subject "Meeting" \
158
+ -b "See attached." --send --identity work
159
+
160
+ # Send a draft
161
+ courier send-draft -f Drafts -u 4530
162
+
163
+ # Organize
164
+ courier move -f INBOX -u 4523 -t Archive
165
+ courier mark-read -f INBOX -u 4524
166
+ courier flag -f INBOX -u 4525
167
+ ```
168
+
169
+ Run `courier --help` for the full command list.
170
+
171
+ ## Claude Code (terminal-based Claude)
172
+
173
+ Run `courier install-claude-command` once after installation. This writes `~/.claude/commands/courier.md`, which tells Claude Code how to use the courier CLI for email tasks. After that, prompts like "find the booking confirmation from last week" or "reply to Alice's message" route through courier automatically.
174
+
175
+ ## MCP server
176
+
177
+ For AI environments that cannot run shell commands (Claude web, Cursor, or any MCP client):
178
+
179
+ ```bash
180
+ courier mcp
181
+ ```
182
+
183
+ This starts an MCP server exposing the same operations as tools. The MCP package is only imported when this subcommand runs, so the CLI stays lightweight.
184
+
185
+ ## Scripting and automation
186
+
187
+ Because every command returns JSON and uses non-zero exit codes on failure, Courier works as a building block in pipelines and cron jobs.
188
+
189
+ ```bash
190
+ # Forward all emails from a sender to another folder
191
+ courier search "from:sender@example.com" --folder INBOX \
192
+ | jq -r '.[].uid' \
193
+ | xargs -I{} courier move -f INBOX -u {} -t Forwarded
194
+
195
+ # Daily digest: save today's unread subjects to a file
196
+ courier search "is:unread" --folder INBOX \
197
+ | jq -r '.[].subject' > ~/daily-digest.txt
198
+
199
+ # Auto-acknowledge incoming invoices
200
+ courier search "is:unread subject:invoice" --folder INBOX \
201
+ | jq -r '.[].uid' \
202
+ | xargs -I{} courier reply -f INBOX -u {} -b "Received, processing." \
203
+ --send --identity work
204
+ ```
205
+
206
+ AI agents with skill/hook systems call Courier the same way: define a skill that runs a shell command and parses the JSON output.
207
+
208
+ ## Faster searches
209
+
210
+ Courier can answer `search` from a local Xapian index instead of IMAP, orders of magnitude faster, with transparent fallback to IMAP when the index can't serve the query. See [docs/LOCAL_CACHE.md](docs/LOCAL_CACHE.md) for the offlineimap+mu setup.
211
+
212
+ ## Connection handling
213
+
214
+ IMAP servers drop idle connections after 10-30 minutes. AI assistants work in bursts: a flurry of operations, then thinking time. Courier tracks connection age and reconnects transparently before operations fail. The default idle timeout is 300 seconds; set `idle_timeout` in the config to adjust.
215
+
216
+ ## Security
217
+
218
+ Courier accesses your email account. Store credentials outside your repository (environment variables, a secrets manager, or a config file in `.gitignore`). Use app-specific passwords or OAuth2 rather than your main account password. Restrict `allowed_folders` in the config to limit what the tool can see.
219
+
220
+ For per-message control over what reaches the LLM, point an `[imap.NAME]` block's `redact` field at a Sieve script. Matching messages have subject, body, and party addresses blanked before courier returns them, so banking notices, OTPs, and other sensitive content stay out of the model's context window. See [examples/work-only.sieve](examples/work-only.sieve) for a starting policy.
221
+
222
+ ## License
223
+
224
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,3 @@
1
+ """Email toolkit for AI assistants and command-line scripting."""
2
+
3
+ __version__ = "1.1.15"