Library / SDK
lettre/lettre avatar
lettre/lettre

lettre: the Rust mailer that grew a TLS feature matrix

a mailer library for Rust

2,267 stars234 forksRustMIT

At a glance

What is it?
A mail library for Rust that treats transport selection as a feature flag problem, from plain relay through starttls to rustls, native-tls and a boring backend.
Who is it for?
lettre's shape follows from treating delivery as a negotiation rather than a single endpoint. The builder produces a message, the transport decides how to hand it over, and a set of Cargo features lets you pick the TLS stack without rewriting the send call.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 28 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A library named after the French word for letter

lettre is a mailer library for Rust, and it is one of the more established choices in that space. GitHub reports 2,267 stars and 234 forks for lettre/lettre, with 86 open issues. The default branch is `master`, the last recorded push was 2026-09-09, and the license is MIT with the project site at lettre.rs and the API docs hosted on docs.rs.

The self-description in the repository metadata is six words: a mailer library for Rust. The GitHub topics say more, listing email, mailer, mailer-library, sendmail, smtp, rust, and hacktoberfest.

The name is the French word for a letter, and that turns out to matter for discovery in a way that is easy to underestimate. Searching the bare word in English returns handwriting guides, penpal apps and dictionary entries rather than a mail library. The word also shares a spelling with several commercial apps, so a naive search for the package name is a poor way to find the source.

The project's own scope statement is worth reading closely, because the list of what it does not do is short and specific. It provides multiple transport methods, Unicode support for both content and addresses, secure delivery over SMTP with encryption and authentication, an email builder, and async support. What it does not provide, the README says plainly, is email parsing.

Adding the dependency and building a message

Installation is a single dependency line in `Cargo.toml`:

toml
[dependencies]
lettre = "0.11"

The example that follows is the clearest statement of the library's ergonomics. A `Message` is built with a chain of methods that reads as a description of the email: a sender, a reply-to, a recipient, a subject, a content type header, and a body. Every address is wrapped in a `Mailbox`, which pairs an optional display name with a parsed address, so a `None` name means no name is emitted.

The example, marked as not run by the compiler:

```rust,no_run use lettre::message::{Mailbox, header::ContentType}; use lettre::transport::smtp::authentication::Credentials; use lettre::{Message, SmtpTransport, Transport};

fn main() { let email = Message::builder() .from(Mailbox::new(Some("NoBody".to_owned()), "[email protected]".parse().unwrap())) .reply_to(Mailbox::new(Some("Yuin".to_owned()), "[email protected]".parse().unwrap())) .to(Mailbox::new(Some("Hei".to_owned()), "[email protected]".parse().unwrap())) .subject("Happy new year") .header(ContentType::TEXT_PLAIN) .body(String::from("Be happy!")) .unwrap();

let creds = Credentials::new("smtp_username".to_owned(), "smtp_password".to_owned());

// Open a remote connection to gmail let mailer = SmtpTransport::relay("smtp.gmail.com") .unwrap() .credentials(creds) .build();

// Send the email match mailer.send(&email) { Ok(_) => println!("Email sent successfully!"), Err(e) => panic!("Could not send email: {e:?}"), } } ```

Three details in that chain matter. The `header` call takes a typed content type rather than a raw string, so headers are checked at compile time. The `.unwrap()` at the end of the builder is where malformed addresses or header values surface, which means message construction fails before any network work begins. And the final `unwrap()` on `SmtpTransport::relay` means a hostname that cannot be parsed is caught when the transport is built rather than when the first message goes out.

Transports as a feature matrix

The Cargo manifest is where the real design shows. Everything except the message parser sits behind optional dependencies, so what lettre can do depends on which features you enable.

The parsing side is thin: `nom` for the parser, `email_address` for address validation and `idna` for internationalised domain names. Optional pieces cover the builder (`httpdate`, `mime`, `fastrand`, `quoted_printable`, `base64`, `email-encoding`), the file transport (`uuid`, `serde`, `serde_json`) and the SMTP transport (`hostname`, `socket2`, `url`, `percent-encoding`).

TLS is the part with real choice. `native-tls` at version 0.2 and `rustls` at 0.23 are both listed as optional, and the features list continues past what the manifest excerpt shows, including a `rustls-platform-verifier` dependency that shows up in the release history. There is also a boring backend, and a `rustls-no-provider` variant added in version 0.11.21 so you can install a crypto provider yourself rather than accepting the library's default.

That set of examples in the repository explains the shape better than the feature list does. There are separate files for `smtp`, `smtp_starttls`, `smtp_tls` and `smtp_selfsigned`, then the same three variants again under `asyncstd1_` and `tokio1_` prefixes, plus `basic_html`, `maud_html` and `autoconfigure`. Reading them side by side is the fastest way to understand the difference between a builder API and a transport choice.

Let the library guess your connection settings

The README has a short section headed Not sure of which connect options to use, and the answer is a runnable example rather than a paragraph. Clone the repository and pass the hostname:

shell
cargo run --example autoconfigure SMTP_HOST

That example exists because choosing between plain relay, starttls and implicit TLS by hand is the most common early mistake. Guessing wrong tends to surface as an authentication error on an already encrypted channel, or as a connection that opens and then hangs. An example that inspects what the host actually supports turns that trial and error into one command.

The troubleshooting section covers the same ground for people who are not running the example. It starts with basic connectivity and open ports, then checks that the provider permits traffic on the ports in use, then SMTP relay authentication, then DNS records with DMARC, SPF, DKIM and MX named explicitly, then SSL and TLS certificates. The last item is the one people forget: filtering, formatting and file size limits at relays and remote hosts, which cause messages to be silently lost or delayed rather than rejected with a useful error.

