chore(all): Move docs from website into API doc

This commit is contained in:
Alexis Mousset
2020-05-02 23:22:05 +02:00
parent 70c33882cc
commit 8425f1d7c4
26 changed files with 470 additions and 781 deletions

View File

@@ -1,21 +0,0 @@
name: Website
on:
push:
branches:
- v0.9.x
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- run: cargo install mdbook --no-default-features --features output,search
- run: cd website && mdbook build
- run: echo "lettre.at" > website/book/html/CNAME
- name: Deploy
uses: peaceiris/actions-gh-pages@v2.5.1
env:
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }}
PUBLISH_BRANCH: gh-pages
PUBLISH_DIR: ./website/book/html

View File

@@ -1,24 +1,31 @@
# lettre
<h1 align="center">lettre</h1>
<div align="center">
<strong>
A mailer library for Rust
</strong>
</div>
**Lettre is a mailer library for Rust.**
<br />
![](https://github.com/lettre/lettre/workflows/Continuous%20integration/badge.svg)
[![Coverage Status](https://coveralls.io/repos/github/lettre/lettre/badge.svg?branch=master)](https://coveralls.io/github/lettre/lettre?branch=master)
[![Crate](https://img.shields.io/crates/v/lettre.svg)](https://crates.io/crates/lettre)
[![Docs](https://docs.rs/lettre/badge.svg)](https://docs.rs/lettre/)
[![Required Rust version](https://img.shields.io/badge/rustc-1.20-green.svg)]()
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
[![Gitter](https://badges.gitter.im/lettre/lettre.svg)](https://gitter.im/lettre/lettre?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/lettre/lettre.svg)](https://isitmaintained.com/project/lettre/lettre "Average time to resolve an issue")
[![Percentage of issues still open](https://isitmaintained.com/badge/open/lettre/lettre.svg)](https://isitmaintained.com/project/lettre/lettre "Percentage of issues still open")
Useful links:
* [User documentation](https://lettre.at/)
* [API documentation](https://docs.rs/lettre/)
* [Changelog](https://github.com/lettre/lettre/blob/master/CHANGELOG.md)
<div align="center">
<a href="https://docs.rs/lettre">
<img src="https://docs.rs/lettre/badge.svg"
alt="docs" />
</a>
<a href="https://crates.io/crates/lettre">
<img src="https://img.shields.io/crates/d/lettre.svg"
alt="downloads" />
</a>
<br />
<a href="https://gitter.im/lettre/lettre">
<img src="https://badges.gitter.im/lettre/lettre.svg"
alt="chat on gitter" />
</a>
<a href="https://lettre.at">
<img src="https://img.shields.io/badge/visit-website-blueviolet"
alt="website" />
</a>
</div>
---

View File

@@ -1,5 +1,11 @@
//! Lettre provides an email builder and several email transports.
//! Lettre is an email library that allows creating and sending messages. It provides:
//!
//! * An easy to use email builder
//! * Pluggable email transports
//! * Unicode support
//! * Secure defaults
//!
//! Lettre requires Rust 1.40 or newer.
#![doc(html_root_url = "https://docs.rs/lettre/0.10.0")]
#![doc(html_favicon_url = "https://lettre.at/favicon.png")]
@@ -36,6 +42,7 @@ pub use crate::transport::smtp::client::net::TlsParameters;
pub use crate::transport::smtp::r2d2::SmtpConnectionManager;
#[cfg(feature = "smtp-transport")]
pub use crate::transport::smtp::{SmtpTransport, Tls};
pub use crate::transport::stub::StubTransport;
#[cfg(feature = "builder")]
use std::convert::TryFrom;

View File

@@ -1,4 +1,181 @@
//! Provides a strongly typed way to build emails
//!
//! ### Creating messages
//!
//! This section explains how to create emails.
//!
//! ## Usage
//!
//! ### Format email messages
//!
//! #### With string body
//!
//! The easiest way how we can create email message with simple string.
//!
//! ```rust
//! # extern crate lettre;
//! use lettre::message::Message;
//!
//! let m = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse().unwrap())
//! .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//! .to("Hei <hei@domain.tld>".parse().unwrap())
//! .subject("Happy new year")
//! .body("Be happy!")
//! .unwrap();
//! ```
//!
//! Will produce:
//!
//! ```sh
//! From: NoBody <nobody@domain.tld>
//! Reply-To: Yuin <yuin@domain.tld>
//! To: Hei <hei@domain.tld>
//! Subject: Happy new year
//!
//! Be happy!
//! ```
//!
//! The unicode header data will be encoded using _UTF8-Base64_ encoding.
//!
//! ### With MIME body
//!
//! ##### Single part
//!
//! The more complex way is using MIME contents.
//!
//! ```rust
//! # extern crate lettre;
//! use lettre::message::{header, Message, SinglePart, Part};
//!
//! let m = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse().unwrap())
//! .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//! .to("Hei <hei@domain.tld>".parse().unwrap())
//! .subject("Happy new year")
//! .singlepart(
//! SinglePart::builder()
//! .header(header::ContentType(
//! "text/plain; charset=utf8".parse().unwrap(),
//! )).header(header::ContentTransferEncoding::QuotedPrintable)
//! .body("Привет, мир!"),
//! )
//! .unwrap();
//! ```
//!
//! The body will be encoded using selected `Content-Transfer-Encoding`.
//!
//! ```sh
//! From: NoBody <nobody@domain.tld>
//! Reply-To: Yuin <yuin@domain.tld>
//! To: Hei <hei@domain.tld>
//! Subject: Happy new year
//! MIME-Version: 1.0
//! Content-Type: text/plain; charset=utf8
//! Content-Transfer-Encoding: quoted-printable
//!
//! =D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82, =D0=BC=D0=B8=D1=80!
//!
//! ```
//!
//! ##### Multiple parts
//!
//! And more advanced way of building message by using multipart MIME contents.
//!
//! ```rust
//! # extern crate lettre;
//! use lettre::message::{header, Message, MultiPart, SinglePart, Part};
//!
//! let m = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse().unwrap())
//! .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//! .to("Hei <hei@domain.tld>".parse().unwrap())
//! .subject("Happy new year")
//! .multipart(
//! MultiPart::mixed()
//! .multipart(
//! MultiPart::alternative()
//! .singlepart(
//! SinglePart::quoted_printable()
//! .header(header::ContentType("text/plain; charset=utf8".parse().unwrap()))
//! .body("Привет, мир!")
//! )
//! .multipart(
//! MultiPart::related()
//! .singlepart(
//! SinglePart::eight_bit()
//! .header(header::ContentType("text/html; charset=utf8".parse().unwrap()))
//! .body("<p><b>Hello</b>, <i>world</i>! <img src=smile.png></p>")
//! )
//! .singlepart(
//! SinglePart::base64()
//! .header(header::ContentType("image/png".parse().unwrap()))
//! .header(header::ContentDisposition {
//! disposition: header::DispositionType::Inline,
//! parameters: vec![],
//! })
//! .body("<smile-raw-image-data>")
//! )
//! )
//! )
//! .singlepart(
//! SinglePart::seven_bit()
//! .header(header::ContentType("text/plain; charset=utf8".parse().unwrap()))
//! .header(header::ContentDisposition {
//! disposition: header::DispositionType::Attachment,
//! parameters: vec![
//! header::DispositionParam::Filename(
//! header::Charset::Ext("utf-8".into()),
//! None, "example.c".as_bytes().into()
//! )
//! ]
//! })
//! .body("int main() { return 0; }")
//! )
//! ).unwrap();
//! ```
//!
//! ```sh
//! From: NoBody <nobody@domain.tld>
//! Reply-To: Yuin <yuin@domain.tld>
//! To: Hei <hei@domain.tld>
//! Subject: Happy new year
//! MIME-Version: 1.0
//! Content-Type: multipart/mixed; boundary="RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m"
//!
//! --RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m
//! Content-Type: multipart/alternative; boundary="qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy"
//!
//! --qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy
//! Content-Transfer-Encoding: quoted-printable
//! Content-Type: text/plain; charset=utf8
//!
//! =D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82, =D0=BC=D0=B8=D1=80!
//! --qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy
//! Content-Type: multipart/related; boundary="BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8"
//!
//! --BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8
//! Content-Transfer-Encoding: 8bit
//! Content-Type: text/html; charset=utf8
//!
//! <p><b>Hello</b>, <i>world</i>! <img src=smile.png></p>
//! --BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8
//! Content-Transfer-Encoding: base64
//! Content-Type: image/png
//! Content-Disposition: inline
//!
//! PHNtaWxlLXJhdy1pbWFnZS1kYXRhPg==
//! --BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8--
//! --qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy--
//! --RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m
//! Content-Transfer-Encoding: 7bit
//! Content-Type: text/plain; charset=utf8
//! Content-Disposition: attachment; filename="example.c"
//!
//! int main() { return 0; }
//! --RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m--
//!
//! ```
pub use encoder::*;
pub use mailbox::*;

View File

@@ -2,6 +2,38 @@
//! `message_id.txt`.
//! It can be useful for testing purposes, or if you want to keep track of sent messages.
//!
//! #### File Transport
//!
//! The file transport writes the emails to the given directory. The name of the file will be
//! `message_id.json`.
//! It can be useful for testing purposes, or if you want to keep track of sent messages.
//!
//! ```rust
//! # #[cfg(feature = "file-transport")]
//! # {
//! use std::env::temp_dir;
//! use lettre::{Transport, Envelope, Message, FileTransport};
//!
//! // Write to the local temp directory
//! let sender = FileTransport::new(temp_dir());
//! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse().unwrap())
//! .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//! .to("Hei <hei@domain.tld>".parse().unwrap())
//! .subject("Happy new year")
//! .body("Be happy!")
//! .unwrap();
//!
//! let result = sender.send(&email);
//! assert!(result.is_ok());
//! # }
//! ```
//!
//! Example result in `/tmp/b7c211bc-9811-45ce-8cd9-68eab575d695.json`:
//!
//! ```json
//! TODO
//! ```
use crate::{transport::file::error::FileResult, Envelope, Transport};
use std::{

View File

@@ -1,3 +1,21 @@
//! ### Sending Messages
//!
//! This section explains how to manipulate emails you have created.
//!
//! This mailer contains several different transports for your emails. To be sendable, the
//! emails have to implement `Email`, which is the case for emails created with `lettre::builder`.
//!
//! The following transports are available:
//!
//! * The `SmtpTransport` uses the SMTP protocol to send the message over the network. It is
//! the preferred way of sending emails.
//! * The `SendmailTransport` uses the sendmail command to send messages. It is an alternative to
//! the SMTP transport.
//! * The `FileTransport` creates a file containing the email content to be sent. It can be used
//! for debugging or if you want to keep all sent emails.
//! * The `StubTransport` is useful for debugging, and only prints the content of the email in the
//! logs.
#[cfg(feature = "file-transport")]
pub mod file;
#[cfg(feature = "sendmail-transport")]

View File

@@ -1,5 +1,27 @@
//! The sendmail transport sends the email using the local sendmail command.
//!
//! #### Sendmail Transport
//!
//! The sendmail transport sends the email using the local sendmail command.
//!
//! ```rust,no_run
//! # #[cfg(feature = "sendmail-transport")]
//! # {
//! use lettre::{Message, Envelope, Transport, SendmailTransport};
//!
//! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse().unwrap())
//! .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//! .to("Hei <hei@domain.tld>".parse().unwrap())
//! .subject("Happy new year")
//! .body("Be happy!")
//! .unwrap();
//!
//! let sender = SendmailTransport::new();
//! let result = sender.send(&email);
//! assert!(result.is_ok());
//! # }
//! ```
use crate::{transport::sendmail::error::SendmailResult, Envelope, Transport};
use std::{

View File

@@ -106,17 +106,13 @@ pub struct SmtpConnection {
server_info: ServerInfo,
}
macro_rules! return_err (
($err: expr, $client: ident) => ({
return Err(From::from($err))
})
);
impl SmtpConnection {
pub fn server_info(&self) -> &ServerInfo {
&self.server_info
}
// FIXME add simple connect and rename this one
/// Connects to the configured server
///
/// Sends EHLO and parses server information
@@ -126,21 +122,7 @@ impl SmtpConnection {
hello_name: &ClientId,
tls_parameters: Option<&TlsParameters>,
) -> Result<SmtpConnection, Error> {
let mut addresses = server.to_socket_addrs()?;
// FIXME try all
let server_addr = match addresses.next() {
Some(addr) => addr,
None => return_err!("Could not resolve hostname", self),
};
#[cfg(feature = "log")]
debug!("connecting to {}", server_addr);
let stream = BufStream::new(NetworkStream::connect(
&server_addr,
timeout,
tls_parameters,
)?);
let stream = BufStream::new(NetworkStream::connect(server, timeout, tls_parameters)?);
let mut conn = SmtpConnection {
stream,
panic: false,

View File

@@ -11,6 +11,170 @@
//! * AUTH ([RFC 4954](http://tools.ietf.org/html/rfc4954)) with PLAIN, LOGIN and XOAUTH2 mechanisms
//! * STARTTLS ([RFC 2487](http://tools.ietf.org/html/rfc2487))
//!
//! #### SMTP Transport
//!
//! This transport uses the SMTP protocol to send emails over the network (locally or remotely).
//!
//! It is designed to be:
//!
//! * Secured: email are encrypted by default
//! * Modern: unicode support for email content and sender/recipient addresses when compatible
//! * Fast: supports connection reuse and pooling
//!
//! This client is designed to send emails to a relay server, and should *not* be used to send
//! emails directly to the destination.
//!
//! The relay server can be the local email server, a specific host or a third-party service.
//!
//! #### Simple example
//!
//! This is the most basic example of usage:
//!
//! ```rust,no_run
//! # #[cfg(feature = "smtp-transport")]
//! # {
//! use lettre::{Message, Transport, SmtpTransport};
//!
//! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse().unwrap())
//! .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//! .to("Hei <hei@domain.tld>".parse().unwrap())
//! .subject("Happy new year")
//! .body("Be happy!")
//! .unwrap();
//!
//! // Create local transport on port 25
//! let sender = SmtpTransport::unencrypted_localhost();
//! // Send the email on local relay
//! let result = sender.send(&email);
//!
//! assert!(result.is_ok());
//! # }
//! ```
//!
//! #### Complete example
//!
//! ```todo
//! # #[cfg(feature = "smtp-transport")]
//! # {
//! use lettre::transport::smtp::authentication::{Credentials, Mechanism};
//! use lettre::{Email, Envelope, Transport, SmtpClient};
//! use lettre::transport::smtp::extension::ClientId;
//!
//! let email_1 = Email::new(
//! Envelope::new(
//! Some(EmailAddress::new("user@localhost".to_string()).unwrap()),
//! vec![EmailAddress::new("root@localhost".to_string()).unwrap()],
//! ).unwrap(),
//! "id1".to_string(),
//! "Hello world".to_string().into_bytes(),
//! );
//!
//! let email_2 = Email::new(
//! Envelope::new(
//! Some(EmailAddress::new("user@localhost".to_string()).unwrap()),
//! vec![EmailAddress::new("root@localhost".to_string()).unwrap()],
//! ).unwrap(),
//! "id2".to_string(),
//! "Hello world a second time".to_string().into_bytes(),
//! );
//!
//! // Connect to a remote server on a custom port
//! let mut mailer = SmtpClient::new_simple("server.tld").unwrap()
//! // Set the name sent during EHLO/HELO, default is `localhost`
//! .hello_name(ClientId::Domain("my.hostname.tld".to_string()))
//! // Add credentials for authentication
//! .credentials(Credentials::new("username".to_string(), "password".to_string()))
//! // Enable SMTPUTF8 if the server supports it
//! .smtp_utf8(true)
//! // Configure expected authentication mechanism
//! .authentication_mechanism(Mechanism::Plain)
//! // Enable connection reuse
//! .connection_reuse(ConnectionReuseParameters::ReuseUnlimited).transport();
//!
//! let result_1 = mailer.send(&email_1);
//! assert!(result_1.is_ok());
//!
//! // The second email will use the same connection
//! let result_2 = mailer.send(&email_2);
//! assert!(result_2.is_ok());
//!
//! // Explicitly close the SMTP transaction as we enabled connection reuse
//! mailer.close();
//! # }
//! ```
//!
//! You can specify custom TLS settings:
//!
//! ```todo
//! # #[cfg(feature = "native-tls")]
//! # {
//! use lettre::{
//! ClientSecurity, ClientTlsParameters, EmailAddress, Envelope,
//! Email, SmtpClient, Transport,
//! };
//! use lettre::transport::smtp::authentication::{Credentials, Mechanism};
//! use lettre::transport::smtp::ConnectionReuseParameters;
//! use native_tls::{Protocol, TlsConnector};
//!
//! let email = Email::new(
//! Envelope::new(
//! Some(EmailAddress::new("user@localhost".to_string()).unwrap()),
//! vec![EmailAddress::new("root@localhost".to_string()).unwrap()],
//! ).unwrap(),
//! "message_id".to_string(),
//! "Hello world".to_string().into_bytes(),
//! );
//!
//! let mut tls_builder = TlsConnector::builder();
//! tls_builder.min_protocol_version(Some(Protocol::Tlsv10));
//! let tls_parameters =
//! ClientTlsParameters::new(
//! "smtp.example.com".to_string(),
//! tls_builder.build().unwrap()
//! );
//!
//! let mut mailer = SmtpClient::new(
//! ("smtp.example.com", 465), ClientSecurity::Wrapper(tls_parameters)
//! ).unwrap()
//! .authentication_mechanism(Mechanism::Login)
//! .credentials(Credentials::new(
//! "example_username".to_string(), "example_password".to_string()
//! ))
//! .connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
//! .transport();
//!
//! let result = mailer.send(&email);
//!
//! assert!(result.is_ok());
//!
//! mailer.close();
//! # }
//! ```
//!
//! #### Lower level
//!
//! You can also send commands, here is a simple email transaction without
//! error handling:
//!
//! ```rust,no_run
//! # #[cfg(feature = "smtp-transport")]
//! # {
//! use lettre::transport::smtp::{SMTP_PORT, extension::ClientId, commands::*, client::SmtpConnection};
//!
//! let hello = ClientId::new("my_hostname".to_string());
//! let mut client = SmtpConnection::connect(&("localhost", SMTP_PORT), None, &hello, None).unwrap();
//! client.command(
//! Mail::new(Some("user@example.com".parse().unwrap()), vec![])
//! ).unwrap();
//! client.command(
//! Rcpt::new("user@example.org".parse().unwrap(), vec![])
//! ).unwrap();
//! client.command(Data).unwrap();
//! client.message("Test email".as_bytes()).unwrap();
//! client.command(Quit).unwrap();
//! # }
//! ```
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
use crate::transport::smtp::client::net::TlsParameters;

View File

@@ -1,6 +1,26 @@
//! The stub transport only logs message envelope and drops the content. It can be useful for
//! testing purposes.
//!
//! #### Stub Transport
//!
//! The stub transport returns provided result and drops the content. It can be useful for
//! testing purposes.
//!
//! ```rust
//! use lettre::{Message, Envelope, Transport, StubTransport};
//!
//! let email = Message::builder()
//! .from("NoBody <nobody@domain.tld>".parse().unwrap())
//! .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//! .to("Hei <hei@domain.tld>".parse().unwrap())
//! .subject("Happy new year")
//! .body("Be happy!")
//! .unwrap();
//!
//! let mut sender = StubTransport::new_positive();
//! let result = sender.send(&email);
//! assert!(result.is_ok());
//! ```
use crate::{Envelope, Transport};

View File

@@ -1,45 +0,0 @@
use std::{
env::{self, consts::EXE_EXTENSION},
path::Path,
process::Command,
};
use walkdir::WalkDir;
#[test]
fn book_test() {
// README needs to be compatible with latest release
//skeptic_test(Path::new("README.md"));
for entry in WalkDir::new("website").into_iter().filter(|e| {
e.as_ref()
.unwrap()
.path()
.extension()
.map(|ex| ex == "md")
.unwrap_or(false)
}) {
skeptic_test(entry.unwrap().path());
}
}
fn skeptic_test(path: &Path) {
let rustdoc = Path::new("rustdoc").with_extension(EXE_EXTENSION);
let exe = env::current_exe().unwrap();
let depdir = exe.parent().unwrap();
let mut cmd = Command::new(rustdoc);
cmd.args(&["--verbose", "--test"])
.arg("-L")
.arg(&depdir)
.arg(path);
let result = cmd
.spawn()
.expect("Failed to spawn process")
.wait()
.expect("Failed to run process");
assert!(
result.success(),
format!("Failed to run rustdoc tests on {:?}", path)
);
}

View File

@@ -1,71 +0,0 @@
#[cfg(all(test, feature = "smtp-transport", feature = "connection-pool"))]
mod test {
use lettre::{
Email, EmailAddress, Envelope, SmtpConnectionManager, SmtpTransport, Tls, Transport,
};
use r2d2::Pool;
use std::{sync::mpsc, thread};
fn email(message: &str) -> Email {
Email::new(
Envelope::new(
Some(EmailAddress::new("user@localhost".to_string()).unwrap()),
vec![EmailAddress::new("root@localhost".to_string()).unwrap()],
)
.unwrap(),
"id".to_string(),
message.to_string().into_bytes(),
)
}
#[test]
fn send_one() {
let client = SmtpTransport::new("127.0.0.1:2525", Tls::None).unwrap();
let manager = SmtpConnectionManager::new(client).unwrap();
let pool = Pool::builder().max_size(1).build(manager).unwrap();
let mut mailer = pool.get().unwrap();
let result = (*mailer).send(email("send one"));
assert!(result.is_ok());
}
#[test]
fn send_from_thread() {
let client = SmtpTransport::new("127.0.0.1:2525", Tls::None).unwrap();
let manager = SmtpConnectionManager::new(client).unwrap();
let pool = Pool::builder().max_size(2).build(manager).unwrap();
let (s1, r1) = mpsc::channel();
let (s2, r2) = mpsc::channel();
let pool1 = pool.clone();
let t1 = thread::spawn(move || {
let mut conn = pool1.get().unwrap();
s1.send(()).unwrap();
r2.recv().unwrap();
(*conn)
.send(email("send from thread 1"))
.expect("Send failed from thread 1");
drop(conn);
});
let pool2 = pool.clone();
let t2 = thread::spawn(move || {
let mut conn = pool2.get().unwrap();
s2.send(()).unwrap();
r1.recv().unwrap();
(*conn)
.send(email("send from thread 2"))
.expect("Send failed from thread 2");
drop(conn);
});
t1.join().unwrap();
t2.join().unwrap();
let mut mailer = pool.get().unwrap();
(*mailer)
.send(email("send from main thread"))
.expect("Send failed from main thread");
}
}

1
website/.gitignore vendored
View File

@@ -1 +0,0 @@
/book

View File

@@ -1,10 +0,0 @@
[book]
title = "Lettre documentation"
authors = ["Alexis Mousset"]
description = "The user documentation of the Lettre crate."
[output.html]
default-theme = "Ayu"
no-section-label = true
[output.linkcheck]

View File

@@ -1,15 +0,0 @@
# Introduction
Lettre is an email library that allows creating and sending messages. It provides:
* An easy to use email builder
* Pluggable email transports
* Unicode support (for emails and transports, including for sender et recipient addresses when compatible)
* Secure defaults (emails are only sent encrypted by default)
Lettre requires Rust 1.40 or newer. Add the following to your `Cargo.toml`:
```toml
[dependencies]
lettre = "0.10"
```

View File

@@ -1,12 +0,0 @@
# Summary
* [Introduction](README.md)
* [News](posts/_index.md)
* [Lettre 0.10 (2020.04.12)](posts/lettre-0-10.md)
* [Are we email yet? (2020.04.12)](posts/are-we-email-yet.md)
* [Creating Messages](creating-messages/email.md)
* [Sending Messages](sending-messages/_index.md)
* [SMTP Transport](sending-messages/smtp.md)
* [Sendmail Transport](sending-messages/sendmail.md)
* [File Transport](sending-messages/file.md)
* [Stub Transport](sending-messages/stub.md)

View File

@@ -1,176 +0,0 @@
### Creating messages
This section explains how to create emails.
## Usage
### Format email messages
#### With string body
The easiest way how we can create email message with simple string.
```rust
# extern crate lettre;
use lettre::message::Message;
let m = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.body("Be happy!")
.unwrap();
```
Will produce:
```sh
From: NoBody <nobody@domain.tld>
Reply-To: Yuin <yuin@domain.tld>
To: Hei <hei@domain.tld>
Subject: Happy new year
Be happy!
```
The unicode header data will be encoded using _UTF8-Base64_ encoding.
### With MIME body
##### Single part
The more complex way is using MIME contents.
```rust
# extern crate lettre;
use lettre::message::{header, Message, SinglePart, Part};
let m = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.singlepart(
SinglePart::builder()
.header(header::ContentType(
"text/plain; charset=utf8".parse().unwrap(),
)).header(header::ContentTransferEncoding::QuotedPrintable)
.body("Привет, мир!"),
)
.unwrap();
```
The body will be encoded using selected `Content-Transfer-Encoding`.
```sh
From: NoBody <nobody@domain.tld>
Reply-To: Yuin <yuin@domain.tld>
To: Hei <hei@domain.tld>
Subject: Happy new year
MIME-Version: 1.0
Content-Type: text/plain; charset=utf8
Content-Transfer-Encoding: quoted-printable
=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82, =D0=BC=D0=B8=D1=80!
```
##### Multiple parts
And more advanced way of building message by using multipart MIME contents.
```rust
# extern crate lettre;
use lettre::message::{header, Message, MultiPart, SinglePart, Part};
let m = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.multipart(
MultiPart::mixed()
.multipart(
MultiPart::alternative()
.singlepart(
SinglePart::quoted_printable()
.header(header::ContentType("text/plain; charset=utf8".parse().unwrap()))
.body("Привет, мир!")
)
.multipart(
MultiPart::related()
.singlepart(
SinglePart::eight_bit()
.header(header::ContentType("text/html; charset=utf8".parse().unwrap()))
.body("<p><b>Hello</b>, <i>world</i>! <img src=smile.png></p>")
)
.singlepart(
SinglePart::base64()
.header(header::ContentType("image/png".parse().unwrap()))
.header(header::ContentDisposition {
disposition: header::DispositionType::Inline,
parameters: vec![],
})
.body("<smile-raw-image-data>")
)
)
)
.singlepart(
SinglePart::seven_bit()
.header(header::ContentType("text/plain; charset=utf8".parse().unwrap()))
.header(header::ContentDisposition {
disposition: header::DispositionType::Attachment,
parameters: vec![
header::DispositionParam::Filename(
header::Charset::Ext("utf-8".into()),
None, "example.c".as_bytes().into()
)
]
})
.body("int main() { return 0; }")
)
).unwrap();
```
```sh
From: NoBody <nobody@domain.tld>
Reply-To: Yuin <yuin@domain.tld>
To: Hei <hei@domain.tld>
Subject: Happy new year
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m"
--RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m
Content-Type: multipart/alternative; boundary="qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy"
--qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=utf8
=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82, =D0=BC=D0=B8=D1=80!
--qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy
Content-Type: multipart/related; boundary="BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8"
--BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8
Content-Transfer-Encoding: 8bit
Content-Type: text/html; charset=utf8
<p><b>Hello</b>, <i>world</i>! <img src=smile.png></p>
--BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8
Content-Transfer-Encoding: base64
Content-Type: image/png
Content-Disposition: inline
PHNtaWxlLXJhdy1pbWFnZS1kYXRhPg==
--BV5RCn9p31oAAAAAUt42E9bYMDEAGCOWlxEz89Bv0qFA5Xsy6rOC3zRahMQ39IFZNnp8--
--qW9QCn9p31oAAAAAodFBg1L1Qrraa5hEl0bDJ6kfJMUcRT2LLSWEoeyhSEbUBIqbjWqy--
--RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset=utf8
Content-Disposition: attachment; filename="example.c"
int main() { return 0; }
--RTxPCn9p31oAAAAAeQxtr1FbXr/i5vW1hFlH9oJqZRMWxRMK1QLjQ4OPqFk9R+0xUb/m--
```

View File

@@ -1,6 +0,0 @@
# News
## 2020
* Apr 12 - [0.10 Release](lettre-0-10.md)
* Apr 12 - [Are we email yet?](are-we-email-yet.md)

View File

@@ -1,13 +0,0 @@
# Are we email yet?
TODO
short email landscape in rust
* [rust-async](https://github.com/async-email/async-smtp), provides a async SMTP transport
compatible with lettre 0.9 messages.
* [Proposal for a mail WG](https://internals.rust-lang.org/t/starting-a-rust-mail-wg/11678)
* samotop
* mail (and other by 1aim), including async smtp implementation (with tokio 0.1). Not maintained
anymore, pretty complete.

View File

@@ -1,61 +0,0 @@
# Lettre 0.10
*2019.04.12*
## What is `lettre`?
Lettre provides an email client for Rust programs, to allow easily sending emails from Rust
applications with the following focuses
* Ease of use, without particular knowledge about email
* Secure by default
* Modern (support for full internationalization)
Non-goals:
* Implementing email RFCs extensively. The goal is to target a modern and safe subset needed to
send emails today, with a nice API (i.e. UTF-8 only, etc.). Particularly, lettre
currently cannot parse emails.
### Background
The `lettre` crate was previously named [`smtp`](https://crates.io/crates/smtp). It was [created](https://github.com/lettre/lettre/commit/270efd193a11e66dce14700a50d3c42c12e725bc) in early 2014 (before cargo, Rust 1.0, etc.).
The first goal was to start a toy project as a pretext to learn Rust. I started with an `smtp` implementation after seeing there was no existing implementation in Rust. Originally, the project aimed at implementing the `SMTP` protocol for client and server.
In 2016, the goal changed, and specialized to email client (as I did not see much use in another SMTP server may it be written in Rust). The project also moved away from "just SMTP" to email client, and was renamed to lettre at this time. Why `lettre`? After some time looking for a fitting name, not already taken by email-related software, I ended up just taking the the French word for "letter"!
## Changes in 0.10
* Replacement of the message implementation (which was based on `rust-email`)
by a new one based on the `emailmessage` crate. To main goal is to provide
sane encoding and multipart was a simple implementation (no message parsing).
* Merge of the `lettre_email` crate into `lettre`. This split made not much sense, and the message
builder is now a feature of the `lettre` crate.
* More features to allow disabling most features.
* Add the option to use `rustls` for TLS.
* Improved parsing of server responses.
* Moved CI from Travis to Github actions.
### Migration from 0.9
TODO
## Road to 1.0
Lettre is now used by several projects, including crates.io itself!
It will be good to have a stable basis for the future.
The plan is that 0.10 is the release preparing the 1.0 in the following months.
I'd also want to add more real-world automated testing with actual mail servers (at least postfix).
`async` is not a goal for 1.0, as it is not as relevant for emails as it is for other ecosystems
(like web), and theadpool-based solutions are in general very suited.
## After
* reuse `smtp` crate for the protocol (a bit like `http`)
* async
If you want to contribute, the `lettre` repo and organizations are definitely open for anything
related to email.

View File

@@ -1,17 +0,0 @@
### Sending Messages
This section explains how to manipulate emails you have created.
This mailer contains several different transports for your emails. To be sendable, the
emails have to implement `Email`, which is the case for emails created with `lettre::builder`.
The following transports are available:
* The `SmtpTransport` uses the SMTP protocol to send the message over the network. It is
the preferred way of sending emails.
* The `SendmailTransport` uses the sendmail command to send messages. It is an alternative to
the SMTP transport.
* The `FileTransport` creates a file containing the email content to be sent. It can be used
for debugging or if you want to keep all sent emails.
* The `StubTransport` is useful for debugging, and only prints the content of the email in the
logs.

View File

@@ -1,45 +0,0 @@
#### File Transport
The file transport writes the emails to the given directory. The name of the file will be
`message_id.txt`.
It can be useful for testing purposes, or if you want to keep track of sent messages.
```rust
# #[cfg(feature = "file-transport")]
# {
# extern crate lettre;
use std::env::temp_dir;
use lettre::transport::file::FileTransport;
use lettre::{Transport, Envelope, EmailAddress, Message};
fn main() {
// Write to the local temp directory
let mut sender = FileTransport::new(temp_dir());
let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.body("Be happy!")
.unwrap();
let result = sender.send(email);
assert!(result.is_ok());
}
# }
```
Example result in `/tmp/b7c211bc-9811-45ce-8cd9-68eab575d695.txt`:
```text
b7c211bc-9811-45ce-8cd9-68eab575d695: from=<user@localhost> to=<root@localhost>
To: <root@localhost>
From: <user@localhost>
Subject: Hello
Date: Sat, 31 Oct 2015 13:42:19 +0100
Message-ID: <b7c211bc-9811-45ce-8cd9-68eab575d695.lettre@localhost>
Hello World!
```

View File

@@ -1,27 +0,0 @@
#### Sendmail Transport
The sendmail transport sends the email using the local sendmail command.
```rust,no_run
# #[cfg(feature = "sendmail-transport")]
# {
# extern crate lettre;
use lettre::transport::sendmail::SendmailTransport;
use lettre::{Message, Envelope, EmailAddress, Transport};
fn main() {
let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.body("Be happy!")
.unwrap();
let mut sender = SendmailTransport::new();
let result = sender.send(email);
assert!(result.is_ok());
}
# }
```

View File

@@ -1,189 +0,0 @@
#### SMTP Transport
This transport uses the SMTP protocol to send emails over the network (locally or remotely).
It is designed to be:
* Secured: email are encrypted by default
* Modern: Unicode support for email content and sender/recipient addresses when compatible
* Fast: supports tcp connection reuse
This client is designed to send emails to a relay server, and should *not* be used to send
emails directly to the destination.
The relay server can be the local email server, a specific host or a third-party service.
#### Simple example
This is the most basic example of usage:
```rust,no_run
# #[cfg(feature = "smtp-transport")]
# {
# extern crate lettre;
use lettre::{Message, EmailAddress, Transport, Envelope, SmtpClient};
fn main() {
let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.body("Be happy!")
.unwrap();
// Open a local connection on port 25
let mut mailer =
SmtpClient::new_unencrypted_localhost().unwrap().transport();
// Send the email
let result = mailer.send(&email);
assert!(result.is_ok());
}
# }
```
#### Complete example
```rust,no_run
# #[cfg(feature = "smtp-transport")]
# {
# extern crate lettre;
use lettre::transport::smtp::authentication::{Credentials, Mechanism};
use lettre::{Email, Envelope, EmailAddress, Transport, SmtpClient};
use lettre::transport::smtp::extension::ClientId;
use lettre::transport::smtp::ConnectionReuseParameters;
fn main() {
let email_1 = Email::new(
Envelope::new(
Some(EmailAddress::new("user@localhost".to_string()).unwrap()),
vec![EmailAddress::new("root@localhost".to_string()).unwrap()],
).unwrap(),
"id1".to_string(),
"Hello world".to_string().into_bytes(),
);
let email_2 = Email::new(
Envelope::new(
Some(EmailAddress::new("user@localhost".to_string()).unwrap()),
vec![EmailAddress::new("root@localhost".to_string()).unwrap()],
).unwrap(),
"id2".to_string(),
"Hello world a second time".to_string().into_bytes(),
);
// Connect to a remote server on a custom port
let mut mailer = SmtpClient::new_simple("server.tld").unwrap()
// Set the name sent during EHLO/HELO, default is `localhost`
.hello_name(ClientId::Domain("my.hostname.tld".to_string()))
// Add credentials for authentication
.credentials(Credentials::new("username".to_string(), "password".to_string()))
// Enable SMTPUTF8 if the server supports it
.smtp_utf8(true)
// Configure expected authentication mechanism
.authentication_mechanism(Mechanism::Plain)
// Enable connection reuse
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited).transport();
let result_1 = mailer.send(&email_1);
assert!(result_1.is_ok());
// The second email will use the same connection
let result_2 = mailer.send(&email_2);
assert!(result_2.is_ok());
// Explicitly close the SMTP transaction as we enabled connection reuse
mailer.close();
}
# }
```
You can specify custom TLS settings:
```rust,no_run
# #[cfg(feature = "native-tls")]
# {
# extern crate native_tls;
# extern crate lettre;
use lettre::{
ClientSecurity, ClientTlsParameters, EmailAddress, Envelope,
Email, SmtpClient, Transport,
};
use lettre::transport::smtp::authentication::{Credentials, Mechanism};
use lettre::transport::smtp::ConnectionReuseParameters;
use native_tls::{Protocol, TlsConnector};
fn main() {
let email = Email::new(
Envelope::new(
Some(EmailAddress::new("user@localhost".to_string()).unwrap()),
vec![EmailAddress::new("root@localhost".to_string()).unwrap()],
).unwrap(),
"message_id".to_string(),
"Hello world".to_string().into_bytes(),
);
let mut tls_builder = TlsConnector::builder();
tls_builder.min_protocol_version(Some(Protocol::Tlsv10));
let tls_parameters =
ClientTlsParameters::new(
"smtp.example.com".to_string(),
tls_builder.build().unwrap()
);
let mut mailer = SmtpClient::new(
("smtp.example.com", 465), ClientSecurity::Wrapper(tls_parameters)
).unwrap()
.authentication_mechanism(Mechanism::Login)
.credentials(Credentials::new(
"example_username".to_string(), "example_password".to_string()
))
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
.transport();
let result = mailer.send(&email);
assert!(result.is_ok());
mailer.close();
}
# }
```
#### Lower level
You can also send commands, here is a simple email transaction without
error handling:
```rust,no_run
# #[cfg(feature = "smtp-transport")]
# {
# extern crate lettre;
use lettre::EmailAddress;
use lettre::transport::smtp::SMTP_PORT;
use lettre::transport::smtp::client::InnerClient;
use lettre::transport::smtp::client::net::NetworkStream;
use lettre::transport::smtp::extension::ClientId;
use lettre::transport::smtp::commands::*;
fn main() {
let mut email_client: InnerClient<NetworkStream> = InnerClient::new();
let _ = email_client.connect(&("localhost", SMTP_PORT), None, None);
let _ = email_client.command(EhloCommand::new(ClientId::new("my_hostname".to_string())));
let _ = email_client.command(
MailCommand::new(Some(EmailAddress::new("user@example.com".to_string()).unwrap()), vec![])
);
let _ = email_client.command(
RcptCommand::new(EmailAddress::new("user@example.org".to_string()).unwrap(), vec![])
);
let _ = email_client.command(DataCommand);
let _ = email_client.message(Box::new("Test email".as_bytes()));
let _ = email_client.command(QuitCommand);
}
# }
```

View File

@@ -1,31 +0,0 @@
#### Stub Transport
The stub transport only logs message envelope and drops the content. It can be useful for
testing purposes.
```rust
# extern crate lettre;
use lettre::transport::stub::StubTransport;
use lettre::{Message, Envelope, Transport};
fn main() {
let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.body("Be happy!")
.unwrap();
let mut sender = StubTransport::new_positive();
let result = sender.send(&email);
assert!(result.is_ok());
}
```
Will log (when using a logger like `env_logger`):
```text
b7c211bc-9811-45ce-8cd9-68eab575d695: from=<user@localhost> to=<root@localhost>
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB