@openemail/cli 0.0.1
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.
- package/README.md +267 -0
- package/openemail.js +27001 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
<div align='center'>
|
|
2
|
+
<a href='https://openemail.uk'>
|
|
3
|
+
<img
|
|
4
|
+
src='https://openemail.uk/logo.svg'
|
|
5
|
+
alt='OpenEmail Logo'
|
|
6
|
+
width='180'
|
|
7
|
+
/>
|
|
8
|
+
</a>
|
|
9
|
+
|
|
10
|
+
<br />
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<p align='center'>
|
|
14
|
+
Send, read and manage your OpenEmail mail and workspace from the terminal.
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<p align='center'>
|
|
18
|
+
<a href='https://openemail.uk'>
|
|
19
|
+
<b>
|
|
20
|
+
Website
|
|
21
|
+
</b>
|
|
22
|
+
</a>
|
|
23
|
+
•
|
|
24
|
+
<a href='https://openemail.uk/docs/cli'>
|
|
25
|
+
<b>
|
|
26
|
+
Documentation
|
|
27
|
+
</b>
|
|
28
|
+
</a>
|
|
29
|
+
•
|
|
30
|
+
<a href='https://openemail.uk/docs/cli/changelog'>
|
|
31
|
+
<b>
|
|
32
|
+
Changelog
|
|
33
|
+
</b>
|
|
34
|
+
</a>
|
|
35
|
+
•
|
|
36
|
+
<a href='https://openemail.uk/docs/mcp/overview'>
|
|
37
|
+
<b>
|
|
38
|
+
MCP Server
|
|
39
|
+
</b>
|
|
40
|
+
</a>
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<br />
|
|
44
|
+
|
|
45
|
+
## OpenEmail CLI
|
|
46
|
+
|
|
47
|
+
The official command line for OpenEmail. Sign in once, then do from a terminal what the web app, the SDK and the MCP server do: send and read mail, manage domains, addresses, keys, webhooks, templates, contacts, audiences and every other resource, translate, write and summarise with AI, and connect AI clients such as Claude, Cursor and VS Code to your mailbox.
|
|
48
|
+
|
|
49
|
+
It is one file with no dependencies, and it carries the SDK it was built with, so every command sends exactly the request the SDK documents.
|
|
50
|
+
|
|
51
|
+
### Installing
|
|
52
|
+
```bash
|
|
53
|
+
npm i -g @openemail/cli@latest
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Node 20.12 or later is required. `pnpm add -g`, `bun add -g` and `yarn global add` work too, and `npx @openemail/cli <command>` runs it without a global install.
|
|
57
|
+
|
|
58
|
+
### Signing in
|
|
59
|
+
```bash
|
|
60
|
+
openemail login
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
There are two ways in, and `openemail login` asks which one you want.
|
|
64
|
+
|
|
65
|
+
**In your browser.** The CLI opens the OpenEmail approval page, where you pick the workspace, the permissions and how long the access lasts, and the approval comes back to the terminal on its own. Over SSH, on a machine without a display or with `--no-browser`, it prints the link instead: open it on any device, approve, and paste back the code the page shows. Each browser sign-in is listed on its own under Account settings, Command line (`openemail open cli`), where you can change its access or sign it out, and `openemail logout` signs it out for you. The access token is renewed as it runs out, so you stay signed in until the approval ends or you sign out. Each refresh token works once. Two commands that renew at the same moment get the same new tokens, but an old refresh token presented more than 30 seconds after it was replaced makes OpenEmail end that sign-in, as it would for a stolen token, and you sign in again. Sign in on each machine rather than copying `config.json` between them. Someone has to approve it, so with `--no-input`, in CI or with no terminal attached `login` exits 2 instead of waiting for a browser, and points at `--with-token`.
|
|
66
|
+
|
|
67
|
+
A browser sign-in acts for you as a person, so a sensitive change, such as adding a webhook, creating or turning on a rule, changing a role or a member, or removing a domain, asks for the same verification code the web app asks for. The code is emailed to you, or comes from your authenticator app or a backup code when two-factor sign-in is on. One code covers that sign-in for 60 minutes, and `openemail verify` asks for it ahead of time, before a script runs, while `openemail verify --status` shows whether it is verified and until when. Without a terminal to ask in, a command that needs a code exits 4 and points at `openemail verify`.
|
|
68
|
+
|
|
69
|
+
A code allows 5 tries, and each sign-in can ask for 5 codes an hour and 20 a day. After 10 wrong codes within 24 hours, verification for that sign-in is paused, and after 20 wrong codes within 24 hours across all your connected apps, codes are paused for every app, while the website keeps working. Either way the CLI says when verification resumes and exits 4 without offering another code.
|
|
70
|
+
|
|
71
|
+
**With an API key.** Create one in OpenEmail under Settings, API keys, then save it without it touching your shell history:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
openemail login --with-token < key.txt
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
An API key never opens a browser and never asks for a code, which makes it the choice for scripts, servers and CI. You can also skip saving it: set `OPENEMAIL_API_KEY`, or pass `--api-key` to a single command. The CLI uses `--api-key` first, then a profile named with `--profile`, then `OPENEMAIL_API_KEY`, then `OPENEMAIL_PROFILE` or the active profile. When `OPENEMAIL_API_KEY` makes a change while a profile is saved, the CLI says so on stderr, so a key left in your shell never acts in silence. With a test key (`oe_test_`) sends are accepted and recorded, but no mail is ever delivered.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
openemail whoami
|
|
81
|
+
openemail status
|
|
82
|
+
openemail login --profile work
|
|
83
|
+
openemail profile use work
|
|
84
|
+
openemail logout
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`whoami` shows the workspace, the mode, the scopes and when a browser sign-in expires, and `status` adds the addresses you can send from and whether your domains are verified. Each sign-in is a profile, so one machine can hold several workspaces. `login --profile <name>` saves a new one without changing which profile is active, unless none is. Pick one per command with `--profile <name>` or `OPENEMAIL_PROFILE`, or make it the default with `openemail profile use <name>`.
|
|
88
|
+
|
|
89
|
+
A profile only ever talks to the API it signed in to. When `--base-url` or `OPENEMAIL_BASE_URL` names another origin, the command exits 2 instead of sending that profile's token there. To use another API, sign in to it as its own profile with `openemail login --profile <name> --base-url <url>`. Commands that send no credential, such as `temp new`, `docs` and `open`, follow those settings whatever profile is active. A disposable inbox's token is kept with the API that issued it, so `temp read`, `temp watch` and `temp delete` exit 2 rather than send it anywhere else. Plain `http` is refused for every origin except localhost, a 127.x.x.x address and ::1, because anyone on the network could read the token. An origin on 0.0.0.0 or [::], which are addresses a server listens on, exits 2 and names the address to use instead.
|
|
90
|
+
|
|
91
|
+
### Mail
|
|
92
|
+
```bash
|
|
93
|
+
openemail inbox
|
|
94
|
+
openemail inbox --unread --limit 50
|
|
95
|
+
openemail search invoice from:ada has:pdf newer_than:30d
|
|
96
|
+
openemail read <thread-id>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`inbox` lists threads newest first with a dot on unread ones. It takes a folder (`sent`, `archive`, `starred`, `snoozed`, `spam`, `trash` or a label id), `--query` for the same search syntax as the app, and `--all` for every page. `read` prints every message on a thread and turns an HTML-only message into readable text, then marks the thread read unless you pass `--no-mark-read`. `read --html --message <n>` prints only that message's raw HTML on stdout, with the headers on stderr, so `> message.html` saves a clean file.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
openemail send --to ada@example.com --subject "Lunch?" --text "Thursday at noon works for me."
|
|
103
|
+
cat report.md | openemail send --from you@acme.com --to team@acme.com --subject "Weekly report" --attach chart.png
|
|
104
|
+
openemail send --to ada@example.de --subject "Invoice" --body-file invoice.html --translate de --at 2h
|
|
105
|
+
openemail reply <thread-id> --all --text "Thanks, that works."
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The body comes from `--text`, `--html`, `--body-file`, whatever you pipe in, or your `$EDITOR`. Without `--from` the only address you can send from is used, or you pick one in a terminal. A piped body turns that picker off, as any unattended run does, so pass `--from` when you can send from more than one address. `--attach` sends files up to 5 MB in total inline and uploads a larger set first. `--at` schedules the send, `--undo` holds it for up to 15 minutes so it can still be cancelled, `--template` sends a stored template with `--props`, and `--translate` delivers it translated into the language you name. Every send carries an idempotency key, so a request the CLI repeats, after renewing a token or asking for a verification code, never sends twice. Pass your own `--idempotency-key` to make that hold when a script runs the command again.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
openemail archive <thread-id> <thread-id>
|
|
112
|
+
openemail trash <thread-id>
|
|
113
|
+
openemail star <thread-id>
|
|
114
|
+
openemail mark unread <thread-id>
|
|
115
|
+
openemail snooze <thread-id> --until 3h
|
|
116
|
+
openemail label add <thread-id> --label USER_RECEIPTS
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Each of these takes several thread ids at once and reports every one.
|
|
120
|
+
|
|
121
|
+
### Disposable inboxes
|
|
122
|
+
```bash
|
|
123
|
+
ADDRESS=$(openemail temp new --ttl 15)
|
|
124
|
+
openemail temp watch --first
|
|
125
|
+
openemail temp read
|
|
126
|
+
openemail temp delete --yes
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
A disposable inbox needs no account and no key. `temp new` prints the address, and the CLI keeps the inbox's token so the other `temp` commands find it by its id or address. `temp watch --first` waits for the first message, which suits a script waiting for a sign-up code.
|
|
130
|
+
|
|
131
|
+
### Every resource
|
|
132
|
+
```bash
|
|
133
|
+
openemail domains list
|
|
134
|
+
openemail domains create --domain example.com
|
|
135
|
+
openemail webhooks create --url https://hooks.acme.com/openemail --event-types email.received,email.bounced
|
|
136
|
+
openemail contacts create --email grace@example.com --name "Grace Hopper"
|
|
137
|
+
openemail keys list --all --max 100
|
|
138
|
+
openemail files download <file-id> --out report.pdf
|
|
139
|
+
openemail emails send --data @message.json
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Every method of the OpenEmail SDK is a command, `openemail <resource> <verb>`. Positional ids come first, body fields are flags named after them, and `--data` takes the whole body as JSON inline, from a file (`@message.json`) or from stdin (`-`), with any flag overriding its keys. A list verb prints one page and the cursor for the next, `--all` walks every page, `--max` stops early, and `--ndjson` prints one JSON object per line. With `--json` a list is always one document, `{ items, hasMore, nextCursor }`, whether stdout is a terminal or a pipe, and `--all` piped without `--json` prints NDJSON. A table that is too wide for the terminal leaves out its last columns and says which. Deleting, revoking, rotating and cancelling ask first unless you pass `--yes`, and so does `openemail api` for a `DELETE` or any call those verbs would confirm. A secret such as the Resend key of `provider-imports` takes `-` for standard input or `@path` for a file, so it stays out of your shell history. `openemail <resource> <verb> --help` shows the flags with their types, the scopes the call needs, the endpoint it calls and examples, and `--help --json` prints the same as data.
|
|
143
|
+
|
|
144
|
+
Anything else in the REST API is one command away, with the same sign-in, token renewal and verification codes:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
openemail api GET /threads --query folder=inbox --query limit=5
|
|
148
|
+
openemail api POST /labels --data '{"name":"Receipts"}'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### AI
|
|
152
|
+
```bash
|
|
153
|
+
openemail ai translate --to de --subject "Your invoice" --text "The invoice is attached."
|
|
154
|
+
openemail ai compose "say yes to Tuesday at 3pm" --thread <thread-id> --tone friendly
|
|
155
|
+
openemail ai summarize <thread-id>
|
|
156
|
+
openemail ai languages
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`ai translate` works with an API key or a browser sign-in. `ai compose` writes a body in your own style and prints it, so you can pipe it into `openemail send`, and `ai summarize` prints the summary OpenEmail keeps for a thread. Those two run through the MCP server, so they need a browser sign-in. Every AI call except `languages` spends actions from the workspace's daily allowance.
|
|
160
|
+
|
|
161
|
+
### MCP
|
|
162
|
+
```bash
|
|
163
|
+
openemail mcp config --client claude-code
|
|
164
|
+
openemail mcp tools
|
|
165
|
+
openemail mcp call listThreads --arg folder=inbox --arg maxResults=5
|
|
166
|
+
openemail mcp serve
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`mcp config` prints the exact snippet or command for Claude Code, Claude Desktop, Cursor, VS Code, Windsurf and Codex. A client can connect to the remote server URL and run its own browser sign-in, or start `npx -y @openemail/cli mcp serve`, a local stdio bridge that reuses this CLI's sign-in. `mcp tools` and `mcp call` use the server from your terminal. All of it needs a browser sign-in, because API keys cannot reach the MCP server.
|
|
170
|
+
|
|
171
|
+
An MCP tool that makes the same sensitive change as a guarded REST call answers `Refused (step_up_required)` until the sign-in is verified. `mcp call` asks for the code in an interactive terminal and calls the tool again. A client behind `mcp serve` cannot type a code, so `mcp serve` passes the refusal to it unchanged and prints one line on stderr telling you to run `openemail verify`. Run it with the same profile, or choose Allow changes for 60 minutes on that sign-in under Account settings, Command line on the website, and the client's tools run without a code for the next 60 minutes. A client connected through the remote server URL is a connected app of its own, and the same menu allows it. The CLI reads only the `Refused (step_up_required)` prefix, and a client should do the same, because the sentence after it may change.
|
|
172
|
+
|
|
173
|
+
### Documentation
|
|
174
|
+
```bash
|
|
175
|
+
openemail docs ask "how do I verify a domain?"
|
|
176
|
+
openemail docs read cli/commands
|
|
177
|
+
openemail docs open api/authentication
|
|
178
|
+
openemail open billing
|
|
179
|
+
openemail open forwarding you@acme.com
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`docs ask` answers from the documentation and lists its sources, and it never sends your credentials. `docs read` prints a page as Markdown. `openemail open <page>` opens the web app at a page, which covers what the CLI does not do yet: billing, workspaces, account security, data export, calendar changes, forwarding, linking a DNS provider and the assistant chat. `openemail open forwarding <address>` opens the forwarding of one address, and `openemail open providers` (or `dns`) opens DNS provider linking. Any path that starts with `/` opens as it is.
|
|
183
|
+
|
|
184
|
+
### Scripting
|
|
185
|
+
```bash
|
|
186
|
+
openemail inbox --unread --json | jq -r ".items[].id" | xargs openemail archive
|
|
187
|
+
openemail keys list --all > keys.ndjson
|
|
188
|
+
PROFILE=$(openemail profile current)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Data goes to stdout. Progress, notes, warnings and errors go to stderr. With `--json` stdout holds only JSON, an error is one `{"error":{...}}` line on stderr with its `code`, `message`, `next`, `requestId` and `exitCode`, and nothing ever prompts. `--no-input`, a `CI` variable or a missing terminal also turn prompts off, so a missing value fails with a usage error that names the flag. `--yes` confirms destructive actions, and a destructive command run unattended without it refuses. `--yes` never skips a verification code. `--debug` adds the request id, the failed request and a stack trace.
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
openemail domains delete <domain-id> --dry-run
|
|
195
|
+
openemail send --to ada@example.com --subject "Hi" --text "Hello" --dry-run --json
|
|
196
|
+
openemail --help --json | jq -r '.commands[].command'
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`--dry-run` works on every command but `mcp serve`, whose stdout is the protocol. Reads still run, then the first request that would change something is printed instead of sent, and the command exits 0: its method, the full URL, the headers with the `Authorization` value redacted, and the JSON body, or the size and type of an upload. That covers every method but GET and HEAD, an MCP tool call, and the sign-in and sign-out requests of `login` and `logout`, while token renewal and `docs ask` still run. Confirmations are skipped, since nothing is sent. With `--json` the plan is one document, `{"dryRun":true,"request":{"method","url","headers","body","raw"}}`, and a change that stays on this machine, such as `profile use` or forgetting a saved API key, prints `{"dryRun":true,"local":{"action","profile"}}` instead. A command that shows what it read before its first change, such as `read` marking the thread read, prints that first.
|
|
200
|
+
|
|
201
|
+
`--help --json` on the root, a group or a command prints the command tree as one JSON document: for every command its path, aliases, summary, description, usage, arguments, flags (with their kind, whether they are required or repeatable, and their choices), the sign-in it needs, the scopes it needs, whether it is destructive and examples, and for a resource command the SDK method, the HTTP method and path and the shape it returns. The global flags and the exit codes come with it. `openemail help <command> --json` prints the same.
|
|
202
|
+
|
|
203
|
+
| Exit code | Meaning |
|
|
204
|
+
| --- | --- |
|
|
205
|
+
| 0 | Success, or a dry run that stopped before its first change |
|
|
206
|
+
| 1 | Unexpected failure, or a server error |
|
|
207
|
+
| 2 | Bad arguments, an unknown command or flag, a missing value with no terminal to ask, a plain `http` origin, or an API origin the profile did not sign in to |
|
|
208
|
+
| 3 | Not signed in, or the sign-in expired or was revoked |
|
|
209
|
+
| 4 | Not allowed: a missing scope (`insufficient_scope`), the owner only, or a verification code that could not be asked for or is paused |
|
|
210
|
+
| 5 | Not found |
|
|
211
|
+
| 6 | Conflict |
|
|
212
|
+
| 7 | The API refused the input |
|
|
213
|
+
| 8 | Rate limited, or the AI allowance is spent |
|
|
214
|
+
| 9 | Network error or timeout |
|
|
215
|
+
| 10 | Cancelled at a prompt or in the browser |
|
|
216
|
+
| 130, 143 | Interrupted by Ctrl+C, or terminated |
|
|
217
|
+
|
|
218
|
+
`openemail completion bash`, `zsh` or `fish` prints a completion script for every command and flag.
|
|
219
|
+
|
|
220
|
+
When the saved sign-in does not have a scope a command needs, the command exits 4 with code `insufficient_scope` before it asks anything or sends a request, and says what to do next. A browser sign-in gets more access in Account settings, Command line (`openemail open cli`, then Edit access), or by signing in again with `openemail login --force` and choosing more access. With an API key, use a key that has the scope. The scopes a saved profile was given are checked with the API once before the command refuses, so access given on the website after the sign-in counts straight away. A key from `--api-key` or `OPENEMAIL_API_KEY` is not checked ahead, and the API answers for it.
|
|
221
|
+
|
|
222
|
+
### For AI agents
|
|
223
|
+
```bash
|
|
224
|
+
openemail agents
|
|
225
|
+
openemail agents --json
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`openemail agents` prints a short guide in Markdown for Claude Code, Codex, a CI job or any other program that drives the CLI, and `--json` prints the same guide as data. In short:
|
|
229
|
+
|
|
230
|
+
- Sign in without a person: set `OPENEMAIL_API_KEY` or pass `--api-key`, or reuse a browser sign-in that a person made once with `openemail login`. The CLI never prompts without a terminal.
|
|
231
|
+
- Pass `--json` to every command, branch on the exit code, and read a failure from the `{"error":{...}}` line on stderr, where `code` says what happened and `next` what to do.
|
|
232
|
+
- Find commands with `--help --json`, preview a change with `--dry-run`, and pass `--yes` only for a destructive change you intend.
|
|
233
|
+
- A list prints one page with `nextCursor`. `--cursor` takes the next page, `--all` walks every page, and `--ndjson` prints one object per line.
|
|
234
|
+
- An API key never needs a verification code. A browser sign-in needs one before a sensitive change, which an agent cannot type: a person runs `openemail verify` first, or allows that sign-in for 60 minutes under Account settings, Command line, or the command exits 4 with `step_up_required`.
|
|
235
|
+
- `openemail mcp serve` connects an MCP client through this CLI's browser sign-in.
|
|
236
|
+
|
|
237
|
+
### Environment variables
|
|
238
|
+
|
|
239
|
+
| Variable | Effect |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `OPENEMAIL_API_KEY` | An API key to use instead of the active profile. `--profile` still picks a saved one |
|
|
242
|
+
| `OPENEMAIL_PROFILE` | The saved profile to use |
|
|
243
|
+
| `OPENEMAIL_BASE_URL` | The API origin to call, `https://api.openemail.uk` by default. A saved profile refuses any origin but its own |
|
|
244
|
+
| `OPENEMAIL_APP_URL` | The web app origin for sign-in, `open` and docs links, which follow it whatever profile is active. Unset, they use the web app the profile signed in through, or `https://openemail.uk` |
|
|
245
|
+
| `OPENEMAIL_CONFIG_DIR` | Where profiles and inbox tokens are kept, `~/.openemail` by default |
|
|
246
|
+
| `OPENEMAIL_NO_UPDATE_CHECK`, `OPENEMAIL_DISABLE_UPDATE_NOTICE` | Never check npm for a newer version |
|
|
247
|
+
| `NO_COLOR`, `FORCE_COLOR=0` | Turn colour off, as `--no-color` does |
|
|
248
|
+
| `CI` | Never prompt and never open a browser |
|
|
249
|
+
| `VISUAL`, `EDITOR` | The editor `send` and `reply` open for a body |
|
|
250
|
+
|
|
251
|
+
### Configuration files
|
|
252
|
+
Everything the CLI keeps lives in `~/.openemail`, a directory only you can read (mode 0700):
|
|
253
|
+
|
|
254
|
+
- `config.json` holds your profiles: API keys, and the access and refresh tokens of browser sign-ins. It is written atomically with mode 0600. A damaged file is reported once, read as empty, and kept as `config.json.bak` the next time the CLI saves.
|
|
255
|
+
- `temp-mail.json` holds the tokens of the disposable inboxes this CLI created, also mode 0600.
|
|
256
|
+
- `update-check.json` remembers the last check for a new version, for a day.
|
|
257
|
+
|
|
258
|
+
Removing the directory signs every profile out of this machine, but it does not revoke anything. Run `openemail logout --all` first to remove the browser sign-ins from your account too.
|
|
259
|
+
|
|
260
|
+
### Updating
|
|
261
|
+
```bash
|
|
262
|
+
openemail update
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Once a day the CLI checks npm in the background and, when a newer version is out, prints a notice on stderr after the command finishes. It never updates itself: `openemail update` checks now and prints the install command for the package manager you used. The check is skipped for `--help`, `--version` and `--json`, in CI and when stdout or stderr is not a terminal, and `OPENEMAIL_NO_UPDATE_CHECK=1` turns it off.
|
|
266
|
+
|
|
267
|
+
The full documentation, with every command and flag, lives at [openemail.uk/docs/cli](https://openemail.uk/docs/cli). What changed in each release is in the [changelog](https://openemail.uk/docs/cli/changelog).
|