Send with any body and attachments, read and search the mailbox, reply, file threads, and use disposable inboxes.
Send
openemail send --to [email protected] --subject "Lunch?" --text "Thursday at noon works for me."The body comes from the first of these that is there:
--textand--html, both together if you want both parts.--body-file <path>. A.htmlor.htmfile is sent as HTML and anything else as text, and-reads stdin.- Whatever is piped on stdin. Input that starts with an HTML tag is sent as HTML.
- Your editor (
$VISUAL, then$EDITOR), when you run it in a terminal.
Without --from, the CLI sends from the only address you can send from, or lets you pick one in a terminal. Unattended with more than one, it stops and asks for --from. In a terminal you see a summary and confirm before anything goes, which --yes skips.
| Flag | What it does |
|---|---|
| --to, --cc, --bcc | Recipients, comma separated or repeated. Ada Lovelace <[email protected]> works too |
| --from, --reply-to | The sending address, and the Reply-To header |
| -s, --subject | The subject line |
| -a, --attach <path> | Attach a file. Repeat it for more |
| --at <when> | Send later: an ISO time such as 2026-10-01T09:00:00Z, or a delay such as 10m, 2h, 1d or PT1H |
| --undo <seconds> | Hold an immediate send for up to 900 seconds, so it can still be cancelled |
| --translate <language> | Translate the email into this language before it goes |
| --template <id>, --props <json> | Send a stored template with its props instead of a body |
| --draft <id> | Send a saved draft exactly as it was written |
| --thread <id> | File the sent email into this thread |
| --tag <key=value> | Tag the email. Repeat it for more |
| --idempotency-key <key> | Running the command again with the same key never sends twice |
cat report.md | openemail send --to [email protected] --subject "Weekly report" --attach chart.pngopenemail send --to [email protected] --subject "Invoice" --body-file invoice.html --translate de --at 2hopenemail send --to [email protected] --template welcome --props '{"name":"Ada"}' --tag campaign=onboardingIt prints the email id and its status. sent, partial and failed mean the send was tried within the request, and queued and scheduled mean it is held. A failed send exits with code 1, and --json prints the whole email.
Every send carries an idempotency key, yours or one the CLI makes up, so a retry after a dropped connection never sends the message twice. Pass your own when a pipeline may run the same step again.
Attachments
--attach takes up to 20 files. While they come to 5 MB or less in total, they travel inside the message. When they come to more, each one is uploaded to Files first and the message refers to it, so a large file needs no extra step. The content type comes from the file extension.
Inbox
openemail inboxopenemail inbox --unread --limit 50openemail inbox archive --sort oldestopenemail inbox --all --json | jq -r ".items[].id"Lists one page of threads, newest first, with when each last moved, who wrote last, the subject, the labels and the thread id. A dot marks unread mail. The folder is the inbox unless you name another: sent, archive, starred, snoozed, spam, trash, draft, or a label id such as USER_RECEIPTS.
-u,--unreadkeeps threads with unread mail,-l,--labelkeeps threads that carry a label, and-q,--querynarrows the list with a search.-n,--limitsets the page size, from 1 to 100 (25 unless you say). When more remain, the next cursor is printed on stderr for--cursor, and--allreads every page.--from-contactskeeps threads whose newest message came from a saved contact, and--sortorders them bynewest,oldest,senderorsubject.--jsonprints{ items, hasMore, nextCursor }, with every thread whole.
Search
openemail search invoice from:adaopenemail search "quarterly report" has:pdf newer_than:30dopenemail search in:anywhere from:me to:ada --limit 50openemail search -- invoice -from:adaSearch takes the syntax of the search box in the app: plain words, quoted phrases, and operators such as from:, to:, subject:, has:pdf, is:unread, after:2026/01/31 and newer_than:7d. It searches the inbox, or the folder in --folder, unless the query names one with in: or is:, and in:anywhere searches every folder. It takes the flags of inbox and shows its results the same way.
A word that starts with a dash is excluded from the results, and the CLI would read it as a flag, so put -- before a query that has one.
Read a thread
openemail read CAHk7pQ2x9LmZ4openemail read CAHk7pQ2x9LmZ4 --message -1openemail read CAHk7pQ2x9LmZ4 --html --message 1 > first.htmlPrints every message on the thread, oldest first: who sent it and to whom, when, the subject, the attachments with their sizes and ids, and the body. A plain text part is shown as it is, and an HTML-only message is turned into readable text that keeps its lists, quotes, headings and links. Reading marks the thread read, unless you pass --no-mark-read.
-m,--message <n>shows one message, counting from 1, or from the newest with a negative number.--htmlprints the raw HTML instead.- An encrypted message is not decrypted here, so open it in the web app.
Reply
openemail reply CAHk7pQ2x9LmZ4 --text "Thanks, that works for me."openemail reply CAHk7pQ2x9LmZ4 --all --attach notes.pdfecho "Confirmed." | openemail reply CAHk7pQ2x9LmZ4 --yesReplies in the same thread to whoever sent the last message, or to its Reply-To address. When the last message is yours, the reply goes to the people it went to. --all copies everyone else on that message, leaving out your own addresses. The subject gets Re: unless it has it already, and the reply goes from the address the thread was delivered to when you can send from it.
It takes the body, attachment, scheduling, translation, tag and idempotency flags of send, with --from and --subject, and --to, --cc and --bcc override the recipients.
File threads
| Command | What it does |
|---|---|
| archive <thread-id...> | Out of the inbox, still searchable |
| unarchive <thread-id...> | Back into the inbox |
| trash <thread-id...> | To the Bin. It asks you to confirm, because no call takes a thread back out |
| star, unstar | Add or remove the star |
| mark read, mark unread | Mark threads read or unread |
| snooze <thread-id...> --until <when> | Hide threads until a time, or for a delay such as 3h or 1w. They come back within about an hour of it |
| unsnooze | Bring snoozed threads back now |
| label add, remove --label <id> | Put labels on threads or take them off. openemail labels list shows the ids |
Each takes several thread ids and reports on each one. The first failure sets the exit code, and --json prints { results, succeeded, failed }. Ids from a search chain straight in:
openemail inbox --query "newer_than:30d from:alerts" --json | jq -r ".items[].id" | xargs openemail archiveDisposable inboxes
openemail temp makes short-lived addresses that need no account and no sign-in, which suits sign-up tests and one-time codes. Each inbox token is kept in ~/.openemail/temp-mail.json, so later commands find an inbox by its id or address, or on their own when you have only one.
ADDRESS=$(openemail temp new --ttl 15)openemail temp watch --first --json | jq -r .snippetopenemail temp listopenemail temp readopenemail temp read tinb_4f2a tmail_91c3openemail temp delete --yestemp newprints only the address on stdout. It lives for 60 minutes unless--ttlsays otherwise, for up to 24 hours, and--nameand--domainchoose the address.temp watchprints what has arrived, then checks every 3 seconds until you stop it or the inbox expires.--firststops at the first message, and--jsonprints one JSON line per message.temp readlists the messages with a short preview, or prints one whole message.temp deletedeletes the inbox and its mail at once. The address stays reserved for 7 days after its lease would have ended.--inbox-tokenreaches an inbox this CLI did not create, intemp read,watchanddelete.- The sender of mail in a disposable inbox is never verified.