diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml deleted file mode 100644 index 53c12a8..0000000 --- a/.github/workflows/website.yml +++ /dev/null @@ -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 diff --git a/README.md b/README.md index 5552a83..562aafd 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,31 @@ -# lettre +

lettre

+
+ + A mailer library for Rust + +
-**Lettre is a mailer library for Rust.** +
-![](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) +
+ + docs + + + downloads + +
+ + chat on gitter + + + website + +
--- diff --git a/src/lib.rs b/src/lib.rs index 0fa37dd..e77a6c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/message/mod.rs b/src/message/mod.rs index 75e97a6..2aa34c1 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -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 ".parse().unwrap()) +//! .reply_to("Yuin ".parse().unwrap()) +//! .to("Hei ".parse().unwrap()) +//! .subject("Happy new year") +//! .body("Be happy!") +//! .unwrap(); +//! ``` +//! +//! Will produce: +//! +//! ```sh +//! From: NoBody +//! Reply-To: Yuin +//! To: Hei +//! 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 ".parse().unwrap()) +//! .reply_to("Yuin ".parse().unwrap()) +//! .to("Hei ".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 +//! Reply-To: Yuin +//! To: Hei +//! 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 ".parse().unwrap()) +//! .reply_to("Yuin ".parse().unwrap()) +//! .to("Hei ".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("

Hello, world!

") +//! ) +//! .singlepart( +//! SinglePart::base64() +//! .header(header::ContentType("image/png".parse().unwrap())) +//! .header(header::ContentDisposition { +//! disposition: header::DispositionType::Inline, +//! parameters: vec![], +//! }) +//! .body("") +//! ) +//! ) +//! ) +//! .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 +//! Reply-To: Yuin +//! To: Hei +//! 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 +//! +//!

Hello, world!

+//! --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::*; diff --git a/src/transport/file/mod.rs b/src/transport/file/mod.rs index 15ce512..f7f2d32 100644 --- a/src/transport/file/mod.rs +++ b/src/transport/file/mod.rs @@ -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 ".parse().unwrap()) +//! .reply_to("Yuin ".parse().unwrap()) +//! .to("Hei ".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::{ diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 3b322c8..6076fe8 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -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")] diff --git a/src/transport/sendmail/mod.rs b/src/transport/sendmail/mod.rs index 0e8f8a7..909c398 100644 --- a/src/transport/sendmail/mod.rs +++ b/src/transport/sendmail/mod.rs @@ -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 ".parse().unwrap()) +//! .reply_to("Yuin ".parse().unwrap()) +//! .to("Hei ".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::{ diff --git a/src/transport/smtp/client/mod.rs b/src/transport/smtp/client/mod.rs index d4d10d6..b2eb1f2 100644 --- a/src/transport/smtp/client/mod.rs +++ b/src/transport/smtp/client/mod.rs @@ -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 { - 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, diff --git a/src/transport/smtp/mod.rs b/src/transport/smtp/mod.rs index 5936003..2afa932 100644 --- a/src/transport/smtp/mod.rs +++ b/src/transport/smtp/mod.rs @@ -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 ".parse().unwrap()) +//! .reply_to("Yuin ".parse().unwrap()) +//! .to("Hei ".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; diff --git a/src/transport/stub/mod.rs b/src/transport/stub/mod.rs index 5d4e28d..50705cb 100644 --- a/src/transport/stub/mod.rs +++ b/src/transport/stub/mod.rs @@ -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 ".parse().unwrap()) +//! .reply_to("Yuin ".parse().unwrap()) +//! .to("Hei ".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}; diff --git a/tests/docs.rs b/tests/docs.rs deleted file mode 100644 index f15b4cf..0000000 --- a/tests/docs.rs +++ /dev/null @@ -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) - ); -} diff --git a/tests/r2d2_smtp.rs b/tests/r2d2_smtp.rs deleted file mode 100644 index 39b55fa..0000000 --- a/tests/r2d2_smtp.rs +++ /dev/null @@ -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"); - } -} diff --git a/website/.gitignore b/website/.gitignore deleted file mode 100644 index 5a0bf03..0000000 --- a/website/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/book diff --git a/website/book.toml b/website/book.toml deleted file mode 100644 index 6637547..0000000 --- a/website/book.toml +++ /dev/null @@ -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] diff --git a/website/src/README.md b/website/src/README.md deleted file mode 100644 index 4f75f4b..0000000 --- a/website/src/README.md +++ /dev/null @@ -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" -``` diff --git a/website/src/SUMMARY.md b/website/src/SUMMARY.md deleted file mode 100644 index 50dc26e..0000000 --- a/website/src/SUMMARY.md +++ /dev/null @@ -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) diff --git a/website/src/creating-messages/email.md b/website/src/creating-messages/email.md deleted file mode 100644 index e4092a1..0000000 --- a/website/src/creating-messages/email.md +++ /dev/null @@ -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 ".parse().unwrap()) - .reply_to("Yuin ".parse().unwrap()) - .to("Hei ".parse().unwrap()) - .subject("Happy new year") - .body("Be happy!") - .unwrap(); -``` - -Will produce: - -```sh -From: NoBody -Reply-To: Yuin -To: Hei -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 ".parse().unwrap()) - .reply_to("Yuin ".parse().unwrap()) - .to("Hei ".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 -Reply-To: Yuin -To: Hei -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 ".parse().unwrap()) - .reply_to("Yuin ".parse().unwrap()) - .to("Hei ".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("