Testing is documented with the same practicality. The test suite expects an open mail server on local port 2525 plus the `sendmail` command, and the README shows how to start one with Python if you have it:

shell
python -m smtpd -n -c DebuggingServer 127.0.0.1:2525

If you only want the unit tests, `cargo test --lib` skips the mail server requirement entirely. That distinction is worth knowing before you clone, because the full suite fails in a confusing way without the local server.

A TLS verification bug worth knowing about

Version 0.11.22 is the release that matters most for anyone running the boring backend, and its title is unambiguous about why: update now if you're using Boring TLS.

The security entry is specific. An inverted TLS hostname verification flag in the `boring-tls` backend had silently disabled hostname verification. That is the kind of defect with no visible symptom: connections succeed, certificates are accepted, and nothing warns you that the certificate is not being checked against the hostname you asked for. The fix is a single commit, and the release pairs it with a cap on the `read_response` buffer to prevent unbounded memory growth, plus an upgrade of `rustls-platform-verifier` to version 0.7.

The surrounding releases are ordinary maintenance. Version 0.11.23, published 2026-08-03, fixed a parsing bug where lines in a multiline SMTP error reply were not separated properly, and upgraded `base64` to 0.23. That one is worth a second look if you have been debugging an error message that looked garbled, because a mis-split server reply is easy to mistake for a server problem. Version 0.11.21, from 2026-04-04, added the `rustls-no-provider` support and a `message_iter` method on `AsyncConnection` and `Connection`.

The cadence, roughly every three months with one security release in that window, is what you would expect from a library that other people's password reset flows depend on.

The README and the manifest disagree on Rust versions

This is the one place where the project's own documentation contradicts itself, so it is worth knowing which number to trust.

The README has a Supported Rust Versions section that says lettre supports all Rust versions released in the last six months, and then states that at the time of writing the minimum supported Rust version is 1.74. The example section repeats it, saying the library requires Rust 1.74 or newer.

The manifest says something different. `Cargo.toml` declares `edition = "2024"` and `rust-version = "1.85"`. The 2024 edition alone settles the question in practice, because that edition requires a 1.85 or newer toolchain to compile at all.

There is a comment in the manifest acknowledging the drift in a different place, noting that the version needs updating in `Cargo.toml` and in the README example and in the deps.rs badge together. The dependency badge at the top of the README points at version 0.11.23, matching the manifest version, so at least that pair is in sync.

The mechanism behind the mismatch is stated in the README itself: the minimum can change at any time, either because a dependency raises its own minimum or because a patch release of lettre does so. That is an honest explanation, and it is also why a number written in prose is less reliable than the number in the manifest. If you are pinning a toolchain, read `rust-version` from the manifest for the version you actually depend on.

The crate is also marked with an is-it-maintained badge block declaring the status as actively-developed, with two badges tracking issue resolution and open issues.

Editorial conclusion

lettre's shape follows from treating delivery as a negotiation rather than a single endpoint. The builder produces a message, the transport decides how to hand it over, and a set of Cargo features lets you pick the TLS stack without rewriting the send call. The parts most likely to reach you first are the builder chain and the relay construction, both short enough to read in a minute. The part worth checking before you deploy is version 0.11.22, where an inverted flag in the boring-tls backend had silently turned off hostname verification; if you run that backend, move to 0.11.23 or later. And take the README's stated minimum Rust version with a grain of salt, since the manifest declares a newer one. Everything else, from the autoconfigure example to the troubleshooting checklist for DNS and relay limits, is documented well enough that you will rarely be guessing.

Frequently asked questions

What is lettre used for in Rust?

It is a mailer library for Rust: it builds email messages with a chained builder, validates addresses, and sends them over SMTP or a file transport. The README lists multiple transport methods, Unicode support for content and addresses, encrypted and authenticated SMTP delivery, the builder, and async support. Parsing incoming email is explicitly not part of it.

What is the minimum Rust version lettre requires?

The README says 1.74, but the Cargo.toml manifest declares edition 2024 and rust-version 1.85, and the 2024 edition needs a 1.85 toolchain or newer to compile. Trust the manifest over the prose. The README also notes the minimum can rise at any time, either because a dependency raises its own minimum or because a lettre patch release does.

Which TLS backend should I use with lettre?

Several are available as Cargo features, including native-tls, rustls and a boring backend, and each has its own example file in the examples directory. If you use the boring backend, update to version 0.11.22 or later: 0.11.22 fixed an inverted hostname verification flag that had silently disabled verification. A rustls-no-provider variant exists if you want to install a crypto provider yourself.

Why does lettre fail to send an email?

The README's troubleshooting list covers the usual causes in order: basic connectivity and open ports, provider restrictions on the ports used, SMTP relay authentication, DNS records including DMARC, SPF, DKIM and MX, and SSL/TLS certificate setup. It also flags filtering, formatting and file size limits at relays, which tend to cause messages to be lost or delayed instead of rejected outright. Running the autoconfigure example is a quick way to confirm what your server actually supports.

How do I run the lettre test suite?

The full suite expects an open mail server listening on local port 2525 along with the sendmail command. The README shows starting one with python -m smtpd -n -c DebuggingServer 127.0.0.1:2525 if you have Python installed. If you only want unit tests, use cargo test --lib, which avoids the mail server requirement.

Official sources

  1. lettre/lettre on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lettre-lettre.svg)](https://hysenlabs.com/projects/lettre-lettre)