use chrono::prelude::*;
use lettre::{
file::error::FileResult, sendmail::error::SendmailResult, smtp::error::SmtpResult,
EmailAddress, Envelope, FileTransport, SendableEmail, SmtpTransport, Transport,
};
pub use lettre::smtp::{client::net::ClientTlsParameters, ClientSecurity, SmtpClient};
use std::net::ToSocketAddrs;
mod echo_transport;
#[derive(Debug)]
pub enum Error {
Lettre(lettre::error::Error),
LettreFile(lettre::file::error::Error),
LettreSendMail(lettre::sendmail::error::Error),
LettreSmtp(lettre::smtp::error::Error),
Io(std::io::Error),
MissingEmail,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::Lettre(i) => i.fmt(f),
Error::LettreFile(i) => i.fmt(f),
Error::LettreSendMail(i) => i.fmt(f),
Error::LettreSmtp(i) => i.fmt(f),
Error::Io(i) => i.fmt(f),
Error::MissingEmail => write!(f, "Error, email address to build a Sender"),
}
}
}
impl std::error::Error for Error {}
impl From<lettre::error::Error> for Error {
fn from(other: lettre::error::Error) -> Self {
Self::Lettre(other)
}
}
impl From<lettre::file::error::Error> for Error {
fn from(other: lettre::file::error::Error) -> Self {
Self::LettreFile(other)
}
}
impl From<lettre::sendmail::error::Error> for Error {
fn from(other: lettre::sendmail::error::Error) -> Self {
Self::LettreSendMail(other)
}
}
impl From<lettre::smtp::error::Error> for Error {
fn from(other: lettre::smtp::error::Error) -> Self {
Self::LettreSmtp(other)
}
}
impl From<std::io::Error> for Error {
fn from(other: std::io::Error) -> Self {
Self::Io(other)
}
}
pub struct SenderBuilder {
pub(crate) address: Option<EmailAddress>,
}
impl SenderBuilder {
pub fn address(mut self, from: &str) -> Self {
if let Ok(add) = EmailAddress::new(from.to_string()) {
self.address = Some(add);
}
self
}
pub fn file<'a, P: AsRef<std::path::Path>>(
self,
p: P,
) -> Result<Sender<'a, FileResult>, Error> {
let client = Box::new(FileTransport::new(p));
if let Some(address) = self.address {
Ok(Sender { address, client })
} else {
Err(Error::MissingEmail)
}
}
pub fn sendmail<'a>(self) -> Result<Sender<'a, SendmailResult>, Error> {
let client = Box::new(lettre::SendmailTransport::new());
if let Some(address) = self.address {
Ok(Sender { address, client })
} else {
Err(Error::MissingEmail)
}
}
pub fn smtp_unencrypted_localhost<'a>(self) -> Result<Sender<'a, SmtpResult>, Error> {
let smtp = lettre::SmtpClient::new_unencrypted_localhost()?;
self.smtp(smtp)
}
pub fn smtp_simple<'a>(self, domain: &str) -> Result<Sender<'a, SmtpResult>, Error> {
let smtp = lettre::SmtpClient::new_simple(domain)?;
self.smtp(smtp)
}
pub fn smtp_full<'a, A: ToSocketAddrs>(
self,
addr: A,
security: ClientSecurity,
) -> Result<Sender<'a, SmtpResult>, Error> {
let smtp = SmtpClient::new(addr, security)?;
self.smtp(smtp)
}
pub fn smtp<'a>(self, smtp: SmtpClient) -> Result<Sender<'a, SmtpResult>, Error> {
let client = Box::new(SmtpTransport::new(smtp));
if let Some(address) = self.address {
Ok(Sender { address, client })
} else {
Err(Error::MissingEmail)
}
}
pub fn stdout<'a>(self) -> Result<Sender<'a, std::io::Result<()>>, Error> {
let client = Box::new(echo_transport::EchoTransport);
if let Some(address) = self.address {
Ok(Sender { address, client })
} else {
Err(Error::MissingEmail)
}
}
}
pub struct Sender<'a, R> {
pub address: EmailAddress,
pub client: Box<dyn Transport<'a, Result = R>>,
}
impl<'a> Sender<'a, ()> {
pub fn builder() -> SenderBuilder {
SenderBuilder { address: None }
}
}
impl<'a, R, E> Sender<'a, Result<R, E>>
where
E: Into<Error>,
{
pub fn send_to(&mut self, dest: &Destination, msg: &str) -> Result<R, Error> {
let to = EmailAddress::new(dest.address())?;
let from = self.address.clone();
let env = Envelope::new(Some(from), vec![to])?;
let email = SendableEmail::new(env, Utc::now().to_rfc2822(), msg.as_bytes().to_vec());
match self.client.send(email) {
Ok(r) => Ok(r),
Err(e) => Err(e.into()),
}
}
}
#[derive(Debug, Clone)]
pub enum Carrier {
ATT,
Sprint,
TMobile,
Verizon,
BoostMobile,
Cricket,
MetroPCS,
Tracfone,
USCellular,
VirginMobile,
Other { domain: String },
}
impl std::str::FromStr for Carrier {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"att" => Self::ATT,
"sprint" => Self::Sprint,
"tmobile" => Self::TMobile,
"verizon" => Self::Verizon,
"boost" => Self::BoostMobile,
"cricket" => Self::Cricket,
"metropcs" => Self::MetroPCS,
"tracfone" => Self::Tracfone,
"uscellular" => Self::USCellular,
"virgin" => Self::VirginMobile,
_ => Self::Other {
domain: s.to_string(),
},
})
}
}
#[derive(Debug)]
pub struct Destination {
pub number: String,
pub carrier: Carrier,
}
impl Destination {
pub fn new(number: &str, carrier: &Carrier) -> Self {
let number = number.chars().filter(|c| c.is_digit(10)).collect();
Self {
number,
carrier: carrier.clone(),
}
}
pub fn address(&self) -> String {
format!("{}@{}", self.number, self.carrier.get_domain())
}
}
impl Carrier {
pub fn get_domain(&self) -> &str {
match self {
Carrier::ATT => "txt.att.net",
Carrier::Sprint => "messaging.sprintpcs.com",
Carrier::TMobile => "tmomail.net",
Carrier::Verizon => "vtext.com",
Carrier::BoostMobile => "myboostmobile.com",
Carrier::Cricket => "sms.mycricket.com",
Carrier::MetroPCS => "mymetropcs.com",
Carrier::Tracfone => "mmst5.tracfone.com",
Carrier::USCellular => "email.uscc.net",
Carrier::VirginMobile => "vmobl.com",
Carrier::Other { domain } => domain,
}
}
}