Hello, world!

") - ) - .singlepart( - SinglePart::base64() - .header(header::ContentType("image/png".parse().unwrap())) - .header(header::ContentDisposition { - disposition: header::DispositionType::Inline, - parameters: vec![], - }) - .body("") - ) - ) - ) - .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 -Reply-To: Yuin -To: Hei -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 - -

Hello, world!

---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-- - -``` diff --git a/website/src/posts/_index.md b/website/src/posts/_index.md deleted file mode 100644 index 2c7000e..0000000 --- a/website/src/posts/_index.md +++ /dev/null @@ -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) diff --git a/website/src/posts/are-we-email-yet.md b/website/src/posts/are-we-email-yet.md deleted file mode 100644 index 8b5fd50..0000000 --- a/website/src/posts/are-we-email-yet.md +++ /dev/null @@ -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. - diff --git a/website/src/posts/lettre-0-10.md b/website/src/posts/lettre-0-10.md deleted file mode 100644 index 84099a2..0000000 --- a/website/src/posts/lettre-0-10.md +++ /dev/null @@ -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. diff --git a/website/src/sending-messages/_index.md b/website/src/sending-messages/_index.md deleted file mode 100644 index 75dd91d..0000000 --- a/website/src/sending-messages/_index.md +++ /dev/null @@ -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. diff --git a/website/src/sending-messages/file.md b/website/src/sending-messages/file.md deleted file mode 100644 index db61ef1..0000000 --- a/website/src/sending-messages/file.md +++ /dev/null @@ -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 ".parse().unwrap()) - .reply_to("Yuin ".parse().unwrap()) - .to("Hei ".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= to= -To: -From: -Subject: Hello -Date: Sat, 31 Oct 2015 13:42:19 +0100 -Message-ID: - -Hello World! -``` diff --git a/website/src/sending-messages/sendmail.md b/website/src/sending-messages/sendmail.md deleted file mode 100644 index e5acbe6..0000000 --- a/website/src/sending-messages/sendmail.md +++ /dev/null @@ -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 ".parse().unwrap()) - .reply_to("Yuin ".parse().unwrap()) - .to("Hei ".parse().unwrap()) - .subject("Happy new year") - .body("Be happy!") - .unwrap(); - - let mut sender = SendmailTransport::new(); - let result = sender.send(email); - assert!(result.is_ok()); -} -# } -``` diff --git a/website/src/sending-messages/smtp.md b/website/src/sending-messages/smtp.md deleted file mode 100644 index 301bef5..0000000 --- a/website/src/sending-messages/smtp.md +++ /dev/null @@ -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 ".parse().unwrap()) - .reply_to("Yuin ".parse().unwrap()) - .to("Hei ".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 = 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); -} -# } -``` diff --git a/website/src/sending-messages/stub.md b/website/src/sending-messages/stub.md deleted file mode 100644 index 1d75456..0000000 --- a/website/src/sending-messages/stub.md +++ /dev/null @@ -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 ".parse().unwrap()) - .reply_to("Yuin ".parse().unwrap()) - .to("Hei ".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= to= -``` diff --git a/website/theme/favicon.png b/website/theme/favicon.png deleted file mode 100644 index 34ab3ff..0000000 Binary files a/website/theme/favicon.png and /dev/null differ