Compare commits

...

13 Commits

Author SHA1 Message Date
Alexis Mousset
a468cb3ab8 chore(all): 0.9.3 release 2020-04-19 10:56:46 +02:00
Alexis Mousset
9b591ff932 chore(all): Fix warnings 2020-04-19 10:24:23 +02:00
Alexis Mousset
eff4e1693f chore(builder): Update email to 0.0.21 2020-04-19 09:56:31 +02:00
Alexis Mousset
75ab05229a feat(all): v0.9.2 release 2019-06-11 19:58:32 +02:00
Alexis Mousset
393ef8dcd1 Simplify header formatting and fix nightly build (fixes #340) 2019-06-11 19:48:01 +02:00
Alexis Mousset
ceb57edfdd Remove failure crate usage (fixes #331) 2019-05-06 09:36:10 +02:00
Alexis Mousset
cf8f934c56 fix(docs): Broken title syntax in SMTP docs 2019-05-05 21:01:36 +02:00
Alexis Mousset
df949f837e fix(all): Properly override favicon in docs theme 2019-05-05 20:49:12 +02:00
Alexis Mousset
0a3d51dc25 fix(docs): Use doc root and set custom favicon 2019-05-05 20:15:45 +02:00
Alexis Mousset
4828cf4e92 feat(all): Require rust 1.32 2019-05-05 20:07:38 +02:00
Alexis Mousset
c33de49fbb feat(all): Move to mdBook for the docs 2019-05-05 19:45:51 +02:00
Alexis Mousset
4f470a2c3f feat(all): 0.9.1 release 2019-05-05 18:20:30 +02:00
Alexis Mousset
a0c8fb947c fix(email): Re-export mime crate 2019-05-05 18:16:05 +02:00
30 changed files with 218 additions and 154 deletions

View File

@@ -4,7 +4,7 @@ set -xe
cd website
make clean && make
echo "lettre.at" > _book/CNAME
echo "lettre.at" > _book/html/CNAME
sudo pip install ghp-import
ghp-import -n _book
git push -f https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
ghp-import -n _book/html
git push -f https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages

View File

@@ -3,7 +3,7 @@ rust:
- stable
- beta
- nightly
- 1.26.0
- 1.32.0
matrix:
allow_failures:
- rust: nightly
@@ -19,13 +19,11 @@ addons:
- gcc
- binutils-dev
- libiberty-dev
- npm
before_script:
- smtp-sink 2525 1000&
- sudo chgrp -R postdrop /var/spool/postfix/maildrop
- sudo npm set strict-ssl false && sudo npm install -g gitbook-cli
script:
- cargo test --verbose --all --all-features
after_success:
- ./.build-scripts/codecov.sh
- '[ "$TRAVIS_BRANCH" = "v0.9.x" ] && [ $TRAVIS_PULL_REQUEST = false ] && ./.build-scripts/site-upload.sh'
- '[ "$TRAVIS_RUST_VERSION" = "stable" ] && [ "$TRAVIS_BRANCH" = "v0.9.x" ] && [ $TRAVIS_PULL_REQUEST = false ] && ./.build-scripts/site-upload.sh'

View File

@@ -1,3 +1,30 @@
<a name="v0.9.3"></a>
### v0.9.3 (2020-04-19)
#### Bug Fixes
* **all:**
* Fix compilation warnings ([9b591ff](https://github.com/lettre/lettre/commit/9b591ff932e35947f793aaaeec0e3f06e8818449))
* **email**
* Update `rust-email` to 0.0.21 ([eff4e169](https://github.com/lettre/lettre/commit/eff4e1693f5e65430b851707fdfd18046bc796e3))
<a name="v0.9.2"></a>
### v0.9.2 (2019-06-11)
#### Bug Fixes
* **email:**
* Fix compilation with Rust 1.36+ ([393ef8d](https://github.com/lettre/lettre/commit/393ef8dcd1b1c6a6119d0666d5f09b12f50f6b4b))
<a name="v0.9.1"></a>
### v0.9.1 (2019-05-05)
#### Features
* **email:**
* Re-export mime crate ([a0c8fb9](https://github.com/lettre/lettre/commit/a0c8fb9))
<a name="v0.9.0"></a>
### v0.9.0 (2019-03-17)

View File

@@ -34,7 +34,7 @@ Lettre provides the following features:
## Example
This library requires Rust 1.26 or newer.
This library requires Rust 1.32 or newer.
To use this library, add the following to your `Cargo.toml`:
```toml

View File

@@ -1,7 +1,7 @@
[package]
name = "lettre"
version = "0.9.0" # remember to update html_root_url
version = "0.9.3" # remember to update html_root_url
description = "Email client"
readme = "README.md"
homepage = "http://lettre.at"
@@ -28,8 +28,6 @@ hostname = { version = "^0.1", optional = true }
serde = { version = "^1.0", optional = true }
serde_json = { version = "^1.0", optional = true }
serde_derive = { version = "^1.0", optional = true }
failure = "^0.1"
failure_derive = "^0.1"
fast_chemail = "^0.9"
r2d2 = { version = "^0.8", optional = true}

View File

@@ -1,18 +1,35 @@
use failure;
use self::Error::*;
use std::{
error::Error as StdError,
fmt::{self, Display, Formatter},
};
/// Error type for email content
#[derive(Fail, Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy)]
pub enum Error {
/// Missing from in envelope
#[fail(display = "missing source address, invalid envelope")]
MissingFrom,
/// Missing to in envelope
#[fail(display = "missing destination address, invalid envelope")]
MissingTo,
/// Invalid email
#[fail(display = "invalid email address")]
InvalidEmailAddress,
}
impl Display for Error {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
fmt.write_str(&match *self {
MissingFrom => "missing source address, invalid envelope".to_owned(),
MissingTo => "missing destination address, invalid envelope".to_owned(),
InvalidEmailAddress => "invalid email address".to_owned(),
})
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
None
}
}
/// Email result type
pub type EmailResult<T> = Result<T, failure::Error>;
pub type EmailResult<T> = Result<T, Error>;

View File

@@ -1,40 +1,61 @@
//! Error and result type for file transport
use failure;
use self::Error::*;
use serde_json;
use std::io;
use std::{
error::Error as StdError,
fmt::{self, Display, Formatter},
};
/// An enum of all error kinds.
#[derive(Fail, Debug)]
#[derive(Debug)]
pub enum Error {
/// Internal client error
#[fail(display = "Internal client error: {}", error)]
Client { error: &'static str },
Client(&'static str),
/// IO error
#[fail(display = "IO error: {}", error)]
Io { error: io::Error },
Io(io::Error),
/// JSON serialization error
#[fail(display = "JSON serialization error: {}", error)]
JsonSerialization { error: serde_json::Error },
JsonSerialization(serde_json::Error),
}
impl Display for Error {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
match *self {
Client(err) => fmt.write_str(err),
Io(ref err) => err.fmt(fmt),
JsonSerialization(ref err) => err.fmt(fmt),
}
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match *self {
Io(ref err) => Some(&*err),
JsonSerialization(ref err) => Some(&*err),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::Io { error: err }
Error::Io(err)
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Error {
Error::JsonSerialization { error: err }
Error::JsonSerialization(err)
}
}
impl From<&'static str> for Error {
fn from(string: &'static str) -> Error {
Error::Client { error: string }
Error::Client(string)
}
}
/// SMTP result type
pub type FileResult = Result<(), failure::Error>;
pub type FileResult = Result<(), Error>;

View File

@@ -3,7 +3,7 @@
//! This mailer contains the available transports for your emails.
//!
#![doc(html_root_url = "https://docs.rs/lettre/0.9.0")]
#![doc(html_root_url = "https://docs.rs/lettre/0.9.3")]
#![deny(
missing_copy_implementations,
trivial_casts,
@@ -30,14 +30,11 @@ extern crate serde;
#[cfg(feature = "serde-impls")]
#[macro_use]
extern crate serde_derive;
extern crate failure;
#[cfg(feature = "file-transport")]
extern crate serde_json;
#[macro_use]
extern crate failure_derive;
extern crate fast_chemail;
#[cfg(feature = "connection-pool")]
extern crate r2d2;
#[cfg(feature = "file-transport")]
extern crate serde_json;
pub mod error;
#[cfg(feature = "file-transport")]
@@ -49,8 +46,7 @@ pub mod smtp;
pub mod stub;
use error::EmailResult;
use error::Error as EmailError;
use failure::Error;
use error::Error;
use fast_chemail::is_valid_email;
#[cfg(feature = "file-transport")]
pub use file::FileTransport;
@@ -77,7 +73,7 @@ pub struct EmailAddress(String);
impl EmailAddress {
pub fn new(address: String) -> EmailResult<EmailAddress> {
if !is_valid_email(&address) && !address.ends_with("localhost") {
Err(EmailError::InvalidEmailAddress)?;
return Err(Error::InvalidEmailAddress);
}
Ok(EmailAddress(address))
}
@@ -127,7 +123,7 @@ impl Envelope {
/// Creates a new envelope, which may fail if `to` is empty.
pub fn new(from: Option<EmailAddress>, to: Vec<EmailAddress>) -> EmailResult<Envelope> {
if to.is_empty() {
Err(EmailError::MissingTo)?;
return Err(Error::MissingTo);
}
Ok(Envelope {
forward_path: to,
@@ -147,7 +143,7 @@ impl Envelope {
}
pub enum Message {
Reader(Box<Read + Send>),
Reader(Box<dyn Read + Send>),
Bytes(Cursor<Vec<u8>>),
}
@@ -179,7 +175,7 @@ impl SendableEmail {
pub fn new_with_reader(
envelope: Envelope,
message_id: String,
message: Box<Read + Send>,
message: Box<dyn Read + Send>,
) -> SendableEmail {
SendableEmail {
envelope,

View File

@@ -1,30 +1,50 @@
//! Error and result type for sendmail transport
use failure;
use self::Error::*;
use std::io;
use std::{
error::Error as StdError,
fmt::{self, Display, Formatter},
};
/// An enum of all error kinds.
#[derive(Fail, Debug)]
#[derive(Debug)]
pub enum Error {
/// Internal client error
#[fail(display = "Internal client error: {}", error)]
Client { error: &'static str },
Client(&'static str),
/// IO error
#[fail(display = "IO error: {}", error)]
Io { error: io::Error },
Io(io::Error),
}
impl Display for Error {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
match *self {
Client(ref err) => err.fmt(fmt),
Io(ref err) => err.fmt(fmt),
}
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match *self {
Io(ref err) => Some(&*err),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::Io { error: err }
Error::Io(err)
}
}
impl From<&'static str> for Error {
fn from(string: &'static str) -> Error {
Error::Client { error: string }
Error::Client(string)
}
}
/// sendmail result type
pub type SendmailResult = Result<(), failure::Error>;
pub type SendmailResult = Result<(), Error>;

View File

@@ -72,9 +72,7 @@ impl<'a> Transport<'a> for SendmailTransport {
Ok(())
} else {
// TODO display stderr
Err(error::Error::Client {
error: "The message could not be sent",
})?
Err(error::Error::Client("The message could not be sent"))
}
}
}

View File

@@ -193,7 +193,7 @@ impl<S: Connector + Write + Read + Timeout + Debug> InnerClient<S> {
}
/// Sends the message content
pub fn message(&mut self, message: Box<Read>) -> SmtpResult {
pub fn message(&mut self, message: Box<dyn Read>) -> SmtpResult {
let mut out_buf: Vec<u8> = vec![];
let mut codec = ClientCodec::new();

View File

@@ -245,7 +245,7 @@ impl AuthCommand {
challenge: Option<String>,
) -> Result<AuthCommand, Error> {
let response = if mechanism.supports_initial_response() || challenge.is_some() {
Some(mechanism.response(&credentials, challenge.as_ref().map(String::as_str))?)
Some(mechanism.response(&credentials, challenge.as_deref())?)
} else {
None
};

View File

@@ -42,36 +42,31 @@ pub enum Error {
impl Display for Error {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
fmt.write_str(self.description())
match *self {
// Try to display the first line of the server's response that usually
// contains a short humanly readable error message
Transient(ref err) => fmt.write_str(match err.first_line() {
Some(line) => line,
None => "transient error during SMTP transaction",
}),
Permanent(ref err) => fmt.write_str(match err.first_line() {
Some(line) => line,
None => "permanent error during SMTP transaction",
}),
ResponseParsing(err) => fmt.write_str(err),
ChallengeParsing(ref err) => err.fmt(fmt),
Utf8Parsing(ref err) => err.fmt(fmt),
Resolution => fmt.write_str("could not resolve hostname"),
Client(err) => fmt.write_str(err),
Io(ref err) => err.fmt(fmt),
Tls(ref err) => err.fmt(fmt),
Parsing(ref err) => fmt.write_str(err.description()),
}
}
}
impl StdError for Error {
#[cfg_attr(feature = "cargo-clippy", allow(clippy::match_same_arms))]
fn description(&self) -> &str {
match *self {
// Try to display the first line of the server's response that usually
// contains a short humanly readable error message
Transient(ref err) => match err.first_line() {
Some(line) => line,
None => "undetailed transient error during SMTP transaction",
},
Permanent(ref err) => match err.first_line() {
Some(line) => line,
None => "undetailed permanent error during SMTP transaction",
},
ResponseParsing(err) => err,
ChallengeParsing(ref err) => err.description(),
Utf8Parsing(ref err) => err.description(),
Resolution => "could not resolve hostname",
Client(err) => err,
Io(ref err) => err.description(),
Tls(ref err) => err.description(),
Parsing(ref err) => err.description(),
}
}
fn cause(&self) -> Option<&StdError> {
fn cause(&self) -> Option<&dyn StdError> {
match *self {
ChallengeParsing(ref err) => Some(&*err),
Utf8Parsing(ref err) => Some(&*err),

View File

@@ -1,7 +1,7 @@
[package]
name = "lettre_email"
version = "0.9.0" # remember to update html_root_url
version = "0.9.3" # remember to update html_root_url
description = "Email builder"
readme = "README.md"
homepage = "http://lettre.at"
@@ -23,11 +23,9 @@ lettre = { version = "^0.9", path = "../lettre", features = ["smtp-transport"] }
glob = "0.3"
[dependencies]
email = "^0.0.20"
email = "^0.0.21"
mime = "^0.3"
time = "^0.1"
uuid = { version = "^0.7", features = ["v4"] }
lettre = { version = "^0.9", path = "../lettre", default-features = false }
base64 = "^0.10"
failure = "^0.1"
failure_derive = "^0.1"

View File

@@ -2,35 +2,52 @@
use lettre;
use std::io;
use std::{
error::Error as StdError,
fmt::{self, Display, Formatter},
};
use self::Error::*;
/// An enum of all error kinds.
#[derive(Debug, Fail)]
#[derive(Debug)]
pub enum Error {
/// Envelope error
#[fail(display = "lettre error: {}", error)]
Envelope {
/// inner error
error: lettre::error::Error,
},
Envelope(lettre::error::Error),
/// Unparseable filename for attachment
#[fail(display = "the attachment filename could not be parsed")]
CannotParseFilename,
/// IO error
#[fail(display = "IO error: {}", error)]
Io {
/// inner error
error: io::Error,
},
Io(io::Error),
}
impl Display for Error {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
fmt.write_str(&match *self {
CannotParseFilename => "Could not parse attachment filename".to_owned(),
Io(ref err) => err.to_string(),
Envelope(ref err) => err.to_string(),
})
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match *self {
Envelope(ref err) => Some(err),
Io(ref err) => Some(err),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::Io { error: err }
Error::Io(err)
}
}
impl From<lettre::error::Error> for Error {
fn from(err: lettre::error::Error) -> Error {
Error::Envelope { error: err }
Error::Envelope(err)
}
}

View File

@@ -1,7 +1,7 @@
//! Lettre is a mailer written in Rust. lettre_email provides a simple email builder.
//!
#![doc(html_root_url = "https://docs.rs/lettre_email/0.9.0")]
#![doc(html_root_url = "https://docs.rs/lettre_email/0.9.3")]
#![deny(
missing_docs,
missing_debug_implementations,
@@ -13,22 +13,17 @@
unused_import_braces
)]
extern crate failure;
#[macro_use]
extern crate failure_derive;
extern crate base64;
extern crate email as email_format;
extern crate lettre;
extern crate mime;
pub extern crate mime;
extern crate time;
extern crate uuid;
pub mod error;
pub use email_format::{Address, Header, Mailbox, MimeMessage, MimeMultipartType};
use error::Error as EmailError;
use failure::Error;
use error::Error;
use lettre::{error::Error as LettreError, EmailAddress, Envelope, SendableEmail};
use mime::Mime;
use std::fs;
@@ -136,7 +131,7 @@ impl PartBuilder {
/// Adds a `ContentType` header with the given MIME type
pub fn content_type(self, content_type: &Mime) -> PartBuilder {
self.header(("Content-Type", format!("{}", content_type).as_ref()))
self.header(("Content-Type", content_type.to_string()))
}
/// Adds a child part
@@ -263,7 +258,7 @@ impl EmailBuilder {
filename.unwrap_or(
path.file_name()
.and_then(|x| x.to_str())
.ok_or(EmailError::CannotParseFilename)?,
.ok_or(Error::CannotParseFilename)?,
),
content_type,
)
@@ -306,10 +301,7 @@ impl EmailBuilder {
pub fn text<S: Into<String>>(self, body: S) -> EmailBuilder {
let text = PartBuilder::new()
.body(body)
.header((
"Content-Type",
format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref(),
))
.header(("Content-Type", mime::TEXT_PLAIN_UTF_8.to_string()))
.build();
self.child(text)
}
@@ -318,10 +310,7 @@ impl EmailBuilder {
pub fn html<S: Into<String>>(self, body: S) -> EmailBuilder {
let html = PartBuilder::new()
.body(body)
.header((
"Content-Type",
format!("{}", mime::TEXT_HTML_UTF_8).as_ref(),
))
.header(("Content-Type", mime::TEXT_HTML_UTF_8.to_string()))
.build();
self.child(html)
}
@@ -334,18 +323,12 @@ impl EmailBuilder {
) -> EmailBuilder {
let text = PartBuilder::new()
.body(body_text)
.header((
"Content-Type",
format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref(),
))
.header(("Content-Type", mime::TEXT_PLAIN_UTF_8.to_string()))
.build();
let html = PartBuilder::new()
.body(body_html)
.header((
"Content-Type",
format!("{}", mime::TEXT_HTML_UTF_8).as_ref(),
))
.header(("Content-Type", mime::TEXT_HTML_UTF_8.to_string()))
.build();
let alternate = PartBuilder::new()
@@ -384,7 +367,7 @@ impl EmailBuilder {
}
// Add the sender header, if any.
if let Some(ref v) = self.sender {
self.message = self.message.header(("Sender", v.to_string().as_ref()));
self.message = self.message.header(("Sender", v.to_string()));
}
// Calculate the envelope
let envelope = match self.envelope {
@@ -404,7 +387,7 @@ impl EmailBuilder {
}
}
let from = Some(EmailAddress::from_str(&match self.sender {
Some(x) => Ok(x.address.clone()), // if we have a sender_header, use it
Some(x) => Ok(x.address), // if we have a sender_header, use it
None => {
// use a from header
debug_assert!(self.from.len() <= 1); // else we'd have sender_header
@@ -416,15 +399,11 @@ impl EmailBuilder {
// if it's an author group, use the first author
Some(mailbox) => Ok(mailbox.address.clone()),
// for an empty author group (the rarest of the rare cases)
None => Err(EmailError::Envelope {
error: LettreError::MissingFrom,
}), // empty envelope sender
None => Err(Error::Envelope(LettreError::MissingFrom)), // empty envelope sender
},
},
// if we don't have a from header
None => Err(EmailError::Envelope {
error: LettreError::MissingFrom,
}), // empty envelope sender
None => Err(Error::Envelope(LettreError::MissingFrom)), // empty envelope sender
}
}
}?)?);
@@ -448,9 +427,7 @@ impl EmailBuilder {
.message
.header(Header::new_with_value("From".into(), from).unwrap());
} else {
Err(EmailError::Envelope {
error: LettreError::MissingFrom,
})?;
return Err(Error::Envelope(LettreError::MissingFrom));
}
if !self.cc.is_empty() {
self.message = self
@@ -476,7 +453,7 @@ impl EmailBuilder {
if !self.date_issued {
self.message = self
.message
.header(("Date", Tm::rfc822z(&now()).to_string().as_ref()));
.header(("Date", Tm::rfc822z(&now()).to_string()));
}
self.message = self.message.header(("MIME-Version", "1.0"));
@@ -602,5 +579,4 @@ mod test {
.as_slice()
);
}
}

1
website/.gitignore vendored
View File

@@ -1,2 +1 @@
node_modules
/_book

View File

@@ -1,13 +1,16 @@
all: depends _book
depends:
gitbook install
cargo install --force mdbook --vers "^0.2"
cargo install --force mdbook-linkcheck --vers "^0.2"
serve:
gitbook serve
mdbook serve
_book:
gitbook build
mdbook build
clean:
rm -rf _book/
.PHONY: _book

View File

@@ -1,10 +0,0 @@
{
"root": "./content",
"plugins": [ "-sharing", "edit-link" ],
"pluginsConfig": {
"edit-link": {
"base": "https://github.com/lettre/lettre/edit/master/website/content",
"label": "Edit"
}
}
}

11
website/book.toml Normal file
View File

@@ -0,0 +1,11 @@
[book]
title = "Lettre"
author = "Alexis Mousset"
description = "The user documentation of the Lettre crate."
[build]
build-dir = "_book"
[output.html]
[output.linkcheck]

View File

@@ -10,7 +10,7 @@ Lettre is an email library that allows creating and sending messages. It provide
The `lettre_email` crate allows you to compose messages, and the `lettre`
provide transports to send them.
Lettre requires Rust 1.26 or newer. Add the following to your `Cargo.toml`:
Lettre requires Rust 1.32 or newer. Add the following to your `Cargo.toml`:
```toml
[dependencies]

View File

@@ -1,4 +1,4 @@
SMTP Transport
#### SMTP Transport
This transport uses the SMTP protocol to send emails over the network (locally or remotely).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB