Awesome
<div align="center"> <img src="./logo.svg" alt="Logo" width="128" height="128" /> <h1>📫 Himalaya REPL</h1> <p><strong>REPL</strong> to manage emails, based on <a href="https://crates.io/crates/email-lib"><code>email-lib</code></a></p> <p> <a href="https://github.com/pimalaya/himalaya-repl/releases/latest"><img alt="Release" src="https://img.shields.io/github/v/release/pimalaya/himalaya-repl?color=success"/></a> <!-- <a href="https://repology.org/project/himalaya-repl/versions"><img alt="Repology" src="https://img.shields.io/repology/repositories/himalaya-repl?color=success"></a> --> <a href="https://matrix.to/#/#pimalaya:matrix.org"><img alt="Matrix" src="https://img.shields.io/matrix/pimalaya:matrix.org?color=success&label=chat"/></a> </p> <p> <em>🚧 Himalaya REPL is still experimental 🚧<br>For a more stable alternative, see <a href="https://github.com/pimalaya/himalaya">Himalaya CLI</a></em> </p> </div>Features
- Multi-accounting
- Interactive configuration via wizard (requires
wizard
feature) - Mailbox, envelope, message and flag management
- Message composition based on
$EDITOR
- IMAP backend (requires
imap
feature) - Maildir backend (requires
maildir
feature) - Notmuch backend (requires
notmuch
feature) - SMTP backend (requires
smtp
feature) - Sendmail backend (requires
sendmail
feature) - Global system keyring for managing secrets (requires
keyring
feature) - OAuth 2.0 authorization (requires
oauth2
feature) - PGP encryption:
- via shell commands (requires
pgp-commands
feature) - via GPG bindings (requires
pgp-gpg
feature) - via native implementation (requires
pgp-native
feature)
- via shell commands (requires
Himalaya REPL is written in Rust, and relies on cargo features to enable or disable functionalities. Default features can be found in the features
section of the Cargo.toml
.
Installation
Pre-built binary
Himalaya REPL can be installed with a pre-built binary. Find the latest pre-release
GitHub workflow and look for the Artifacts section. You should find a pre-built binary matching your OS.
Cargo (git)
Himalaya REPL can also be installed with cargo:
$ cargo install --locked --git https://github.com/pimalaya/himalaya-repl.git
Configuration
Just run himalaya-repl
, the wizard will help you to configure your default account.
You can also manually edit your own configuration, from scratch:
- Copy the content of the documented
./config.sample.toml
- Paste it in a new file
~/.config/himalaya/config.toml
- Edit, then comment or uncomment the options you want
When using Proton Bridge, emails are synchronized locally and exposed via a local IMAP/SMTP server. This implies 2 things:
- Id order may be reversed or shuffled, but envelopes will still be sorted by date.
- SSL/TLS needs to be deactivated manually.
- The password to use is the one generated by Proton Bridge, not the one from your Proton Mail account.
[accounts.proton]
email = "example@proton.me"
backend = "imap"
imap.host = "127.0.0.1"
imap.port = 1143
imap.encryption = false
imap.login = "example@proton.me"
imap.auth.type = "password"
imap.auth.raw = "<bridge-imap-p@ssw0rd>"
message.send.backend = "smtp"
smtp.host = "127.0.0.1"
smtp.port = 1025
smtp.encryption = false
smtp.login = "example@proton.me"
smtp.auth.type = "password"
smtp.auth.raw = "<bridge-smtp-p@ssw0rd>"
Keeping your password inside the configuration file is good for testing purpose, but it is not safe. You have 2 better alternatives:
-
Save your password in any password manager that can be queried via the CLI:
imap.auth.type = "password" imap.auth.cmd = "pass show proton"
-
Use the global keyring of your system (requires the
keyring
cargo feature):imap.auth.type = "password" imap.auth.keyring = "proton-example"
Running
himalaya configure -a proton
will ask for your IMAP password, just paste the one generated previously.
Google passwords cannot be used directly. There is two ways to authenticate yourself:
Using App Passwords
This option is the simplest and the fastest. First, be sure that:
- IMAP is enabled
- Two-step authentication is enabled
- Less secure app access is enabled
First create a dedicated password for Himalaya.
[accounts.gmail]
email = "example@gmail.com"
folder.alias.inbox = "INBOX"
folder.alias.sent = "[Gmail]/Sent Mail"
folder.alias.drafts = "[Gmail]/Drafts"
folder.alias.trash = "[Gmail]/Trash"
backend = "imap"
imap.host = "imap.gmail.com"
imap.port = 993
imap.login = "example@gmail.com"
imap.auth.type = "password"
imap.auth.cmd = "pass show gmail"
message.send.backend = "smtp"
smtp.host = "smtp.gmail.com"
smtp.port = 465
smtp.login = "example@gmail.com"
smtp.auth.type = "password"
smtp.auth.cmd = "pass show gmail"
Keeping your password inside the configuration file is good for testing purpose, but it is not safe. You have 2 better alternatives:
-
Save your password in any password manager that can be queried via the CLI:
imap.auth.type = "password" imap.auth.cmd = "pass show gmail"
-
Use the global keyring of your system (requires the
keyring
cargo feature):imap.auth.type = "password" imap.auth.keyring = "gmail-example"
Running
himalaya configure -a gmail
will ask for your IMAP password, just paste the one generated previously.
Using OAuth 2.0
This option is the most secure but the hardest to configure. It requires the oauth2
and keyring
cargo features.
First, you need to get your OAuth 2.0 credentials by following this guide. Once you get your client id and your client secret, you can configure your Himalaya account this way:
[accounts.gmail]
email = "example@gmail.com"
folder.alias.inbox = "INBOX"
folder.alias.sent = "[Gmail]/Sent Mail"
folder.alias.drafts = "[Gmail]/Drafts"
folder.alias.trash = "[Gmail]/Trash"
backend = "imap"
imap.host = "imap.gmail.com"
imap.port = 993
imap.login = "example@gmail.com"
imap.auth.type = "oauth2"
imap.auth.client-id = "<imap-client-id>"
imap.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth"
imap.auth.token-url = "https://www.googleapis.com/oauth2/v3/token"
imap.auth.pkce = true
imap.auth.scope = "https://mail.google.com/"
message.send.backend = "smtp"
smtp.host = "smtp.gmail.com"
smtp.port = 465
smtp.login = "example@gmail.com"
smtp.auth.type = "oauth2"
smtp.auth.client-id = "<smtp-client-id>"
smtp.auth.auth-url = "https://accounts.google.com/o/oauth2/v2/auth"
smtp.auth.token-url = "https://www.googleapis.com/oauth2/v3/token"
smtp.auth.pkce = true
smtp.auth.scope = "https://mail.google.com/"
# If you want your SMTP to share the same client id (and so the same access token)
# as your IMAP config, you can add the following:
#
# imap.auth.type = "oauth2"
# imap.auth.client-id = "<client-id>"
# imap.auth.client-secret.keyring = "gmail-oauth2-client-secret"
# imap.auth.access-token.keyring = "gmail-oauth2-access-token"
# imap.auth.refresh-token.keyring = "gmail-oauth2-refresh-token"
#
# smtp.auth.type = "oauth2"
# smtp.auth.client-id = "<client-id>"
# smtp.auth.client-secret.keyring = "gmail-oauth2-client-secret"
# smtp.auth.access-token.keyring = "gmail-oauth2-access-token"
# smtp.auth.refresh-token.keyring = "gmail-oauth2-refresh-token"
Running himalaya configure -a gmail
will complete your OAuth 2.0 setup and ask for your client secret.
[accounts.outlook]
email = "example@outlook.com"
backend = "imap"
imap.host = "outlook.office365.com"
imap.port = 993
imap.login = "example@outlook.com"
imap.auth.type = "password"
imap.auth.cmd = "pass show outlook"
message.send.backend = "smtp"
smtp.host = "smtp.mail.outlook.com"
smtp.port = 587
smtp.encryption = "start-tls"
smtp.login = "example@outlook.com"
smtp.auth.type = "password"
smtp.auth.cmd = "pass show outlook"
Using OAuth 2.0
This option is the most secure but the hardest to configure. First, you need to get your OAuth 2.0 credentials by following this guide. Once you get your client id and your client secret, you can configure your Himalaya account this way:
[accounts.outlook]
email = "example@outlook.com"
backend = "imap"
imap.host = "outlook.office365.com"
imap.port = 993
imap.login = "example@outlook.com"
imap.auth.type = "oauth2"
imap.auth.client-id = "<imap-client-id>"
imap.auth.auth-url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
imap.auth.token-url = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
imap.auth.pkce = true
imap.auth.scope = "https://outlook.office.com/IMAP.AccessAsUser.All"
message.send.backend = "smtp"
smtp.host = "smtp.mail.outlook.com"
smtp.port = 587
smtp.starttls = true
smtp.login = "example@outlook.com"
smtp.auth.type = "oauth2"
smtp.auth.client-id = "<smtp-client-id>"
smtp.auth.auth-url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
smtp.auth.token-url = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
smtp.auth.pkce = true
smtp.auth.scope = "https://outlook.office.com/SMTP.Send"
# If you want your SMTP to share the same client id (and so the same access token)
# as your IMAP config, you can add the following:
#
# imap.auth.type = "oauth2"
# imap.auth.client-id = "<client-id>"
# imap.auth.client-secret.keyring = "outlook-oauth2-client-secret"
# imap.auth.access-token.keyring = "outlook-oauth2-access-token"
# imap.auth.refresh-token.keyring = "outlook-oauth2-refresh-token"
#
# smtp.auth.type = "oauth2"
# smtp.auth.client-id = "<client-id>"
# smtp.auth.client-secret.keyring = "outlook-oauth2-client-secret"
# smtp.auth.access-token.keyring = "outlook-oauth2-access-token"
# smtp.auth.refresh-token.keyring = "outlook-oauth2-refresh-token"
Running himalaya configure -a outlook
will complete your OAuth 2.0 setup and ask for your client secret.
From the iCloud Mail support page:
- IMAP port =
993
. - IMAP login = name of your iCloud Mail email address (for example,
johnappleseed
, notjohnappleseed@icloud.com
) - SMTP port =
587
withSTARTTLS
- SMTP login = full iCloud Mail email address (for example,
johnappleseed@icloud.com
, notjohnappleseed
)
[accounts.icloud]
email = "johnappleseed@icloud.com"
backend = "imap"
imap.host = "imap.mail.me.com"
imap.port = 993
imap.login = "johnappleseed"
imap.auth.type = "password"
imap.auth.cmd = "pass show icloud"
message.send.backend = "smtp"
smtp.host = "smtp.mail.me.com"
smtp.port = 587
smtp.encryption = "start-tls"
smtp.login = "johnappleseed@icloud.com"
smtp.auth.type = "password"
smtp.auth.cmd = "pass show icloud"
</details>
FAQ
<details> <summary>How to compose a message?</summary>An email message is a list of headers (key: val
) followed by a body. They form together a template:
Header: value
Header: value
Header: value
Body
Headers and body must be separated by an empty line.
Headers
Here a non-exhaustive list of valid email message template headers:
Message-ID
: represents the message identifier (you usually do not need to set up it manually)In-Reply-To
: represents the identifier of the replied messageDate
: represents the date of the messageSubject
: represents the subject of the messageFrom
: represents the address of the senderTo
: represents the addresses of the receiversReply-To
: represents the address the receiver should reply to instead of theFrom
headerCc
: represents the addresses of the other receivers (carbon copy)Bcc
: represents the addresses of the other hidden receivers (blind carbon copy)
An address can be:
- a single email address
user@domain
- a named address
Name <user@domain>
- a quoted named address
"Name" <user@domain>
Multiple address are separated by a coma ,
: user@domain, Name <user@domain>, "Name" <user@domain>
.
Plain text body
Email message template body can be written in plain text. The result will be compiled into a single text/plain
MIME part:
From: alice@localhost
To: Bob <bob@localhost>
Subject: Hello from Himalaya
Hello, world!
MML boby
Email message template body can also be written in MML. The MIME Meta Language was introduced by the Emacs mml
ELisp module. Pimalaya ported it in Rust.
A raw email message is structured according to the MIME standard. This standard produces verbose, non-friendly messages. Here comes MML: it simplifies the way email message body are structured. Thanks to its simple XML-based syntax, it allows you to easily add multiple parts, attach a binary file, or attach inline image to your body without dealing with the MIME standard.
For instance, this MML template:
From: alice@localhost
To: bob@localhost
Subject: MML simple
<#multipart type=alternative>
This is a plain text part.
<#part type=text/enriched>
<center>This is a centered enriched part</center>
<#/multipart>
compiles into the following MIME Message:
Subject: MML simple
To: bob@localhost
From: alice@localhost
MIME-Version: 1.0
Date: Tue, 29 Nov 2022 13:07:01 +0000
Content-Type: multipart/alternative;
boundary="4CV1Cnp7mXkDyvb55i77DcNSkKzB8HJzaIT84qZe"
--4CV1Cnp7mXkDyvb55i77DcNSkKzB8HJzaIT84qZe
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
This is a plain text part.
--4CV1Cnp7mXkDyvb55i77DcNSkKzB8HJzaIT84qZe
Content-Type: text/enriched
Content-Transfer-Encoding: 7bit
<center>This is a centered enriched part</center>
--4CV1Cnp7mXkDyvb55i77DcNSkKzB8HJzaIT84qZe--
See more examples at pimalaya/core/mml.
</details> <details> <summary>How to add attachments to a message?</summary>Read first about the FAQ: How to compose a message?.
From: alice@localhost
To: bob@localhost
Subject: How to attach stuff
Regular binary attachment:
<#part filename=/path/to/file.pdf><#/part>
Custom file name:
<#part filename=/path/to/file.pdf name=custom.pdf><#/part>
Inline image:
<#part disposition=inline filename=/path/to/image.png><#/part>
See more examples at pimalaya/core/mml.
</details> <details> <summary>How to debug Himalaya REPL?</summary>The simplest way is to use --debug
and --trace
arguments.
The advanced way is based on environment variables:
RUST_LOG=<level>
: determines the log level filter, can be one ofoff
,error
,warn
,info
,debug
andtrace
.RUST_SPANTRACE=1
: enables the spantrace (a span represent periods of time in which a program was executing in a particular context).RUST_BACKTRACE=1
: enables the error backtrace.RUST_BACKTRACE=full
: enables the full error backtrace, which include source lines where the error originated from.
All the lookup mechanisms use the email address domain as base for the lookup. It is heavily inspired from the Thunderbird Autoconfiguration protocol. For example, for the email address test@example.com
, the lookup is performed as (in this order):
- check for
autoconfig.example.com
- look up of
example.com
in the ISPDB (the Thunderbird central database) - look up
MX example.com
in DNS, and formx1.mail.hoster.com
, look uphoster.com
in the ISPDB - look up
SRV example.com
in DNS - try to guess (
imap.example.com
,smtp.example.com
…)
Sponsoring
Special thanks to the NLnet foundation and the European Commission that helped the project to receive financial support from various programs:
- NGI Assure in 2022
- NGI Zero Entrust in 2023
- NGI Zero Core in 2024 (still ongoing)
If you appreciate the project, feel free to donate using one of the following providers: