diff --git a/.travis.yml b/.travis.yml index 561c46a..63b60ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ rust: - stable - beta - nightly +- 1.13.0 matrix: allow_failures: @@ -12,39 +13,25 @@ matrix: sudo: required cache: - apt: true - pip: true directories: - target/debug/deps - target/debug/build - target/release/deps - target/release/build -install: -- pip install 'travis-cargo<0.2' --user -- export PATH=$HOME/.local/bin:$PATH - addons: apt: packages: - postfix - - libcurl4-openssl-dev - - libelf-dev - - libdw-dev before_script: -- smtp-sink 2525 1000& -- sudo chgrp -R postdrop /var/spool/postfix/maildrop + - smtp-sink 2525 1000& + - sudo chgrp -R postdrop /var/spool/postfix/maildrop script: -- travis-cargo build -- travis-cargo test -- travis-cargo doc - -after_success: -- ./.travis/doc.sh -- ./.travis/coverage.sh -- travis-cargo --only nightly bench + - | + cd lettre && cargo build && cargo test + cd ../lettre_email && cargo build && cargo test env: global: diff --git a/.travis/coverage.sh b/.travis/coverage.sh deleted file mode 100755 index 415cae3..0000000 --- a/.travis/coverage.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -set -o errexit - -if [ "$TRAVIS_RUST_VERSION" != "stable" ]; then - exit 0 -fi - -cargo test --no-run - -wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz -tar xzf master.tar.gz -mkdir kcov-master/build -cd kcov-master/build -cmake .. -make -make install DESTDIR=../tmp -cd ../.. -ls target/debug -./kcov-master/tmp/usr/local/bin/kcov --coveralls-id=$TRAVIS_JOB_ID --exclude-pattern=/.cargo target/kcov target/debug/lettre-* diff --git a/.travis/doc.sh b/.travis/doc.sh deleted file mode 100755 index 3ff6a33..0000000 --- a/.travis/doc.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -set -o errexit - -if [ "$TRAVIS_RUST_VERSION" != "stable" ] || [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - exit 0 -fi - -cargo clean -cargo doc --no-deps - -git clone --branch gh-pages "https://$GH_TOKEN@github.com/${TRAVIS_REPO_SLUG}.git" deploy_docs -cd deploy_docs - -git config user.email "contact@amousset.me" -git config user.name "Alexis Mousset" - -if [ "$TRAVIS_BRANCH" == "master" ]; then - rm -rf master - mv ../target/doc ./master - echo "" > ./master/index.html -elif [ "$TRAVIS_TAG" != "" ]; then - rm -rf $TRAVIS_TAG - mv ../target/doc ./$TRAVIS_TAG - echo "" > ./$TRAVIS_TAG/index.html - - latest=$(echo * | tr " " "\n" | sort -V -r | head -n1) - if [ "$TRAVIS_TAG" == "$latest" ]; then - - echo "" > index.html - fi -else - exit 0 -fi - -git add -A . -git commit -m "Rebuild pages at ${TRAVIS_COMMIT}" -git push --quiet origin gh-pages diff --git a/Cargo.toml b/Cargo.toml index 5f57987..3146062 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,29 +1,5 @@ -[package] - -name = "lettre" -version = "0.6.2" -description = "Email client" -readme = "README.md" -documentation = "https://docs.rs/lettre/" -repository = "https://github.com/lettre/lettre" -license = "MIT" -authors = ["Alexis Mousset "] -keywords = ["email", "smtp", "mailer"] - -[dependencies] -bufstream = "^0.1" -email = "^0.0" -log = "^0.3" -mime = "^0.2" -openssl = "^0.9" -base64 = "~0.5.0" -hex = "^0.2.0" -rust-crypto = "^0.2" -time = "^0.1" -uuid = { version = ">=0.4, <0.6", features = ["v4"] } - -[dev-dependencies] -env_logger = "^0.4" - -[features] -unstable = [] +[workspace] +members = [ + "lettre", + "lettre_email", +] \ No newline at end of file diff --git a/benches/transport_smtp.rs b/benches/transport_smtp.rs deleted file mode 100644 index 1f8d332..0000000 --- a/benches/transport_smtp.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![feature(test)] - -extern crate lettre; -extern crate test; - -use lettre::transport::smtp::SmtpTransportBuilder; -use lettre::transport::EmailTransport; -use lettre::email::EmailBuilder; - -#[bench] -fn bench_simple_send(b: &mut test::Bencher) { - let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525").unwrap().build(); - b.iter(|| { - let email = EmailBuilder::new() - .to("root@localhost") - .from("user@localhost") - .body("Hello World!") - .subject("Hello") - .build() - .unwrap(); - let result = sender.send(email); - assert!(result.is_ok()); - }); -} - -#[bench] -fn bench_reuse_send(b: &mut test::Bencher) { - let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525") - .unwrap() - .connection_reuse(true) - .build(); - b.iter(|| { - let email = EmailBuilder::new() - .to("root@localhost") - .from("user@localhost") - .body("Hello World!") - .subject("Hello") - .build() - .unwrap(); - let result = sender.send(email); - assert!(result.is_ok()); - }); - sender.close() -} diff --git a/CHANGELOG.md b/lettre/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to lettre/CHANGELOG.md diff --git a/lettre/Cargo.toml b/lettre/Cargo.toml new file mode 100644 index 0000000..7ad3db8 --- /dev/null +++ b/lettre/Cargo.toml @@ -0,0 +1,29 @@ +[package] + +name = "lettre" +version = "0.7.0" +description = "Email client" +readme = "README.md" +documentation = "https://docs.rs/lettre/" +repository = "https://github.com/lettre/lettre" +license = "MIT" +authors = ["Alexis Mousset "] +categories = ["email"] +keywords = ["email", "smtp", "mailer"] + +[badges] +travis-ci = { repository = "lettre/lettre" } + +[dependencies] +bufstream = "^0.1" +log = "^0.3" +openssl = "^0.9" +base64 = "^0.5" +hex = "^0.2" +rust-crypto = "^0.2" + +[dev-dependencies] +env_logger = "^0.4" + +[features] +unstable = [] diff --git a/lettre/LICENSE b/lettre/LICENSE new file mode 120000 index 0000000..ea5b606 --- /dev/null +++ b/lettre/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/lettre/README.md b/lettre/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/lettre/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/lettre/benches/transport_smtp.rs b/lettre/benches/transport_smtp.rs new file mode 100644 index 0000000..177a7d4 --- /dev/null +++ b/lettre/benches/transport_smtp.rs @@ -0,0 +1,37 @@ +#![feature(test)] + +extern crate lettre; +extern crate test; + +use lettre::smtp::SmtpTransportBuilder; +use lettre::{EmailTransport, SimpleSendableEmail}; + +#[bench] +fn bench_simple_send(b: &mut test::Bencher) { + let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525").unwrap().build(); + b.iter(|| { + let email = SimpleSendableEmail::new("user@localhost", + vec!["root@localhost"], + "id", + "Hello world"); + let result = sender.send(email); + assert!(result.is_ok()); + }); +} + +#[bench] +fn bench_reuse_send(b: &mut test::Bencher) { + let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525") + .unwrap() + .connection_reuse(true) + .build(); + b.iter(|| { + let email = SimpleSendableEmail::new("user@localhost", + vec!["root@localhost"], + "file_id", + "Hello file"); + let result = sender.send(email); + assert!(result.is_ok()); + }); + sender.close() +} diff --git a/lettre/examples/smtp.rs b/lettre/examples/smtp.rs new file mode 100644 index 0000000..f0427a4 --- /dev/null +++ b/lettre/examples/smtp.rs @@ -0,0 +1,24 @@ +extern crate lettre; + +use lettre::{EmailTransport, SimpleSendableEmail}; +use lettre::smtp::SmtpTransportBuilder; + +fn main() { + let email = SimpleSendableEmail::new("user@localhost", + vec!["root@localhost"], + "file_id", + "Hello file"); + + // Open a local connection on port 25 + let mut mailer = SmtpTransportBuilder::localhost().unwrap().build(); + // Send the email + let result = mailer.send(email); + + if result.is_ok() { + println!("Email sent"); + } else { + println!("Could not send email: {:?}", result); + } + + assert!(result.is_ok()); +} diff --git a/lettre/rustfmt.toml b/lettre/rustfmt.toml new file mode 120000 index 0000000..39f97b0 --- /dev/null +++ b/lettre/rustfmt.toml @@ -0,0 +1 @@ +../rustfmt.toml \ No newline at end of file diff --git a/src/transport/file/error.rs b/lettre/src/file/error.rs similarity index 100% rename from src/transport/file/error.rs rename to lettre/src/file/error.rs diff --git a/src/transport/file/mod.rs b/lettre/src/file/mod.rs similarity index 84% rename from src/transport/file/mod.rs rename to lettre/src/file/mod.rs index 6bdac2c..8357686 100644 --- a/src/transport/file/mod.rs +++ b/lettre/src/file/mod.rs @@ -1,12 +1,12 @@ //! This transport creates a file for each email, containing the envelope information and the email //! itself. -use email::SendableEmail; +use EmailTransport; +use SendableEmail; +use file::error::FileResult; use std::fs::File; use std::io::prelude::*; use std::path::{Path, PathBuf}; -use transport::EmailTransport; -use transport::file::error::FileResult; pub mod error; @@ -34,8 +34,8 @@ impl EmailTransport for FileEmailTransport { let log_line = format!("{}: from=<{}> to=<{}>\n", email.message_id(), - email.envelope().from, - email.envelope().to.join("> to=<")); + email.from(), + email.to().join("> to=<")); try!(f.write_all(log_line.as_bytes())); try!(f.write_all(email.message().as_bytes())); diff --git a/src/lib.rs b/lettre/src/lib.rs similarity index 52% rename from src/lib.rs rename to lettre/src/lib.rs index e48cce2..2128c42 100644 --- a/src/lib.rs +++ b/lettre/src/lib.rs @@ -2,64 +2,8 @@ //! //! ## Overview //! -//! This mailer is divided into: -//! -//! * An `email` part: builds the email message -//! * A `transport` part: contains the available transports for your emails. To be sendable, the -//! emails have to implement `SendableEmail`. -//! -//! ## Creating messages -//! -//! The `email` part builds email messages. For now, it does not support attachments. -//! An email is built using an `EmailBuilder`. The simplest email could be: -//! -//! ```rust -//! use lettre::email::EmailBuilder; -//! -//! // Create an email -//! let email = EmailBuilder::new() -//! // Addresses can be specified by the tuple (email, alias) -//! .to(("user@example.org", "Firstname Lastname")) -//! // ... or by an address only -//! .from("user@example.com") -//! .subject("Hi, Hello world") -//! .text("Hello world.") -//! .build(); -//! -//! assert!(email.is_ok()); -//! ``` -//! -//! When the `build` method is called, the `EmailBuilder` will add the missing headers (like -//! `Message-ID` or `Date`) and check for missing necessary ones (like `From` or `To`). It will -//! then generate an `Email` that can be sent. -//! -//! The `text()` method will create a plain text email, while the `html()` method will create an -//! HTML email. You can use the `alternative()` method to provide both versions, using plain text -//! as fallback for the HTML version. -//! -//! Below is a more complete example, not using method chaining: -//! -//! ```rust -//! use lettre::email::EmailBuilder; -//! -//! let mut builder = EmailBuilder::new(); -//! builder.add_to(("user@example.org", "Alias name")); -//! builder.add_cc(("user@example.net", "Alias name")); -//! builder.add_from("no-reply@example.com"); -//! builder.add_from("no-reply@example.eu"); -//! builder.set_sender("no-reply@example.com"); -//! builder.set_subject("Hello world"); -//! builder.set_alternative("

Hi, Hello world.

", "Hi, Hello world."); -//! builder.add_reply_to("contact@example.com"); -//! builder.add_header(("X-Custom-Header", "my header")); -//! -//! let email = builder.build(); -//! assert!(email.is_ok()); -//! ``` -//! -//! See the `EmailBuilder` documentation for a complete list of methods. -//! -//! ## Sending messages +//! This mailer contains the available transports for your emails. To be sendable, the +//! emails have to implement `SendableEmail`. //! //! The following sections describe the available transport methods to handle emails. //! @@ -93,18 +37,16 @@ //! This is the most basic example of usage: //! //! ```rust,no_run -//! use lettre::transport::smtp::SmtpTransportBuilder; -//! use lettre::email::EmailBuilder; -//! use lettre::transport::EmailTransport; -//! use lettre::transport::smtp::SecurityLevel; +//! use lettre::{SimpleSendableEmail, EmailTransport}; +//! use lettre::smtp::SmtpTransportBuilder; +//! use lettre::smtp::SecurityLevel; //! -//! let email = EmailBuilder::new() -//! .to("root@localhost") -//! .from("user@localhost") -//! .body("Hello World!") -//! .subject("Hello") -//! .build() -//! .unwrap(); +//! let email = SimpleSendableEmail::new( +//! "user@localhost", +//! vec!["root@localhost"], +//! "message_id", +//! "Hello world" +//! ); //! //! // Open a local connection on port 25 //! let mut mailer = @@ -118,20 +60,18 @@ //! #### Complete example //! //! ```rust,no_run -//! use lettre::email::EmailBuilder; -//! use lettre::transport::smtp::{SecurityLevel, SmtpTransport, +//! use lettre::smtp::{SecurityLevel, SmtpTransport, //! SmtpTransportBuilder}; -//! use lettre::transport::smtp::authentication::Mechanism; -//! use lettre::transport::smtp::SUBMISSION_PORT; -//! use lettre::transport::EmailTransport; +//! use lettre::smtp::authentication::Mechanism; +//! use lettre::smtp::SUBMISSION_PORT; +//! use lettre::{SimpleSendableEmail, EmailTransport}; //! -//! let email = EmailBuilder::new() -//! .to("root@localhost") -//! .from("user@localhost") -//! .body("Hello World!") -//! .subject("Hello") -//! .build() -//! .unwrap(); +//! let email = SimpleSendableEmail::new( +//! "user@localhost", +//! vec!["root@localhost"], +//! "message_id", +//! "Hello world" +//! ); //! //! // Connect to a remote server on a custom port //! let mut mailer = SmtpTransportBuilder::new(("server.tld", @@ -167,9 +107,9 @@ //! error handling: //! //! ```rust -//! use lettre::transport::smtp::SMTP_PORT; -//! use lettre::transport::smtp::client::Client; -//! use lettre::transport::smtp::client::net::NetworkStream; +//! use lettre::smtp::SMTP_PORT; +//! use lettre::smtp::client::Client; +//! use lettre::smtp::client::net::NetworkStream; //! //! let mut email_client: Client = Client::new(); //! let _ = email_client.connect(&("localhost", SMTP_PORT), None); @@ -186,17 +126,15 @@ //! The sendmail transport sends the email using the local sendmail command. //! //! ```rust -//! use lettre::transport::sendmail::SendmailTransport; -//! use lettre::transport::EmailTransport; -//! use lettre::email::EmailBuilder; +//! use lettre::sendmail::SendmailTransport; +//! use lettre::{SimpleSendableEmail, EmailTransport}; //! -//! let email = EmailBuilder::new() -//! .to("root@localhost") -//! .from("user@localhost") -//! .body("Hello World!") -//! .subject("Hello") -//! .build() -//! .unwrap(); +//! let email = SimpleSendableEmail::new( +//! "user@localhost", +//! vec!["root@localhost"], +//! "message_id", +//! "Hello world" +//! ); //! //! let mut sender = SendmailTransport::new(); //! let result = sender.send(email); @@ -209,17 +147,15 @@ //! testing purposes. //! //! ```rust -//! use lettre::transport::stub::StubEmailTransport; -//! use lettre::transport::EmailTransport; -//! use lettre::email::EmailBuilder; +//! use lettre::stub::StubEmailTransport; +//! use lettre::{SimpleSendableEmail, EmailTransport}; //! -//! let email = EmailBuilder::new() -//! .to("root@localhost") -//! .from("user@localhost") -//! .body("Hello World!") -//! .subject("Hello") -//! .build() -//! .unwrap(); +//! let email = SimpleSendableEmail::new( +//! "user@localhost", +//! vec!["root@localhost"], +//! "message_id", +//! "Hello world" +//! ); //! //! let mut sender = StubEmailTransport; //! let result = sender.send(email); @@ -241,19 +177,17 @@ //! ```rust //! use std::env::temp_dir; //! -//! use lettre::transport::file::FileEmailTransport; -//! use lettre::transport::EmailTransport; -//! use lettre::email::{EmailBuilder, SendableEmail}; +//! use lettre::file::FileEmailTransport; +//! use lettre::{SimpleSendableEmail, EmailTransport}; //! //! // Write to the local temp directory //! let mut sender = FileEmailTransport::new(temp_dir()); -//! let email = EmailBuilder::new() -//! .to("root@localhost") -//! .from("user@localhost") -//! .body("Hello World!") -//! .subject("Hello") -//! .build() -//! .unwrap(); +//! let email = SimpleSendableEmail::new( +//! "user@localhost", +//! vec!["root@localhost"], +//! "message_id", +//! "Hello world" +//! ); //! //! let result = sender.send(email); //! assert!(result.is_ok()); @@ -275,16 +209,80 @@ #[macro_use] extern crate log; -#[macro_use] -extern crate mime; extern crate base64; extern crate hex; extern crate crypto; -extern crate time; -extern crate uuid; -extern crate email as email_format; extern crate bufstream; extern crate openssl; -pub mod transport; -pub mod email; +pub mod smtp; +pub mod sendmail; +pub mod stub; +pub mod file; + +/// Email sendable by an SMTP client +pub trait SendableEmail { + /// To + fn to(&self) -> Vec; + /// From + fn from(&self) -> String; + /// Message ID, used for logging + fn message_id(&self) -> String; + /// Message content + fn message(self) -> String; +} + +/// Transport method for emails +pub trait EmailTransport { + /// Sends the email + fn send(&mut self, email: T) -> U; + /// Close the transport explicitly + fn close(&mut self); +} + +/// Minimal email structure +#[derive(Debug,Clone)] +pub struct SimpleSendableEmail { + /// To + to: Vec, + /// From + from: String, + /// Message ID + message_id: String, + /// Message content + message: String, +} + +impl SimpleSendableEmail { + /// Returns a new email + pub fn new(from_address: &str, + to_addresses: Vec<&str>, + message_id: &str, + message: &str) + -> SimpleSendableEmail { + SimpleSendableEmail { + from: from_address.to_string(), + to: to_addresses.iter().map(|s| s.to_string()).collect(), + message_id: message_id.to_string(), + message: message.to_string(), + } + } +} + +impl SendableEmail for SimpleSendableEmail { + fn to(&self) -> Vec { + self.to.clone() + } + + fn from(&self) -> String { + self.from.clone() + } + + fn message_id(&self) -> String { + self.message_id.clone() + } + + fn message(self) -> String { + self.message + } +} diff --git a/src/transport/sendmail/error.rs b/lettre/src/sendmail/error.rs similarity index 100% rename from src/transport/sendmail/error.rs rename to lettre/src/sendmail/error.rs diff --git a/src/transport/sendmail/mod.rs b/lettre/src/sendmail/mod.rs similarity index 83% rename from src/transport/sendmail/mod.rs rename to lettre/src/sendmail/mod.rs index c013c9f..fe225f1 100644 --- a/src/transport/sendmail/mod.rs +++ b/lettre/src/sendmail/mod.rs @@ -1,12 +1,12 @@ //! This transport uilizes the sendmail executable for each email. -use email::SendableEmail; + +use EmailTransport; +use SendableEmail; +use sendmail::error::SendmailResult; use std::io::prelude::*; use std::process::{Command, Stdio}; -use transport::EmailTransport; -use transport::sendmail::error::SendmailResult; - pub mod error; /// Sends an email using the `sendmail` command @@ -31,10 +31,7 @@ impl EmailTransport for SendmailTransport { fn send(&mut self, email: T) -> SendmailResult { // Spawn the sendmail command let mut process = try!(Command::new(&self.command) - .args(&["-i", - "-f", - &email.envelope().from, - &email.envelope().to.join(" ")]) + .args(&["-i", "-f", &email.from(), &email.to().join(" ")]) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()); diff --git a/src/transport/smtp/authentication.rs b/lettre/src/smtp/authentication.rs similarity index 93% rename from src/transport/smtp/authentication.rs rename to lettre/src/smtp/authentication.rs index 145c35c..d1442c4 100644 --- a/src/transport/smtp/authentication.rs +++ b/lettre/src/smtp/authentication.rs @@ -4,10 +4,10 @@ use crypto::hmac::Hmac; use crypto::mac::Mac; use crypto::md5::Md5; use hex::ToHex; +use smtp::NUL; +use smtp::error::Error; use std::fmt; use std::fmt::{Display, Formatter}; -use transport::smtp::NUL; -use transport::smtp::error::Error; /// Represents authentication mechanisms #[derive(PartialEq,Eq,Copy,Clone,Hash,Debug)] @@ -26,13 +26,11 @@ pub enum Mechanism { impl Display for Mechanism { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, - "{}", - match *self { - Mechanism::Plain => "PLAIN", - Mechanism::Login => "LOGIN", - Mechanism::CramMd5 => "CRAM-MD5", - }) + write!(f, "{}", match *self { + Mechanism::Plain => "PLAIN", + Mechanism::Login => "LOGIN", + Mechanism::CramMd5 => "CRAM-MD5", + }) } } diff --git a/src/transport/smtp/client/mod.rs b/lettre/src/smtp/client/mod.rs similarity index 92% rename from src/transport/smtp/client/mod.rs rename to lettre/src/smtp/client/mod.rs index 8b5df5c..8f35746 100644 --- a/src/transport/smtp/client/mod.rs +++ b/lettre/src/smtp/client/mod.rs @@ -1,20 +1,20 @@ //! SMTP client -use bufstream::BufStream; -use openssl::ssl::SslContext; use base64; +use bufstream::BufStream; +use openssl::ssl::SslContext; +use smtp::{CRLF, MESSAGE_ENDING}; +use smtp::authentication::Mechanism; +use smtp::client::net::{Connector, NetworkStream, Timeout}; +use smtp::error::{Error, SmtpResult}; +use smtp::response::ResponseParser; use std::fmt::Debug; use std::io; use std::io::{BufRead, Read, Write}; use std::net::ToSocketAddrs; use std::string::String; use std::time::Duration; -use transport::smtp::{CRLF, MESSAGE_ENDING}; -use transport::smtp::authentication::Mechanism; -use transport::smtp::client::net::{Connector, NetworkStream, Timeout}; -use transport::smtp::error::{Error, SmtpResult}; -use transport::smtp::response::ResponseParser; pub mod net; @@ -203,8 +203,12 @@ impl Client { if mechanism.supports_initial_response() { self.command(&format!("AUTH {} {}", - mechanism, - base64::encode_config(try!(mechanism.response(username, password, None)).as_bytes(), base64::STANDARD))) + mechanism, + base64::encode_config(try!(mechanism.response(username, + password, + None)) + .as_bytes(), + base64::STANDARD))) } else { let encoded_challenge = match try!(self.command(&format!("AUTH {}", mechanism))) .first_word() { @@ -232,7 +236,8 @@ impl Client { let response = try!(self.command(&base64::encode_config(&try!(mechanism.response(username, password, Some(&decoded_challenge))) - .as_bytes(), base64::STANDARD))); + .as_bytes(), + base64::STANDARD))); if !response.has_code(334) { return Ok(response); diff --git a/src/transport/smtp/client/net.rs b/lettre/src/smtp/client/net.rs similarity index 100% rename from src/transport/smtp/client/net.rs rename to lettre/src/smtp/client/net.rs diff --git a/src/transport/smtp/error.rs b/lettre/src/smtp/error.rs similarity index 97% rename from src/transport/smtp/error.rs rename to lettre/src/smtp/error.rs index 4a0a29e..9654742 100644 --- a/src/transport/smtp/error.rs +++ b/lettre/src/smtp/error.rs @@ -2,12 +2,12 @@ use self::Error::*; use base64::DecodeError; +use smtp::response::{Response, Severity}; use std::error::Error as StdError; use std::fmt; use std::fmt::{Display, Formatter}; use std::io; use std::string::FromUtf8Error; -use transport::smtp::response::{Response, Severity}; /// An enum of all error kinds. #[derive(Debug)] diff --git a/src/transport/smtp/extension.rs b/lettre/src/smtp/extension.rs similarity index 93% rename from src/transport/smtp/extension.rs rename to lettre/src/smtp/extension.rs index 113dbdd..6a279e4 100644 --- a/src/transport/smtp/extension.rs +++ b/lettre/src/smtp/extension.rs @@ -1,12 +1,12 @@ //! ESMTP features +use smtp::authentication::Mechanism; +use smtp::error::Error; +use smtp::response::Response; use std::collections::HashSet; use std::fmt; use std::fmt::{Display, Formatter}; use std::result::Result; -use transport::smtp::authentication::Mechanism; -use transport::smtp::error::Error; -use transport::smtp::response::Response; /// Supported ESMTP keywords #[derive(PartialEq,Eq,Hash,Clone,Debug)] @@ -53,13 +53,10 @@ pub struct ServerInfo { impl Display for ServerInfo { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, - "{} with {}", - self.name, - match self.features.is_empty() { - true => "no supported features".to_string(), - false => format!("{:?}", self.features), - }) + write!(f, "{} with {}", self.name, match self.features.is_empty() { + true => "no supported features".to_string(), + false => format!("{:?}", self.features), + }) } } @@ -125,9 +122,9 @@ impl ServerInfo { mod test { use super::{Extension, ServerInfo}; + use smtp::authentication::Mechanism; + use smtp::response::{Category, Code, Response, Severity}; use std::collections::HashSet; - use transport::smtp::authentication::Mechanism; - use transport::smtp::response::{Category, Code, Response, Severity}; #[test] fn test_extension_fmt() { diff --git a/src/transport/smtp/mod.rs b/lettre/src/smtp/mod.rs similarity index 96% rename from src/transport/smtp/mod.rs rename to lettre/src/smtp/mod.rs index a4504d6..5f577db 100644 --- a/src/transport/smtp/mod.rs +++ b/lettre/src/smtp/mod.rs @@ -1,15 +1,15 @@ //! This transport sends emails using the SMTP protocol -use email::SendableEmail; +use EmailTransport; +use SendableEmail; use openssl::ssl::{SslContext, SslMethod}; +use smtp::authentication::Mechanism; +use smtp::client::Client; +use smtp::error::{Error, SmtpResult}; +use smtp::extension::{Extension, ServerInfo}; use std::net::{SocketAddr, ToSocketAddrs}; use std::string::String; use std::time::Duration; -use transport::EmailTransport; -use transport::smtp::authentication::Mechanism; -use transport::smtp::client::Client; -use transport::smtp::error::{Error, SmtpResult}; -use transport::smtp::extension::{Extension, ServerInfo}; pub mod extension; pub mod authentication; @@ -318,7 +318,9 @@ impl EmailTransport for SmtpTransport { .as_ref() .unwrap() .supports_feature(&Extension::StartTls)) { - (&SecurityLevel::AlwaysEncrypt, false) => return Err(From::from("Could not encrypt connection, aborting")), + (&SecurityLevel::AlwaysEncrypt, false) => { + return Err(From::from("Could not encrypt connection, aborting")) + } (&SecurityLevel::Opportunistic, false) => (), (&SecurityLevel::NeverEncrypt, _) => (), (&SecurityLevel::EncryptedWrapper, _) => (), @@ -387,13 +389,13 @@ impl EmailTransport for SmtpTransport { (false, _) => None, }; - try_smtp!(self.client.mail(&email.envelope().from, mail_options), self); + try_smtp!(self.client.mail(&email.from(), mail_options), self); // Log the mail command - info!("{}: from=<{}>", message_id, email.envelope().from); + info!("{}: from=<{}>", message_id, email.from()); // Recipient - for to_address in &email.envelope().to { + for to_address in &email.to() { try_smtp!(self.client.rcpt(&to_address), self); // Log the rcpt command info!("{}: to=<{}>", message_id, to_address); diff --git a/src/transport/smtp/response.rs b/lettre/src/smtp/response.rs similarity index 96% rename from src/transport/smtp/response.rs rename to lettre/src/smtp/response.rs index 28471b5..9a249e9 100644 --- a/src/transport/smtp/response.rs +++ b/lettre/src/smtp/response.rs @@ -3,10 +3,10 @@ use self::Category::*; use self::Severity::*; +use smtp::error::{Error, SmtpResult}; use std::fmt::{Display, Formatter, Result}; use std::result; use std::str::FromStr; -use transport::smtp::error::{Error, SmtpResult}; /// First digit indicates severity #[derive(PartialEq,Eq,Copy,Clone,Debug)] @@ -36,14 +36,12 @@ impl FromStr for Severity { impl Display for Severity { fn fmt(&self, f: &mut Formatter) -> Result { - write!(f, - "{}", - match *self { - PositiveCompletion => 2, - PositiveIntermediate => 3, - TransientNegativeCompletion => 4, - PermanentNegativeCompletion => 5, - }) + write!(f, "{}", match *self { + PositiveCompletion => 2, + PositiveIntermediate => 3, + TransientNegativeCompletion => 4, + PermanentNegativeCompletion => 5, + }) } } @@ -81,16 +79,14 @@ impl FromStr for Category { impl Display for Category { fn fmt(&self, f: &mut Formatter) -> Result { - write!(f, - "{}", - match *self { - Syntax => 0, - Information => 1, - Connections => 2, - Unspecified3 => 3, - Unspecified4 => 4, - MailSystem => 5, - }) + write!(f, "{}", match *self { + Syntax => 0, + Information => 1, + Connections => 2, + Unspecified3 => 3, + Unspecified4 => 4, + MailSystem => 5, + }) } } diff --git a/src/transport/stub/error.rs b/lettre/src/stub/error.rs similarity index 100% rename from src/transport/stub/error.rs rename to lettre/src/stub/error.rs diff --git a/src/transport/stub/mod.rs b/lettre/src/stub/mod.rs similarity index 81% rename from src/transport/stub/mod.rs rename to lettre/src/stub/mod.rs index 295c3c6..ba2e454 100644 --- a/src/transport/stub/mod.rs +++ b/lettre/src/stub/mod.rs @@ -1,8 +1,8 @@ //! This transport is a stub that only logs the message, and always returns //! success -use email::SendableEmail; -use transport::EmailTransport; +use EmailTransport; +use SendableEmail; pub mod error; @@ -18,8 +18,8 @@ impl EmailTransport for StubEmailTransport { info!("{}: from=<{}> to=<{:?}>", email.message_id(), - email.envelope().from, - email.envelope().to); + email.from(), + email.to()); Ok(()) } diff --git a/tests/lib.rs b/lettre/tests/lib.rs similarity index 100% rename from tests/lib.rs rename to lettre/tests/lib.rs diff --git a/tests/transport_file.rs b/lettre/tests/transport_file.rs similarity index 68% rename from tests/transport_file.rs rename to lettre/tests/transport_file.rs index 8c0beb5..48992fc 100644 --- a/tests/transport_file.rs +++ b/lettre/tests/transport_file.rs @@ -1,9 +1,8 @@ extern crate lettre; -use lettre::email::{EmailBuilder, SendableEmail}; -use lettre::transport::EmailTransport; +use lettre::{EmailTransport, SendableEmail, SimpleSendableEmail}; -use lettre::transport::file::FileEmailTransport; +use lettre::file::FileEmailTransport; use std::env::temp_dir; use std::fs::File; use std::fs::remove_file; @@ -12,13 +11,10 @@ use std::io::Read; #[test] fn file_transport() { let mut sender = FileEmailTransport::new(temp_dir()); - let email = EmailBuilder::new() - .to("root@localhost") - .from("user@localhost") - .body("Hello World!") - .subject("Hello file") - .build() - .unwrap(); + let email = SimpleSendableEmail::new("user@localhost", + vec!["root@localhost"], + "file_id", + "Hello file"); let result = sender.send(email.clone()); assert!(result.is_ok()); diff --git a/lettre/tests/transport_sendmail.rs b/lettre/tests/transport_sendmail.rs new file mode 100644 index 0000000..db46558 --- /dev/null +++ b/lettre/tests/transport_sendmail.rs @@ -0,0 +1,17 @@ +extern crate lettre; + +use lettre::{EmailTransport, SimpleSendableEmail}; +use lettre::sendmail::SendmailTransport; + +#[test] +fn sendmail_transport_simple() { + let mut sender = SendmailTransport::new(); + let email = SimpleSendableEmail::new("user@localhost", + vec!["root@localhost"], + "sendmail_id", + "Hello sendmail"); + + let result = sender.send(email); + println!("{:?}", result); + assert!(result.is_ok()); +} diff --git a/lettre/tests/transport_smtp.rs b/lettre/tests/transport_smtp.rs new file mode 100644 index 0000000..4b87e3e --- /dev/null +++ b/lettre/tests/transport_smtp.rs @@ -0,0 +1,20 @@ +extern crate lettre; + +use lettre::{EmailTransport, SimpleSendableEmail}; +use lettre::smtp::SecurityLevel; +use lettre::smtp::SmtpTransportBuilder; + +#[test] +fn smtp_transport_simple() { + let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525") + .unwrap() + .security_level(SecurityLevel::Opportunistic) + .build(); + let email = SimpleSendableEmail::new("user@localhost", + vec!["root@localhost"], + "smtp_id", + "Hello smtp"); + + let result = sender.send(email); + assert!(result.is_ok()); +} diff --git a/lettre/tests/transport_stub.rs b/lettre/tests/transport_stub.rs new file mode 100644 index 0000000..d1b8f2d --- /dev/null +++ b/lettre/tests/transport_stub.rs @@ -0,0 +1,16 @@ +extern crate lettre; + +use lettre::{EmailTransport, SimpleSendableEmail}; +use lettre::stub::StubEmailTransport; + +#[test] +fn stub_transport() { + let mut sender = StubEmailTransport; + let email = SimpleSendableEmail::new("user@localhost", + vec!["root@localhost"], + "stub_id", + "Hello stub"); + + let result = sender.send(email); + assert!(result.is_ok()); +} diff --git a/lettre_email/CHANGELOG.md b/lettre_email/CHANGELOG.md new file mode 100644 index 0000000..ddba820 --- /dev/null +++ b/lettre_email/CHANGELOG.md @@ -0,0 +1,39 @@ +### v0.6.2 (2017-02-18) + +#### Features + +* **all** + * Update uuid crate to 0.4 + * Update env-logger crate to 0.4 + * Update openssl crate to 0.9 + +### v0.6.1 (2016-10-19) + +#### Features + +* **documentation** + * #91: Build seperate docs for each release + * #96: Add complete documentation information to README + +#### Bugfixes + +* **email** + * #85: Use address-list for "To", "From" etc. + +* **tests** + * #93: Force building tests before coverage computing + +### v0.6.0 (2016-05-05) + +#### Features + +* **email** + * multipart support + * add non-consuming methods for Email builders + +#### Beaking Change + +* **email** + * `add_header` does not return the builder anymore, + for consistency with other methods. Use the `header` + method instead diff --git a/lettre_email/Cargo.toml b/lettre_email/Cargo.toml new file mode 100644 index 0000000..3b8db2f --- /dev/null +++ b/lettre_email/Cargo.toml @@ -0,0 +1,22 @@ +[package] + +name = "lettre_email" +version = "0.7.0" +description = "Email builder" +readme = "README.md" +documentation = "https://docs.rs/lettre_email/" +repository = "https://github.com/lettre/lettre" +license = "MIT" +authors = ["Alexis Mousset "] +categories = ["email"] +keywords = ["email", "mailer"] + +[badges] +travis-ci = { repository = "lettre/lettre_email" } + +[dependencies] +email = "^0.0" +mime = "^0.2" +time = "^0.1" +uuid = { version = ">=0.4, <0.6", features = ["v4"] } +lettre = { path = "../lettre" } diff --git a/lettre_email/LICENSE b/lettre_email/LICENSE new file mode 120000 index 0000000..ea5b606 --- /dev/null +++ b/lettre_email/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/lettre_email/README.md b/lettre_email/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/lettre_email/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/lettre_email/examples/smtp.rs b/lettre_email/examples/smtp.rs new file mode 100644 index 0000000..6625189 --- /dev/null +++ b/lettre_email/examples/smtp.rs @@ -0,0 +1,31 @@ +extern crate lettre; +extern crate lettre_email; + +use lettre_email::email::EmailBuilder; +use lettre::EmailTransport; +use lettre::smtp::SmtpTransportBuilder; + +fn main() { + let email = EmailBuilder::new() + // Addresses can be specified by the tuple (email, alias) + .to(("user@example.org", "Firstname Lastname")) + // ... or by an address only + .from("user@example.com") + .subject("Hi, Hello world") + .text("Hello world.") + .build() + .unwrap(); + + // Open a local connection on port 25 + let mut mailer = SmtpTransportBuilder::localhost().unwrap().build(); + // Send the email + let result = mailer.send(email); + + if result.is_ok() { + println!("Email sent"); + } else { + println!("Could not send email: {:?}", result); + } + + assert!(result.is_ok()); +} diff --git a/lettre_email/rustfmt.toml b/lettre_email/rustfmt.toml new file mode 120000 index 0000000..39f97b0 --- /dev/null +++ b/lettre_email/rustfmt.toml @@ -0,0 +1 @@ +../rustfmt.toml \ No newline at end of file diff --git a/src/email/mod.rs b/lettre_email/src/email.rs similarity index 95% rename from src/email/mod.rs rename to lettre_email/src/email.rs index dca15ea..b3ab358 100644 --- a/src/email/mod.rs +++ b/lettre_email/src/email.rs @@ -1,14 +1,13 @@ //! Simple email representation -pub mod error; - -use email::error::Error; +use error::Error; use email_format::{Address, Header, Mailbox, MimeMessage, MimeMultipartType}; use mime::Mime; use std::fmt; use std::fmt::{Display, Formatter}; use time::{Tm, now}; use uuid::Uuid; +use lettre::SendableEmail; /// Converts an address or an address with an alias to a `Header` pub trait IntoHeader { @@ -638,8 +637,7 @@ impl EmailBuilder { } // Add the sender header, if any. if let Some(ref v) = self.sender_header { - self.message - .add_header(("Sender", v.to_string().as_ref())); + self.message.add_header(("Sender", v.to_string().as_ref())); } // Calculate the envelope let envelope = match self.envelope { @@ -732,58 +730,13 @@ impl EmailBuilder { } } -/// Email sendable by an SMTP client -pub trait SendableEmail { - /// Envelope - fn envelope(&self) -> &Envelope; - /// Message ID - fn message_id(&self) -> String; - /// Message content - fn message(self) -> String; -} - -/// Minimal email structure -#[derive(Debug)] -pub struct SimpleSendableEmail { - /// Message envelope - envelope: Envelope, - /// Message content - message: String, -} - -impl SimpleSendableEmail { - /// Returns a new email - pub fn new(from_address: String, - to_addresses: Vec, - message: String) - -> SimpleSendableEmail { - SimpleSendableEmail { - envelope: Envelope { - from: from_address, - to: to_addresses, - }, - message: message, - } - } -} - -impl SendableEmail for SimpleSendableEmail { - fn envelope(&self) -> &Envelope { - &self.envelope - } - - fn message_id(&self) -> String { - format!("{}", Uuid::new_v4()) - } - - fn message(self) -> String { - self.message - } -} - impl SendableEmail for Email { - fn envelope(&self) -> &Envelope { - &self.envelope + fn to(&self) -> Vec { + self.envelope.to.clone() + } + + fn from(&self) -> String { + self.envelope.from.clone() } fn message_id(&self) -> String { @@ -823,7 +776,8 @@ pub trait ExtractableEmail { #[cfg(test)] mod test { - use super::{Email, EmailBuilder, Envelope, IntoEmail, SendableEmail, SimpleEmail}; + use lettre::SendableEmail; + use super::{Email, EmailBuilder, Envelope, IntoEmail, SimpleEmail}; use email_format::{Header, MimeMessage}; use time::now; @@ -959,8 +913,8 @@ mod test { .build() .unwrap(); - assert_eq!(email.envelope().from, "sender@localhost".to_string()); - assert_eq!(email.envelope().to, + assert_eq!(email.from(), "sender@localhost".to_string()); + assert_eq!(email.to(), vec!["user@localhost".to_string(), "cc@localhost".to_string(), "bcc@localhost".to_string()]); diff --git a/src/email/error.rs b/lettre_email/src/error.rs similarity index 100% rename from src/email/error.rs rename to lettre_email/src/error.rs diff --git a/lettre_email/src/lib.rs b/lettre_email/src/lib.rs new file mode 100644 index 0000000..bb1dfac --- /dev/null +++ b/lettre_email/src/lib.rs @@ -0,0 +1,65 @@ +//! Lettre is a mailer written in Rust. It provides a simple email builder and several transports. +//! +//! ## Overview +//! +//! The `email` part builds email messages. For now, it does not support attachments. +//! An email is built using an `EmailBuilder`. The simplest email could be: +//! +//! ```rust +//! use lettre_email::email::EmailBuilder; +//! +//! // Create an email +//! let email = EmailBuilder::new() +//! // Addresses can be specified by the tuple (email, alias) +//! .to(("user@example.org", "Firstname Lastname")) +//! // ... or by an address only +//! .from("user@example.com") +//! .subject("Hi, Hello world") +//! .text("Hello world.") +//! .build(); +//! +//! assert!(email.is_ok()); +//! ``` +//! +//! When the `build` method is called, the `EmailBuilder` will add the missing headers (like +//! `Message-ID` or `Date`) and check for missing necessary ones (like `From` or `To`). It will +//! then generate an `Email` that can be sent. +//! +//! The `text()` method will create a plain text email, while the `html()` method will create an +//! HTML email. You can use the `alternative()` method to provide both versions, using plain text +//! as fallback for the HTML version. +//! +//! Below is a more complete example, not using method chaining: +//! +//! ```rust +//! use lettre_email::email::EmailBuilder; +//! +//! let mut builder = EmailBuilder::new(); +//! builder.add_to(("user@example.org", "Alias name")); +//! builder.add_cc(("user@example.net", "Alias name")); +//! builder.add_from("no-reply@example.com"); +//! builder.add_from("no-reply@example.eu"); +//! builder.set_sender("no-reply@example.com"); +//! builder.set_subject("Hello world"); +//! builder.set_alternative("

