refactor(all): split email and transport into different crates

This commit is contained in:
Alexis Mousset
2017-05-20 11:38:48 +02:00
parent 87aa3ca701
commit ae640da631
45 changed files with 528 additions and 498 deletions

View File

@@ -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:

View File

@@ -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-*

View File

@@ -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 "<meta http-equiv=refresh content=0;url=lettre/index.html>" > ./master/index.html
elif [ "$TRAVIS_TAG" != "" ]; then
rm -rf $TRAVIS_TAG
mv ../target/doc ./$TRAVIS_TAG
echo "<meta http-equiv=refresh content=0;url=lettre/index.html>" > ./$TRAVIS_TAG/index.html
latest=$(echo * | tr " " "\n" | sort -V -r | head -n1)
if [ "$TRAVIS_TAG" == "$latest" ]; then
echo "<meta http-equiv=refresh content=0;url=$latest/lettre/index.html>" > index.html
fi
else
exit 0
fi
git add -A .
git commit -m "Rebuild pages at ${TRAVIS_COMMIT}"
git push --quiet origin gh-pages

View File

@@ -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 <contact@amousset.me>"]
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",
]

View File

@@ -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()
}

29
lettre/Cargo.toml Normal file
View File

@@ -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 <contact@amousset.me>"]
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 = []

1
lettre/LICENSE Symbolic link
View File

@@ -0,0 +1 @@
../LICENSE

1
lettre/README.md Symbolic link
View File

@@ -0,0 +1 @@
../README.md

View File

@@ -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()
}

24
lettre/examples/smtp.rs Normal file
View File

@@ -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());
}

1
lettre/rustfmt.toml Symbolic link
View File

@@ -0,0 +1 @@
../rustfmt.toml

View File

@@ -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<FileResult> 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()));

View File

@@ -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("<h2>Hi, Hello world.</h2>", "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<NetworkStream> = 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<String>;
/// 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<U> {
/// Sends the email
fn send<T: SendableEmail>(&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<String>,
/// 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<String> {
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
}
}

View File

@@ -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<SendmailResult> for SendmailTransport {
fn send<T: SendableEmail>(&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());

View File

@@ -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",
})
}
}

View File

@@ -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<S: Connector + Timeout + Write + Read + Debug> Client<S> {
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<S: Connector + Timeout + Write + Read + Debug> Client<S> {
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);

View File

@@ -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)]

View File

@@ -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() {

View File

@@ -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<SmtpResult> 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<SmtpResult> 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);

View File

@@ -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,
})
}
}

View File

@@ -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<StubResult> for StubEmailTransport {
info!("{}: from=<{}> to=<{:?}>",
email.message_id(),
email.envelope().from,
email.envelope().to);
email.from(),
email.to());
Ok(())
}

View File

@@ -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());

View File

@@ -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());
}

View File

@@ -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());
}

View File

@@ -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());
}

39
lettre_email/CHANGELOG.md Normal file
View File

@@ -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

22
lettre_email/Cargo.toml Normal file
View File

@@ -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 <contact@amousset.me>"]
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" }

1
lettre_email/LICENSE Symbolic link
View File

@@ -0,0 +1 @@
../LICENSE

1
lettre_email/README.md Symbolic link
View File

@@ -0,0 +1 @@
../README.md

View File

@@ -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());
}

1
lettre_email/rustfmt.toml Symbolic link
View File

@@ -0,0 +1 @@
../rustfmt.toml

View File

@@ -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<String>,
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<String> {
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()]);

65
lettre_email/src/lib.rs Normal file
View File

@@ -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("<h2>Hi, Hello world.</h2>", "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;

View File

@@ -1,2 +0,0 @@
reorder_imports = true
reorder_imported_names = true

View File

@@ -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<U> {
/// Sends the email
fn send<T: SendableEmail>(&mut self, email: T) -> U;
/// Close the transport explicitly
fn close(&mut self);
}

View File

@@ -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());
}

View File

@@ -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());
}

View File

@@ -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());
}