summaryrefslogtreecommitdiffstats
path: root/smtp
diff options
context:
space:
mode:
authorPhilipp Korber <p.korber@1aim.com>2018-11-16 16:15:42 +0100
committerPhilipp Korber <p.korber@1aim.com>2018-11-16 16:15:42 +0100
commitcaf049791b8e7107734cd1f8917d51c91a2b80ad (patch)
tree338b7edede2a3df91e6712f0567bce61a705fc66 /smtp
parentbad94a94285a6d13cb02c26702c525a9e1b3e32f (diff)
refactor: prepared to be merged into different repository
Diffstat (limited to 'smtp')
-rw-r--r--smtp/Cargo.toml23
-rw-r--r--smtp/README.md88
-rw-r--r--smtp/src/error.rs116
-rw-r--r--smtp/src/lib.rs109
-rw-r--r--smtp/src/request.rs299
-rw-r--r--smtp/src/resolve_all.rs81
-rw-r--r--smtp/src/send_mail.rs139
7 files changed, 855 insertions, 0 deletions
diff --git a/smtp/Cargo.toml b/smtp/Cargo.toml
new file mode 100644
index 0000000..536a92e
--- /dev/null
+++ b/smtp/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+authors = ["Philipp Korber <p.korber@1aim.com>"]
+name = "mail-smtp"
+version = "0.2.0-wip"
+categories = []
+description = "[internal/mail-api] combines mail-core with new-tokio-smtp"
+documentation = "https://docs.rs/mail-smtp"
+keywords = ["mail-api"]
+license = "MIT OR Apache-2.0"
+readme = "./README.md"
+repository = "https://github.com/1aim/mail-smtp"
+
+[dependencies]
+futures = "0.1"
+failure = "0.1.1"
+mail-core = { path="../core" }
+mail-headers = { path="../headers"}
+mail-internals = { path="../internals" }
+new-tokio-smtp = "0.8.1"
+
+[features]
+test-with-traceing = ["mail-internals/traceing"]
+extended-api = [] \ No newline at end of file
diff --git a/smtp/README.md b/smtp/README.md
new file mode 100644
index 0000000..b02fade
--- /dev/null
+++ b/smtp/README.md
@@ -0,0 +1,88 @@
+# mail-smtp &emsp;
+
+**Allows sending `mail-core` `Mail`'s through `new-tokio-smtp`**
+
+---
+
+
+This library binds together `new-tokio-smtp` and the `mail` crates.
+
+It can be used to send mail given as mail crates `Mail` instances
+to a Mail Submission Agent (MSA). It could, theoretically also
+be used to send to an MX, but this often needs additional functionality
+for reliable usage which is not part of this crate.
+
+For ease of use this crate re-exports some of the most commonly used
+parts from `new-tokio-smtp` including `ConnectionConfig`,
+`ConnectionBuilder`, all authentication commands/methods (the
+`auth` module) as well as useful types (in the `misc` module).
+
+The `send_mails` function is the simplest way to send a batch
+of mails. Nevertheless it doesn't directly accept `Mail` instances,
+instead it accepts `MailRequest` instances. This is needed, as
+the sender/recipient(s) specified through the `Mail` headers
+and those used fro smtp mail delivery are not necessary exactly
+the same (e.g. for bounce back mails and some no-reply setups).
+
+# Example
+
+```rust ,no_run
+extern crate futures;
+//if you use the mail facade use the re-exports from it instead
+extern crate mail_core;
+extern crate mail_smtp;
+#[macro_use] extern crate mail_headers;
+
+use futures::Future;
+use mail_headers::*;
+use mail_headers::components::Domain;
+use mail_core::{Mail, default_impl::simple_context};
+use mail_smtp::{send_mails, ConnectionConfig};
+
+fn main() {
+ // this is normally done _once per application instance_
+ // and then stored in e.g. a lazy_static. Also `Domain`
+ // will implement `FromStr` in the future.
+ let ctx = simple_context::new(
+ Domain::from_unchecked("example.com".to_owned(),
+ // This should be "world" unique for the given domain
+ // to assure message and content ids are world unique.
+ "asdkds".parse().unwrap()
+ ).unwrap();
+
+ let mut mail = Mail::plain_text("Some body").unwrap();
+ mail.set_headers(headers! {
+ _From: ["bla@example.com"],
+ _To: ["blub@example.com"],
+ Subject: "Some Mail"
+ }.unwrap()).unwrap();
+
+ // don't use unencrypted con for anything but testing and
+ // simplified examples
+ let con_config = ConnectionConfig::build_local_unencrypted().build();
+
+ let fut = send_mails(con_config, vec![mail.into()], ctx);
+ let results = fut.wait();
+}
+```
+
+
+## Documentation
+
+Documentation can be [viewed on docs.rs](https://docs.rs/mail-smtp).
+(once published)
+
+## License
+
+Licensed under either of
+
+* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
+additional terms or conditions.
diff --git a/smtp/src/error.rs b/smtp/src/error.rs
new file mode 100644
index 0000000..17f76e5
--- /dev/null
+++ b/smtp/src/error.rs
@@ -0,0 +1,116 @@
+//! Module containing all custom errors.
+use std::{io as std_io};
+
+use new_tokio_smtp::error::{
+ ConnectingFailed,
+ LogicError, GeneralError
+};
+
+use mail::error::MailError;
+use headers::error::HeaderValidationError;
+
+/// Error used when sending a mail fails.
+///
+/// Failing to encode a mail before sending
+/// it also counts as a `MailSendError`, as
+/// it's done "on the fly" when sending a mail.
+#[derive(Debug, Fail)]
+pub enum MailSendError {
+
+ /// Something is wrong with the mail instance (e.g. it can't be encoded).
+ ///
+ /// This can happen for a number of reasons including:
+ ///
+ /// 1. Missing header fields.
+ /// 2. Invalid header fields.
+ /// 2. Encoding header fields fails.
+ /// 3. Loading resources failed (resources like e.g. appendix, logo embedded in html mail, etc.)
+ #[fail(display = "{}", _0)]
+ Mail(MailError),
+
+ /// Sending the mail failed.
+ ///
+ /// This can happen for a number of reasons including:
+ /// 1. Server rejects mail transaction because of send or receiver
+ /// address or body data (e.g. body to long).
+ /// 2. Mail address requires smtputf8 support, which is not given.
+ /// 3. Server rejects sending the mail for other reasons (it's
+ /// closing, overloaded etc.).
+ #[fail(display = "{}", _0)]
+ Smtp(LogicError),
+
+ /// Setting up the connection failed.
+ ///
+ /// Failures can include but are not limited to:
+ ///
+ /// - Connecting with TCP failed.
+ /// - Starting TLS failed.
+ /// - Server does not want to be used (e.g. failure on sending EHLO).
+ /// - Authentication failed.
+ #[fail(display = "{}", _0)]
+ Connecting(ConnectingFailed),
+
+ /// An I/O error happened while using the connection.
+ ///
+ /// This is mainly for I/O errors after the setup of the connection
+ /// was successful, which normally includes sending Ehlo and Auth
+ /// commands.
+ #[fail(display = "{}", _0)]
+ Io(std_io::Error)
+}
+
+impl From<MailError> for MailSendError {
+ fn from(err: MailError) -> Self {
+ MailSendError::Mail(err)
+ }
+}
+
+impl From<LogicError> for MailSendError {
+ fn from(err: LogicError) -> Self {
+ MailSendError::Smtp(err)
+ }
+}
+
+impl From<std_io::Error> for MailSendError {
+ fn from(err: std_io::Error) -> Self {
+ MailSendError::Io(err)
+ }
+}
+
+impl From<ConnectingFailed> for MailSendError {
+ fn from(err: ConnectingFailed) -> Self {
+ MailSendError::Connecting(err)
+ }
+}
+
+impl From<GeneralError> for MailSendError {
+ fn from(err: GeneralError) -> Self {
+ use self::GeneralError::*;
+ match err {
+ Connecting(err) => Self::from(err),
+ Cmd(err) => Self::from(err),
+ Io(err) => Self::from(err)
+ }
+ }
+}
+
+
+#[derive(Debug, Fail)]
+pub enum OtherValidationError {
+
+ #[fail(display = "no To header was present")]
+ NoTo
+}
+
+impl From<OtherValidationError> for HeaderValidationError {
+
+ fn from(ove: OtherValidationError) -> Self {
+ HeaderValidationError::Custom(ove.into())
+ }
+}
+
+impl From<OtherValidationError> for MailError {
+ fn from(ove: OtherValidationError) -> Self {
+ MailError::from(HeaderValidationError::from(ove))
+ }
+} \ No newline at end of file
diff --git a/smtp/src/lib.rs b/smtp/src/lib.rs
new file mode 100644
index 0000000..271fa46
--- /dev/null
+++ b/smtp/src/lib.rs
@@ -0,0 +1,109 @@
+//! This library binds together `new-tokio-smtp` and the `mail` crates.
+//!
+//! It can be used to send mail given as mail crates `Mail` instances
+//! to a Mail Submission Agent (MSA). It could, theoretically also
+//! be used to send to an MX, but this often needs additional functionality
+//! for reliable usage which is not part of this crate.
+//!
+//! For ease of use this crate re-exports some of the most commonly used
+//! parts from `new-tokio-smtp` including `ConnectionConfig`,
+//! `ConnectionBuilder`, all authentication commands/methods (the
+//! `auth` module) as well as useful types (in the `misc` module).
+//!
+//! The `send_mails` function is the simplest way to send a batch
+//! of mails. Nevertheless it doesn't directly accept `Mail` instances,
+//! instead it accepts `MailRequest` instances. This is needed, as
+//! the sender/recipient(s) specified through the `Mail` headers
+//! and those used fro smtp mail delivery are not necessary exactly
+//! the same (e.g. for bounce back mails and some no-reply setups).
+//!
+//! # Example
+//!
+//! ```no_run
+//! extern crate futures;
+//! //if you use the mail facade use the re-exports from it instead
+//! extern crate mail_core;
+//! extern crate mail_smtp;
+//! #[macro_use] extern crate mail_headers;
+//!
+//! use futures::Future;
+//! use mail_headers::{
+//! headers::*,
+//! header_components::Domain
+//! };
+//! use mail_core::{Mail, default_impl::simple_context};
+//! use mail_smtp::{self as smtp, ConnectionConfig};
+//!
+//! # fn main() {
+//! // this is normally done _once per application instance_
+//! // and then stored in e.g. a lazy_static. Also `Domain`
+//! // will implement `FromStr` in the future.
+//! let ctx = simple_context::new(Domain::from_unchecked("example.com".to_owned()), "asdkds".parse().unwrap())
+//! .unwrap();
+//!
+//! let mut mail = Mail::plain_text("Some body");
+//! mail.insert_headers(headers! {
+//! _From: ["bla@example.com"],
+//! _To: ["blub@example.com"],
+//! Subject: "Some Mail"
+//! }.unwrap());
+//!
+//! // don't use unencrypted con for anything but testing and
+//! // simplified examples
+//! let con_config = ConnectionConfig::build_local_unencrypted().build();
+//!
+//! let fut = smtp::send(mail.into(), con_config, ctx);
+//! let results = fut.wait();
+//! # }
+//! ```
+//!
+//!
+extern crate futures;
+extern crate new_tokio_smtp;
+extern crate mail_core as mail;
+extern crate mail_internals;
+#[cfg_attr(test, macro_use)]
+extern crate mail_headers as headers;
+#[macro_use]
+extern crate failure;
+
+mod resolve_all;
+
+pub mod error;
+mod request;
+mod send_mail;
+
+pub use self::request::MailRequest;
+#[cfg(feature="extended-api")]
+pub use self::request::derive_envelop_data_from_mail;
+
+pub use self::send_mail::{send, send_batch};
+#[cfg(feature="extended-api")]
+pub use self::send_mail::encode;
+
+pub use new_tokio_smtp::{ConnectionConfig, ConnectionBuilder};
+
+pub mod auth {
+ //! Module containing authentification commands/methods.
+ //!
+ //! This Module is re-exported from `new-tokio-smtp` for
+ //! ease of use.
+
+ pub use new_tokio_smtp::command::auth::*;
+
+ /// Auth command for not doing anything on auth.
+ //FIXME: this currently still sends the noop cmd,
+ // replace it with some new "NoCommand" command.
+ pub type NoAuth = ::new_tokio_smtp::command::Noop;
+}
+
+pub mod misc {
+ //! A small collection of usefull types re-exported from `new-tokio-smtp`.
+ pub use new_tokio_smtp::{
+ ClientId,
+ Domain,
+ AddressLiteral,
+ SetupTls,
+ DefaultTlsSetup
+ };
+} \ No newline at end of file
diff --git a/smtp/src/request.rs b/smtp/src/request.rs
new file mode 100644
index 0000000..5f68437
--- /dev/null
+++ b/smtp/src/request.rs
@@ -0,0 +1,299 @@
+use std::mem;
+
+use new_tokio_smtp::send_mail::{
+ self as smtp,
+ MailAddress,
+ EnvelopData
+};
+
+use mail_internals::{
+ MailType,
+ encoder::{EncodingBuffer, EncodableInHeader},
+ error::EncodingError
+};
+use headers::{
+ headers::{Sender, _From, _To},
+ header_components::Mailbox,
+ error::{BuildInValidationError}
+};
+use mail::{
+ Mail,
+ error::{MailError, OtherValidationError}
+};
+
+use ::error::{ OtherValidationError as AnotherOtherValidationError };
+
+/// This type contains a mail and potentially some envelop data.
+///
+/// It is needed as in some edge cases the smtp envelop data (i.e.
+/// smtp from and smtp recipient) can not be correctly derived
+/// from the mail.
+///
+/// The default usage is to directly turn a `Mail` into a `MailRequest`
+/// by either using `MailRequest::new`, `MailRequest::from` or `Mail::into`.
+///
+#[derive(Clone, Debug)]
+pub struct MailRequest {
+ mail: Mail,
+ envelop_data: Option<EnvelopData>
+}
+
+impl From<Mail> for MailRequest {
+ fn from(mail: Mail) -> Self {
+ MailRequest::new(mail)
+ }
+}
+
+
+
+impl MailRequest {
+
+ /// creates a new `MailRequest` from a `Mail` instance
+ pub fn new(mail: Mail) -> Self {
+ MailRequest { mail, envelop_data: None }
+ }
+
+ /// create a new `MailRequest` and use custom smtp `EnvelopData`
+ ///
+ /// Note that envelop data comes from `new-tokio-smtp::send_mail` and
+ /// is not re-exported so if you happen to run into one of the view
+ /// cases where you need to set it manually just import it from
+ /// `new-tokio-smtp`.
+ pub fn new_with_envelop(mail: Mail, envelop: EnvelopData) -> Self {
+ MailRequest { mail, envelop_data: Some(envelop) }
+ }
+
+ /// replace the smtp `EnvelopData`
+ pub fn override_envelop(&mut self, envelop: EnvelopData) -> Option<EnvelopData> {
+ mem::replace(&mut self.envelop_data, Some(envelop))
+ }
+
+ pub fn _into_mail_with_envelop(self) -> Result<(Mail, EnvelopData), MailError> {
+ let envelop =
+ if let Some(envelop) = self.envelop_data { envelop }
+ else { derive_envelop_data_from_mail(&self.mail)? };
+
+ Ok((self.mail, envelop))
+ }
+
+ #[cfg(not(feature="extended-api"))]
+ #[inline(always)]
+ pub(crate) fn into_mail_with_envelop(self) -> Result<(Mail, EnvelopData), MailError> {
+ self._into_mail_with_envelop()
+ }
+
+ /// Turns this type into the contained mail an associated envelop data.
+ ///
+ /// If envelop data was explicitly set it is returned.
+ /// If no envelop data was explicitly given it is derived from the
+ /// Mail header fields using `derive_envelop_data_from_mail`.
+ #[cfg(feature="extended-api")]
+ #[inline(always)]
+ pub fn into_mail_with_envelop(self) -> Result<(Mail, EnvelopData), MailError> {
+ self._into_mail_with_envelop()
+ }
+}
+
+fn mailaddress_from_mailbox(mailbox: &Mailbox) -> Result<MailAddress, EncodingError> {
+ let email = &mailbox.email;
+ let needs_smtputf8 = email.check_if_internationalized();
+ let mt = if needs_smtputf8 { MailType::Internationalized } else { MailType::Ascii };
+ let mut buffer = EncodingBuffer::new(mt);
+ {
+ let mut writer = buffer.writer();
+ email.encode(&mut writer)?;
+ writer.commit_partial_header();
+ }
+ let raw: Vec<u8> = buffer.into();
+ let address = String::from_utf8(raw).expect("[BUG] encoding Email produced non utf8 data");
+ Ok(MailAddress::new_unchecked(address, needs_smtputf8))
+}
+
+/// Generates envelop data based on the given Mail.
+///
+/// If a sender header is given smtp will use this
+/// as smtp from else the single mailbox in from
+/// is used as smtp from.
+///
+/// All `To`'s are used as smtp recipients.
+///
+/// **`Cc`/`Bcc` is currently no supported/has no
+/// special handling**
+///
+/// # Error
+///
+/// An error is returned if there is:
+///
+/// - No From header
+/// - No To header
+/// - A From header with multiple addresses but no Sender header
+///
+pub fn derive_envelop_data_from_mail(mail: &Mail)
+ -> Result<smtp::EnvelopData, MailError>
+{
+ let headers = mail.headers();
+ let smtp_from =
+ if let Some(sender) = headers.get_single(Sender) {
+ let sender = sender?;
+ //TODO double check with from field
+ mailaddress_from_mailbox(sender)?
+ } else {
+ let from = headers.get_single(_From)
+ .ok_or(OtherValidationError::NoFrom)??;
+
+ if from.len() > 1 {
+ return Err(BuildInValidationError::MultiMailboxFromWithoutSender.into());
+ }
+
+ mailaddress_from_mailbox(from.first())?
+ };
+
+ let smtp_to =
+ if let Some(to) = headers.get_single(_To) {
+ let to = to?;
+ to.try_mapped_ref(mailaddress_from_mailbox)?
+ } else {
+ return Err(AnotherOtherValidationError::NoTo.into());
+ };
+
+ //TODO Cc, Bcc
+
+ Ok(EnvelopData {
+ from: Some(smtp_from),
+ to: smtp_to
+ })
+}
+
+#[cfg(test)]
+mod test {
+
+ mod derive_envelop_data_from_mail {
+ use super::super::derive_envelop_data_from_mail;
+ use mail::{
+ Mail,
+ Resource,
+ file_buffer::FileBuffer
+ };
+ use headers::{
+ headers::{_From, _To, Sender},
+ header_components::MediaType
+ };
+
+ fn mock_resource() -> Resource {
+ let mt = MediaType::parse("text/plain; charset=utf-8").unwrap();
+ let fb = FileBuffer::new(mt, "abcd↓efg".to_owned().into());
+ Resource::sourceless_from_buffer(fb)
+ }
+
+ #[test]
+ fn use_sender_if_given() {
+ let mut mail = Mail::new_singlepart_mail(mock_resource());
+
+ mail.insert_headers(headers! {
+ Sender: "strange@caffe.test",
+ _From: ["ape@caffe.test", "epa@caffe.test"],
+ _To: ["das@ding.test"]
+ }.unwrap());
+
+ let envelop_data = derive_envelop_data_from_mail(&mail).unwrap();
+
+ assert_eq!(
+ envelop_data.from.as_ref().unwrap().as_str(),
+ "strange@caffe.test"
+ );
+ }
+
+ #[test]
+ fn use_from_if_no_sender_given() {
+ let mut mail = Mail::new_singlepart_mail(mock_resource());
+ mail.insert_headers(headers! {
+ _From: ["ape@caffe.test"],
+ _To: ["das@ding.test"]
+ }.unwrap());
+
+ let envelop_data = derive_envelop_data_from_mail(&mail).unwrap();
+
+ assert_eq!(
+ envelop_data.from.as_ref().unwrap().as_str(),
+ "ape@caffe.test"
+ );
+ }
+
+ #[test]
+ fn fail_if_no_sender_but_multi_mailbox_from() {
+ let mut mail = Mail::new_singlepart_mail(mock_resource());
+ mail.insert_headers(headers! {
+ _From: ["ape@caffe.test", "a@b.test"],
+ _To: ["das@ding.test"]
+ }.unwrap());
+
+ let envelop_data = derive_envelop_data_from_mail(&mail);
+
+ //assert is_err
+ envelop_data.unwrap_err();
+ }
+
+ #[test]
+ fn use_to() {
+ let mut mail = Mail::new_singlepart_mail(mock_resource());
+ mail.insert_headers(headers! {
+ _From: ["ape@caffe.test"],
+ _To: ["das@ding.test"]
+ }.unwrap());
+
+ let envelop_data = derive_envelop_data_from_mail(&mail).unwrap();
+
+ assert_eq!(
+ envelop_data.to.first().as_str(),
+ "das@ding.test"
+ );
+ }
+ }
+
+ mod mailaddress_from_mailbox {
+ use headers::{
+ HeaderTryFrom,
+ header_components::{Mailbox, Email}
+ };
+ use super::super::mailaddress_from_mailbox;
+
+ #[test]
+ #[cfg_attr(not(feature="test-with-traceing"), ignore)]
+ fn does_not_panic_with_tracing_enabled() {
+ let mb = Mailbox::try_from("hy@b").unwrap();
+ mailaddress_from_mailbox(&mb).unwrap();
+ }
+
+ #[test]
+ fn correctly_converts_mailbox() {
+ let mb = Mailbox::from(Email::new("tast@tost.test").unwrap());
+ let address = mailaddress_from_mailbox(&mb).unwrap();
+ assert_eq!(address.as_str(), "tast@tost.test");
+ assert_eq!(address.needs_smtputf8(), false);
+ }
+
+ #[test]
+ fn tracks_if_smtputf8_is_needed() {
+ let mb = Mailbox::from(Email::new("tüst@tost.test").unwrap());
+ let address = mailaddress_from_mailbox(&mb).unwrap();
+ assert_eq!(address.as_str(), "tüst@tost.test");
+ assert_eq!(address.needs_smtputf8(), true);
+ }
+
+ #[test]
+ fn puny_encodes_domain_if_smtputf8_is_not_needed() {
+ let mb = Mailbox::from(Email::new("tast@tüst.test").unwrap());
+ let address = mailaddress_from_mailbox(&mb).unwrap();
+ assert_eq!(address.as_str(), "tast@xn--tst-hoa.test");
+ assert_eq!(address.needs_smtputf8(), false);
+ }
+
+ #[test]
+ fn does_not_puny_encodes_domain_if_smtputf8_is_needed() {
+ let mb = Mailbox::from(Email::new("töst@tüst.test").unwrap());
+ let address = mailaddress_from_mailbox(&mb).unwrap();
+ assert_eq!(address.as_str(), "töst@tüst.test");
+ assert_eq!(address.needs_smtputf8(), true);
+ }
+ }
+} \ No newline at end of file
diff --git a/smtp/src/resolve_all.rs b/smtp/src/resolve_all.rs
new file mode 100644
index 0000000..69d3c8b
--- /dev/null
+++ b/smtp/src/resolve_all.rs
@@ -0,0 +1,81 @@
+use std::mem;
+use std::iter::FromIterator;
+
+use futures::{Future, Async, Poll};
+
+pub enum AltFuse<F: Future> {
+ Future(F),
+ Resolved(Result<F::Item, F::Error>)
+}
+
+impl<F> Future for AltFuse<F>
+ where F: Future
+{
+ type Item = ();
+ //TODO[futures/v>=0.2 |rust/! type]: use Never or !
+ type Error = ();
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ let result = match *self {
+ AltFuse::Resolved(_) => return Ok(Async::Ready(())),
+ AltFuse::Future(ref mut fut) => match fut.poll() {
+ Ok(Async::NotReady) => return Ok(Async::NotReady),
+ Ok(Async::Ready(val)) => Ok(val),
+ Err(err) => Err(err)
+ }
+ };
+
+ *self = AltFuse::Resolved(result);
+ Ok(Async::Ready(()))
+ }
+}
+
+
+pub struct ResolveAll<F>
+ where F: Future
+{
+ all: Vec<AltFuse<F>>
+}
+
+impl<F> Future for ResolveAll<F>
+ where F: Future
+{
+ type Item = Vec<Result<F::Item, F::Error>>;
+ //TODO[futures >= 0.2/rust ! type]: use Never or !
+ type Error = ();
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ let mut any_not_ready = false;
+ for fut in self.all.iter_mut() {
+ if let Ok(Async::NotReady) = fut.poll() {
+ any_not_ready = true;
+ }
+ }
+ if any_not_ready {
+ Ok(Async::NotReady)
+ } else {
+ let results = mem::replace(&mut self.all, Vec::new())
+ .into_iter().map(|alt_fuse_fut| match alt_fuse_fut {
+ AltFuse::Resolved(res) => res,
+ AltFuse::Future(_) => unreachable!()
+ })
+ .collect();
+ Ok(Async::Ready(results))
+ }
+ }
+}
+
+impl<I> FromIterator<I> for ResolveAll<I>
+ where I: Future
+{
+ fn from_iter<T>(all: T) -> Self
+ where T: IntoIterator<Item = I>
+ {
+ let all = all
+ .into_iter()
+ .map(|fut| AltFuse::Future(fut))
+ .collect();
+
+ ResolveAll { all }
+ }
+} \ No newline at end of file
diff --git a/smtp/src/send_mail.rs b/smtp/src/send_mail.rs
new file mode 100644
index 0000000..3839421
--- /dev/null
+++ b/smtp/src/send_mail.rs
@@ -0,0 +1,139 @@
+//! Module implementing mail sending using `new-tokio-smtp::send_mail`.
+
+use std::iter::{once as one};
+
+use futures::{
+ stream::{self, Stream},
+ future::{self, Future, Either}
+};
+
+use mail_internals::{
+ MailType,
+ encoder::EncodingBuffer
+};
+use mail::Context;
+
+use new_tokio_smtp::{
+ ConnectionConfig,
+ Cmd,
+ SetupTls,
+ send_mail::MailEnvelop,
+ Connection,
+ send_mail as smtp
+};
+
+use ::{
+ error::MailSendError,
+ request::MailRequest
+};
+
+/// Sends a given mail (request).
+///
+/// - This will use the given context to encode the mail.
+/// - Then it will use the connection config to open a connection to a mail
+/// server (likely a Mail Submission Agent (MSA)).
+/// - Following this it will send the mail to the server.
+/// - After which it will close the connection again.
+///
+/// You can use `MailRequest: From<Mail>` (i.e. `mail.into()`) to pass in
+/// a mail and derive the envelop data (from, to) from it or create your own
+/// mail request if different smtp envelop data is needed.
+pub fn send<A, S>(mail: MailRequest, conconf: ConnectionConfig<A, S>, ctx: impl Context)
+ -> impl Future<Item=(), Error=MailSendError>
+ where A: Cmd, S: SetupTls
+{
+ let fut = encode(mail, ctx)
+ .then(move |envelop_res| Connection
+ ::connect_send_quit(conconf, one(envelop_res))
+ .collect())
+ .map(|mut results| results.pop().expect("[BUG] sending one mail expects one result"));
+
+ fut
+}
+
+/// Sends a batch of mails to a server.
+///
+/// - This will use the given context to encode all mails.
+/// - After which it will use the connection config to open a connection
+/// to the server (like a Mail Submission Agent (MSA)).
+/// - Then it will start sending mails.
+/// - If a mail fails because of an error code but setting up the connection
+/// (which includes auth) didn't fail then others mails in the input will
+/// still be send
+/// - If the connection is broken because setting it up failed or it was
+/// interrupted, then the mail at which place it was noticed will return
+/// the given error and all later mails will return a I/0-Error with the
+/// `ErrorKind::NoConnection`
+/// - It will return a `Stream` which when polled will send the mails
+/// and return results _in the order the mails had been supplied_. So
+/// for each mail there will be exactly one result.
+/// - Once the stream is completed the connection will automatically be
+/// closed (even if the stream is not yet dropped, it closes it the
+/// moment it notices that there are no more mails to send!)
+///
+pub fn send_batch<A, S, C>(
+ mails: Vec<MailRequest>,
+ conconf: ConnectionConfig<A, S>,
+ ctx: C
+) -> impl Stream<Item=(), Error=MailSendError>
+ where A: Cmd, S: SetupTls, C: Context
+{
+ let iter = mails.into_iter().map(move |mail| encode(mail, ctx.clone()));
+
+ let fut = collect_res(stream::futures_ordered(iter))
+ .map(move |vec_of_res| Connection::connect_send_quit(conconf, vec_of_res))
+ .flatten_stream();
+
+ fut
+}
+
+//FIXME[futures/v>=0.2] use Error=Never
+fn collect_res<S, E>(stream: S) -> impl Future<Item=Vec<Result<S::Item, S::Error>>, Error=E>
+ where S: Stream
+{
+ stream.then(|res| Ok(res)).collect()
+}
+
+/// Turns a `MailRequest` into a future resolving to a `MailEnvelop`.
+///
+/// This function is mainly used internally for `send`, `send_batch`
+/// but can be used by other libraries when `send`/`send_batch` doesn't
+/// quite match their use case. E.g. if they want to have a connection
+/// pool and instead of `connect->send->quit` want to have something like
+/// `take_from_pool->test->send->place_back_to_pool`, in which case they
+/// probably would want to do something along the lines of using encode
+/// then take a connection, test it, use the mail envelops with `new-tokio-smtp`'s
+/// `SendAllMails` stream with a `on_completion` handler which places it
+/// back in the pool.
+pub fn encode<C>(request: MailRequest, ctx: C)
+ -> impl Future<Item=MailEnvelop, Error=MailSendError>
+ where C: Context
+{
+ let (mail, envelop_data) =
+ match request.into_mail_with_envelop() {
+ Ok(pair) => pair,
+ Err(e) => return Either::A(future::err(e.into()))
+ };
+
+ let fut = mail
+ .into_encodeable_mail(ctx.clone())
+ .and_then(move |enc_mail| ctx.offload_fn(move || {
+ let (mail_type, requirement) =
+ if envelop_data.needs_smtputf8() {
+ (MailType::Internationalized, smtp::EncodingRequirement::Smtputf8)
+ } else {
+ (MailType::Ascii, smtp::EncodingRequirement::None)
+ };
+
+ let mut buffer = EncodingBuffer::new(mail_type);
+ enc_mail.encode(&mut buffer)?;
+
+ let vec_buffer: Vec<_> = buffer.into();
+ let smtp_mail = smtp::Mail::new(requirement, vec_buffer);
+
+ Ok(smtp::MailEnvelop::from((smtp_mail, envelop_data)))
+ }))
+ .map_err(MailSendError::from);
+
+ Either::B(fut)
+} \ No newline at end of file