Hi, Hello world.

", "Hi, Hello world."); +//! builder.add_reply_to("contact@example.com"); +//! builder.add_header(("X-Custom-Header", "my header")); +//! +//! let email = builder.build(); +//! assert!(email.is_ok()); +//! ``` +//! +//! See the `EmailBuilder` documentation for a complete list of methods. +//! + +#![deny(missing_docs, unsafe_code, unstable_features, warnings, missing_debug_implementations)] + +#[macro_use] +extern crate mime; +extern crate time; +extern crate uuid; +extern crate email as email_format; +extern crate lettre; + +pub mod error; +pub mod email; diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 0a6378c..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1,2 +0,0 @@ -reorder_imports = true -reorder_imported_names = true \ No newline at end of file diff --git a/src/transport/mod.rs b/src/transport/mod.rs deleted file mode 100644 index a007604..0000000 --- a/src/transport/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Represents an Email transport - -pub mod smtp; -pub mod sendmail; -pub mod stub; -pub mod file; - -use email::SendableEmail; - -/// Transport method for emails -pub trait EmailTransport { - /// Sends the email - fn send(&mut self, email: T) -> U; - /// Close the transport explicitly - fn close(&mut self); -} diff --git a/tests/transport_sendmail.rs b/tests/transport_sendmail.rs deleted file mode 100644 index 3586d34..0000000 --- a/tests/transport_sendmail.rs +++ /dev/null @@ -1,20 +0,0 @@ -extern crate lettre; - -use lettre::email::EmailBuilder; -use lettre::transport::EmailTransport; -use lettre::transport::sendmail::SendmailTransport; - -#[test] -fn sendmail_transport_simple() { - let mut sender = SendmailTransport::new(); - let email = EmailBuilder::new() - .to("root@localhost") - .from("user@localhost") - .body("Hello World!") - .subject("Hello sendmail") - .build() - .unwrap(); - let result = sender.send(email); - println!("{:?}", result); - assert!(result.is_ok()); -} diff --git a/tests/transport_smtp.rs b/tests/transport_smtp.rs deleted file mode 100644 index 56ae8dd..0000000 --- a/tests/transport_smtp.rs +++ /dev/null @@ -1,23 +0,0 @@ -extern crate lettre; - -use lettre::email::EmailBuilder; -use lettre::transport::EmailTransport; -use lettre::transport::smtp::SecurityLevel; -use lettre::transport::smtp::SmtpTransportBuilder; - -#[test] -fn smtp_transport_simple() { - let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525") - .unwrap() - .security_level(SecurityLevel::Opportunistic) - .build(); - let email = EmailBuilder::new() - .to("root@localhost") - .from("user@localhost") - .body("Hello World!") - .subject("Hello smtp") - .build() - .unwrap(); - let result = sender.send(email); - assert!(result.is_ok()); -} diff --git a/tests/transport_stub.rs b/tests/transport_stub.rs deleted file mode 100644 index 468cb80..0000000 --- a/tests/transport_stub.rs +++ /dev/null @@ -1,19 +0,0 @@ -extern crate lettre; - -use lettre::email::EmailBuilder; -use lettre::transport::EmailTransport; -use lettre::transport::stub::StubEmailTransport; - -#[test] -fn stub_transport() { - let mut sender = StubEmailTransport; - let email = EmailBuilder::new() - .to("root@localhost") - .from("user@localhost") - .body("Hello World!") - .subject("Hello stub") - .build() - .unwrap(); - let result = sender.send(email); - assert!(result.is_ok()); -}