murat is a small terminal mail client with an encrypted local store.
It syncs mail over IMAPS or JMAP, stores messages locally under your XDG data directory, and provides both a fullscreen TUI and focused CLI commands for scripting.
- Encrypted local mail store using the same file locations as the Python
muratclient. - IMAPS and JMAP sync with delta fetching for already-known remote messages.
- SMTPS and JMAP sending.
- Fullscreen terminal UI with list, preview, filters, account cycling, async sync/send, reply/reply-all/forward, spam/trash/unread, header view, attachment save/open/import.
- Truecolor pixel-art rendering for CID-referenced PNG, JPEG, and GIF images directly in the message preview.
- Compose flow through
$VISUALor$EDITOR. - Murat-managed OpenPGP keys: decrypt/verify on open, encrypt/sign/self-encrypt/attach-public-key while composing.
- Address index built from all seen mail identities, exposed through
murat lspfor editor completion in compose drafts.
go build ./cmd/muratThe binary is written to ./murat if you pass -o murat:
go build -o murat ./cmd/muratmurat uses these paths by default:
- Config:
~/.config/murat - Data:
~/.local/share/murat - Key:
~/.local/share/murat/key.ssh.json - Mail index:
~/.local/share/murat/mail.enc.json - Accounts:
~/.local/share/murat/accounts.enc.json - Search index:
~/.local/share/murat/search.enc.json - Raw encrypted EML blobs:
~/.local/share/murat/eml/
Show resolved paths:
murat pathsInitialize the local key:
murat init # selects a local Ed25519 SSH keyAdd an IMAP account:
murat account add-imap --email you@example.comFor Exchange Online, register a public client app in Microsoft Entra with
delegated https://outlook.office.com/IMAP.AccessAsUser.All and
https://outlook.office.com/SMTP.Send permissions, then use device-code login:
murat account add-exchange-online --email you@example.com --oauth-client-id CLIENT_IDExchange Online controls SMTP AUTH separately from the Entra application permissions. Enable Authenticated SMTP only for the mailbox that Murat uses:
- Microsoft 365 admin center -> Users -> Active users -> select the user -> Mail -> Manage email apps -> enable Authenticated SMTP.
- Or use Exchange Online PowerShell:
Get-CASMailbox -Identity you@example.com | Format-List SmtpClientAuthenticationDisabled
Set-CASMailbox -Identity you@example.com -SmtpClientAuthenticationDisabled $falseIf sending still reports SmtpClientAuthentication is disabled for this tenant, inspect the organization setting:
Get-TransportConfig | Format-List SmtpClientAuthenticationDisabledThe mailbox setting above overrides the normal organization-wide setting and is preferred over broadly enabling SMTP AUTH. If you intentionally need to enable it tenant-wide, use Set-TransportConfig -SmtpClientAuthenticationDisabled $false. Microsoft Entra Security Defaults disables SMTP AUTH regardless; using SMTP AUTH requires disabling Security Defaults and replacing its protections appropriately. See Microsoft's SMTP AUTH configuration guide.
Or add a JMAP account:
murat account add-jmap --email you@example.comSync and open the TUI:
murat sync
murat tuiRunning murat without arguments starts the TUI.
Common commands:
murat init --ssh-key ~/.ssh/id_ed25519
murat account list
murat account add-imap
murat account add-exchange-online
murat account add-jmap
murat account edit-imap --host imap.example.com --port 993 --mailbox INBOX ACCOUNT_ID
murat account edit-smtp --host smtp.example.com --port 587 ACCOUNT_ID
murat account edit-jmap --session-url https://mail.example.com/.well-known/jmap ACCOUNT_ID
murat export BACKUP.murat
murat import BACKUP.murat
murat sync [--account ID_OR_EMAIL] [--limit N]
murat compose --to you@example.com
murat list
murat open MESSAGE_PREFIX
murat read MESSAGE_PREFIX
murat unread MESSAGE_PREFIX
murat save-attachments MESSAGE_PREFIX [DIR]
murat import-eml FILE_OR_DIR...
murat lsp
murat paths
murat versionsync fetches only new remote messages after the initial local remote-id index is known.
Useful keys:
j/k: move selectionenter: open messagespace: message actionsf: filters/: searchs: syncc: composea: cycle accountsq: back or quit
CID-referenced images in HTML mail are rendered at their document position using ANSI half-block pixels. Images preserve their aspect ratio, are never upscaled, and are bounded to 48 columns and 24 terminal rows. Source width is always capped at eight pixels per terminal column so small icons stay compact, including when HTML dimensions are present. JPEG EXIF orientation is applied before rendering. Click a rendered image to open its MIME attachment with the system handler. Ordinary image attachments remain in the attachment menu.
Message action menu:
r: replyR: reply allf: forwardh: toggle headersa: attachmentsu: mark unreadt: move to trashs: toggle spam
Compose opens $VISUAL, then $EDITOR, then vi.
Draft files use mail-like headers:
From: you@example.com
To: someone@example.com
Cc:
Bcc:
Subject: Hello
Body text here.
Insert a PNG, JPEG, or GIF inline by writing ![[path/to/image]] in the body. The compose confirmation renders it as terminal pixels; click the rendered image to open it. The sent HTML message references a matching inline MIME part by cid:/Content-ID. Relative paths are resolved from Murat's working directory; ~/... is also supported.
You can edit From: to send from another configured account. Replies pick the best initial sender from the message account and recipient headers. PGP options are controlled from the compose confirmation menu, not editable draft headers.
Press d in the compose confirmation view to save a local encrypted draft. Failed sends save or update a local draft automatically. Use the D filter to list drafts; open one and press space, then e, to resume editing.
CLI compose examples:
murat compose --to someone@example.com
murat compose --from other@example.com --to someone@example.com
murat compose --to someone@example.com --pgp encrypt,signPGP keys are managed and encrypted by Murat. Create keys with murat pgp create --email you@example.com; import existing armored keys with murat pgp import FILE. Import prompts for the passphrase of each protected secret key before storing it in Murat's encrypted keyring.
While confirming a draft in the TUI, press g to open the PGP submenu. Available options are hidden unless usable:
s: sign, only if a secret key exists forFrom:a: attach public key, only if a secret key/public key exists forFrom:e: encrypt, only if all recipient public keys are knownE: self-encrypt, only if encryption is available and a sender key exists
Opening mail detects inline PGP messages/signatures and decrypts or verifies them with Murat-managed keys.
murat keeps a known-address index from every address it has seen in mail headers and sent drafts.
murat lsp starts a small stdio language server that completes compose draft headers:
From:configured account identities onlyTo:known identitiesCc:known identitiesBcc:known identities
When launching $EDITOR, murat prepends the current executable to PATH, so editor LSP configs can use murat lsp reliably.
Example Helix config:
[language-server.murat]
command = "murat"
args = ["lsp"]
[[language]]
name = "murat-compose"
scope = "source.murat-compose"
file-types = [{ glob = "murat-compose-*.txt" }, { glob = "**/murat-compose-*.txt" }]
language-servers = ["murat"]- Inline image rendering only decodes image data already present in the local MIME message. It never fetches remote or data-URL image sources, and applies count, byte-size, dimension, and decoded-pixel limits.
murat initwraps Murat's local store key with a selected Ed25519 SSH key. It usesssh-agentwhen available and otherwise prompts for that SSH key's passphrase.- Account secrets are stored inside the encrypted account store.
murat exportwrites account secrets and Murat-managed PGP secret keys into one native encrypted archive;murat importaccepts that same archive format only and prompts for its backup passphrase.- PGP keys are managed by Murat and stored encrypted with the local store key; use
murat pgpto list, create, import, or export them.
