pub const VERSION: &str = env!("CARGO_PKG_VERSION");
mod address;
mod attachment;
mod email;
mod error;
pub mod interceptor;
mod mailer;
pub mod providers;
#[cfg(feature = "local")]
mod storage;
#[cfg(feature = "local")]
pub mod testing;
#[cfg(any(
feature = "preview",
feature = "preview-axum",
feature = "preview-actix"
))]
pub mod preview;
#[cfg(feature = "templates")]
mod template;
#[cfg(feature = "templates")]
pub use template::{EmailTemplate, EmailTemplateExt};
use parking_lot::RwLock;
use std::env;
use std::sync::Arc;
#[cfg(feature = "metrics")]
use std::time::Instant;
pub use address::{Address, ToAddress};
pub use attachment::{Attachment, AttachmentType};
pub use email::Email;
pub use error::MailError;
pub use interceptor::{Interceptor, InterceptorExt, WithInterceptor};
pub use mailer::{DeliveryResult, Mailer, MailerExt};
#[cfg(feature = "local")]
pub use storage::{MemoryStorage, Storage, StoredEmail};
static MAILER: RwLock<Option<Arc<dyn Mailer>>> = RwLock::new(None);
#[cfg(feature = "local")]
static LOCAL_STORAGE: std::sync::OnceLock<Arc<MemoryStorage>> = std::sync::OnceLock::new();
#[cfg(feature = "local")]
pub fn local_storage() -> Option<Arc<MemoryStorage>> {
LOCAL_STORAGE.get().cloned()
}
pub fn default_from() -> Option<Address> {
let email = env::var("EMAIL_FROM").ok()?;
match env::var("EMAIL_FROM_NAME").ok() {
Some(name) => Some(Address::with_name(name, email)),
None => Some(Address::new(email)),
}
}
fn detect_provider() -> Option<&'static str> {
#[cfg(feature = "resend")]
if env::var("RESEND_API_KEY").is_ok() {
return Some("resend");
}
#[cfg(feature = "sendgrid")]
if env::var("SENDGRID_API_KEY").is_ok() {
return Some("sendgrid");
}
#[cfg(feature = "postmark")]
if env::var("POSTMARK_API_KEY").is_ok() {
return Some("postmark");
}
#[cfg(feature = "unsent")]
if env::var("UNSENT_API_KEY").is_ok() {
return Some("unsent");
}
#[cfg(feature = "brevo")]
if env::var("BREVO_API_KEY").is_ok() {
return Some("brevo");
}
#[cfg(feature = "mailgun")]
if env::var("MAILGUN_API_KEY").is_ok() && env::var("MAILGUN_DOMAIN").is_ok() {
return Some("mailgun");
}
#[cfg(feature = "amazon_ses")]
if env::var("AWS_ACCESS_KEY_ID").is_ok()
&& env::var("AWS_SECRET_ACCESS_KEY").is_ok()
&& env::var("AWS_REGION").is_ok()
{
return Some("amazon_ses");
}
#[cfg(feature = "mailtrap")]
if env::var("MAILTRAP_API_KEY").is_ok() {
return Some("mailtrap");
}
#[cfg(feature = "socketlabs")]
if env::var("SOCKETLABS_API_KEY").is_ok() && env::var("SOCKETLABS_SERVER_ID").is_ok() {
return Some("socketlabs");
}
#[cfg(feature = "gmail")]
if env::var("GMAIL_ACCESS_TOKEN").is_ok() {
return Some("gmail");
}
#[cfg(feature = "protonbridge")]
if env::var("PROTONBRIDGE_USERNAME").is_ok() && env::var("PROTONBRIDGE_PASSWORD").is_ok() {
return Some("protonbridge");
}
#[cfg(feature = "jmap")]
if env::var("JMAP_URL").is_ok()
&& (env::var("JMAP_BEARER_TOKEN").is_ok()
|| (env::var("JMAP_USERNAME").is_ok() && env::var("JMAP_PASSWORD").is_ok()))
{
return Some("jmap");
}
#[cfg(feature = "smtp")]
if env::var("SMTP_HOST").is_ok() {
return Some("smtp");
}
#[cfg(feature = "local")]
{
return Some("local");
}
#[allow(unreachable_code)]
None
}
fn create_mailer_from_env() -> Result<Arc<dyn Mailer>, MailError> {
let provider = match env::var("EMAIL_PROVIDER") {
Ok(p) => p.to_lowercase(),
Err(_) => {
match detect_provider() {
Some(p) => {
tracing::debug!(provider = p, "Auto-detected email provider");
p.to_string()
}
None => {
return Err(MailError::Configuration(
"EMAIL_PROVIDER not set and could not auto-detect. \
Set EMAIL_PROVIDER or ensure an API key is configured."
.into(),
));
}
}
}
};
match provider.as_str() {
#[cfg(feature = "smtp")]
"smtp" => {
let host = env::var("SMTP_HOST")
.map_err(|_| MailError::Configuration("SMTP_HOST not set".into()))?;
let port: u16 = env::var("SMTP_PORT")
.unwrap_or_else(|_| "587".to_string())
.parse()
.unwrap_or(587);
let username = env::var("SMTP_USERNAME").unwrap_or_default();
let password = env::var("SMTP_PASSWORD").unwrap_or_default();
let mailer = if username.is_empty() {
providers::SmtpMailer::new(&host, port).build()
} else {
providers::SmtpMailer::new(&host, port)
.credentials(&username, &password)
.build()
};
Ok(Arc::new(mailer))
}
#[cfg(not(feature = "smtp"))]
"smtp" => Err(MailError::Configuration(
"EMAIL_PROVIDER=smtp but 'smtp' feature is not enabled. \
Add `features = [\"smtp\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "resend")]
"resend" => {
let key = env::var("RESEND_API_KEY")
.map_err(|_| MailError::Configuration("RESEND_API_KEY not set".into()))?;
Ok(Arc::new(providers::ResendMailer::new(&key)))
}
#[cfg(not(feature = "resend"))]
"resend" => Err(MailError::Configuration(
"EMAIL_PROVIDER=resend but 'resend' feature is not enabled. \
Add `features = [\"resend\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "unsent")]
"unsent" => {
let key = env::var("UNSENT_API_KEY")
.map_err(|_| MailError::Configuration("UNSENT_API_KEY not set".into()))?;
Ok(Arc::new(providers::UnsentMailer::new(&key)))
}
#[cfg(not(feature = "unsent"))]
"unsent" => Err(MailError::Configuration(
"EMAIL_PROVIDER=unsent but 'unsent' feature is not enabled. \
Add `features = [\"unsent\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "postmark")]
"postmark" => {
let key = env::var("POSTMARK_API_KEY")
.map_err(|_| MailError::Configuration("POSTMARK_API_KEY not set".into()))?;
Ok(Arc::new(providers::PostmarkMailer::new(&key)))
}
#[cfg(not(feature = "postmark"))]
"postmark" => Err(MailError::Configuration(
"EMAIL_PROVIDER=postmark but 'postmark' feature is not enabled. \
Add `features = [\"postmark\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "sendgrid")]
"sendgrid" => {
let key = env::var("SENDGRID_API_KEY")
.map_err(|_| MailError::Configuration("SENDGRID_API_KEY not set".into()))?;
Ok(Arc::new(providers::SendGridMailer::new(&key)))
}
#[cfg(not(feature = "sendgrid"))]
"sendgrid" => Err(MailError::Configuration(
"EMAIL_PROVIDER=sendgrid but 'sendgrid' feature is not enabled. \
Add `features = [\"sendgrid\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "brevo")]
"brevo" => {
let key = env::var("BREVO_API_KEY")
.map_err(|_| MailError::Configuration("BREVO_API_KEY not set".into()))?;
Ok(Arc::new(providers::BrevoMailer::new(&key)))
}
#[cfg(not(feature = "brevo"))]
"brevo" => Err(MailError::Configuration(
"EMAIL_PROVIDER=brevo but 'brevo' feature is not enabled. \
Add `features = [\"brevo\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "mailgun")]
"mailgun" => {
let key = env::var("MAILGUN_API_KEY")
.map_err(|_| MailError::Configuration("MAILGUN_API_KEY not set".into()))?;
let domain = env::var("MAILGUN_DOMAIN")
.map_err(|_| MailError::Configuration("MAILGUN_DOMAIN not set".into()))?;
let mut mailer = providers::MailgunMailer::new(&key, &domain);
if let Ok(base_url) = env::var("MAILGUN_BASE_URL") {
mailer = mailer.base_url(base_url);
}
Ok(Arc::new(mailer))
}
#[cfg(not(feature = "mailgun"))]
"mailgun" => Err(MailError::Configuration(
"EMAIL_PROVIDER=mailgun but 'mailgun' feature is not enabled. \
Add `features = [\"mailgun\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "amazon_ses")]
"amazon_ses" => {
let region = env::var("AWS_REGION")
.map_err(|_| MailError::Configuration("AWS_REGION not set".into()))?;
let access_key = env::var("AWS_ACCESS_KEY_ID")
.map_err(|_| MailError::Configuration("AWS_ACCESS_KEY_ID not set".into()))?;
let secret = env::var("AWS_SECRET_ACCESS_KEY")
.map_err(|_| MailError::Configuration("AWS_SECRET_ACCESS_KEY not set".into()))?;
Ok(Arc::new(providers::AmazonSesMailer::new(region, access_key, secret)))
}
#[cfg(not(feature = "amazon_ses"))]
"amazon_ses" => Err(MailError::Configuration(
"EMAIL_PROVIDER=amazon_ses but 'amazon_ses' feature is not enabled. \
Add `features = [\"amazon_ses\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "mailtrap")]
"mailtrap" => {
let key = env::var("MAILTRAP_API_KEY")
.map_err(|_| MailError::Configuration("MAILTRAP_API_KEY not set".into()))?;
let mut mailer = providers::MailtrapMailer::new(&key);
if let Ok(inbox_id) = env::var("MAILTRAP_SANDBOX_INBOX_ID") {
mailer = mailer.sandbox_inbox_id(inbox_id);
}
Ok(Arc::new(mailer))
}
#[cfg(not(feature = "mailtrap"))]
"mailtrap" => Err(MailError::Configuration(
"EMAIL_PROVIDER=mailtrap but 'mailtrap' feature is not enabled. \
Add `features = [\"mailtrap\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "socketlabs")]
"socketlabs" => {
let server_id = env::var("SOCKETLABS_SERVER_ID")
.map_err(|_| MailError::Configuration("SOCKETLABS_SERVER_ID not set".into()))?;
let api_key = env::var("SOCKETLABS_API_KEY")
.map_err(|_| MailError::Configuration("SOCKETLABS_API_KEY not set".into()))?;
Ok(Arc::new(providers::SocketLabsMailer::new(server_id, api_key)))
}
#[cfg(not(feature = "socketlabs"))]
"socketlabs" => Err(MailError::Configuration(
"EMAIL_PROVIDER=socketlabs but 'socketlabs' feature is not enabled. \
Add `features = [\"socketlabs\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "gmail")]
"gmail" => {
let token = env::var("GMAIL_ACCESS_TOKEN")
.map_err(|_| MailError::Configuration("GMAIL_ACCESS_TOKEN not set".into()))?;
Ok(Arc::new(providers::GmailMailer::new(token)))
}
#[cfg(not(feature = "gmail"))]
"gmail" => Err(MailError::Configuration(
"EMAIL_PROVIDER=gmail but 'gmail' feature is not enabled. \
Add `features = [\"gmail\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "protonbridge")]
"protonbridge" => {
let username = env::var("PROTONBRIDGE_USERNAME")
.map_err(|_| MailError::Configuration("PROTONBRIDGE_USERNAME not set".into()))?;
let password = env::var("PROTONBRIDGE_PASSWORD")
.map_err(|_| MailError::Configuration("PROTONBRIDGE_PASSWORD not set".into()))?;
let mut builder = providers::ProtonBridgeMailer::new(&username, &password);
if let Ok(host) = env::var("PROTONBRIDGE_HOST") {
builder = builder.host(&host);
}
if let Ok(port) = env::var("PROTONBRIDGE_PORT") {
if let Ok(port) = port.parse() {
builder = builder.port(port);
}
}
Ok(Arc::new(builder.build()))
}
#[cfg(not(feature = "protonbridge"))]
"protonbridge" => Err(MailError::Configuration(
"EMAIL_PROVIDER=protonbridge but 'protonbridge' feature is not enabled. \
Add `features = [\"protonbridge\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "jmap")]
"jmap" => {
let url = env::var("JMAP_URL")
.map_err(|_| MailError::Configuration("JMAP_URL not set".into()))?;
let builder = providers::JmapMailer::new(&url);
let mailer = if let Ok(token) = env::var("JMAP_BEARER_TOKEN") {
builder.bearer_token(&token).build()
} else {
let username = env::var("JMAP_USERNAME")
.map_err(|_| MailError::Configuration("JMAP_USERNAME not set".into()))?;
let password = env::var("JMAP_PASSWORD")
.map_err(|_| MailError::Configuration("JMAP_PASSWORD not set".into()))?;
builder.credentials(&username, &password).build()
};
Ok(Arc::new(mailer))
}
#[cfg(not(feature = "jmap"))]
"jmap" => Err(MailError::Configuration(
"EMAIL_PROVIDER=jmap but 'jmap' feature is not enabled. \
Add `features = [\"jmap\"]` to Cargo.toml"
.into(),
)),
#[cfg(feature = "local")]
"local" => {
let storage = LOCAL_STORAGE.get_or_init(MemoryStorage::shared);
Ok(Arc::new(providers::LocalMailer::with_storage(Arc::clone(storage))))
}
#[cfg(not(feature = "local"))]
"local" => Err(MailError::Configuration(
"EMAIL_PROVIDER=local but 'local' feature is not enabled. \
Add `features = [\"local\"]` to Cargo.toml"
.into(),
)),
"logger" => Ok(Arc::new(providers::LoggerMailer::new())),
"logger_full" => Ok(Arc::new(providers::LoggerMailer::full())),
_ => Err(MailError::Configuration(format!(
"Unknown EMAIL_PROVIDER: {}. Valid providers are: smtp, resend, unsent, postmark, sendgrid, brevo, mailgun, amazon_ses, mailtrap, socketlabs, gmail, protonbridge, jmap, local, logger, logger_full",
provider
))),
}
}
fn get_mailer() -> Result<Arc<dyn Mailer>, MailError> {
{
let guard = MAILER.read();
if let Some(ref mailer) = *guard {
return Ok(Arc::clone(mailer));
}
}
let mailer = create_mailer_from_env()?;
let mut guard = MAILER.write();
if guard.is_none() {
*guard = Some(Arc::clone(&mailer));
}
Ok(guard.as_ref().unwrap().clone())
}
pub fn is_configured() -> bool {
let provider = match env::var("EMAIL_PROVIDER") {
Ok(p) => p,
Err(_) => {
match detect_provider() {
Some(p) => p.to_string(),
None => return false,
}
}
};
match provider.to_lowercase().as_str() {
#[cfg(feature = "smtp")]
"smtp" => env::var("SMTP_HOST").is_ok(),
#[cfg(not(feature = "smtp"))]
"smtp" => {
tracing::warn!(
"EMAIL_PROVIDER=smtp but 'smtp' feature is not enabled. \
Add `features = [\"smtp\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "resend")]
"resend" => env::var("RESEND_API_KEY").is_ok(),
#[cfg(not(feature = "resend"))]
"resend" => {
tracing::warn!(
"EMAIL_PROVIDER=resend but 'resend' feature is not enabled. \
Add `features = [\"resend\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "unsent")]
"unsent" => env::var("UNSENT_API_KEY").is_ok(),
#[cfg(not(feature = "unsent"))]
"unsent" => {
tracing::warn!(
"EMAIL_PROVIDER=unsent but 'unsent' feature is not enabled. \
Add `features = [\"unsent\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "postmark")]
"postmark" => env::var("POSTMARK_API_KEY").is_ok(),
#[cfg(not(feature = "postmark"))]
"postmark" => {
tracing::warn!(
"EMAIL_PROVIDER=postmark but 'postmark' feature is not enabled. \
Add `features = [\"postmark\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "sendgrid")]
"sendgrid" => env::var("SENDGRID_API_KEY").is_ok(),
#[cfg(not(feature = "sendgrid"))]
"sendgrid" => {
tracing::warn!(
"EMAIL_PROVIDER=sendgrid but 'sendgrid' feature is not enabled. \
Add `features = [\"sendgrid\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "brevo")]
"brevo" => env::var("BREVO_API_KEY").is_ok(),
#[cfg(not(feature = "brevo"))]
"brevo" => {
tracing::warn!(
"EMAIL_PROVIDER=brevo but 'brevo' feature is not enabled. \
Add `features = [\"brevo\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "mailgun")]
"mailgun" => env::var("MAILGUN_API_KEY").is_ok() && env::var("MAILGUN_DOMAIN").is_ok(),
#[cfg(not(feature = "mailgun"))]
"mailgun" => {
tracing::warn!(
"EMAIL_PROVIDER=mailgun but 'mailgun' feature is not enabled. \
Add `features = [\"mailgun\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "amazon_ses")]
"amazon_ses" => {
env::var("AWS_REGION").is_ok()
&& env::var("AWS_ACCESS_KEY_ID").is_ok()
&& env::var("AWS_SECRET_ACCESS_KEY").is_ok()
}
#[cfg(not(feature = "amazon_ses"))]
"amazon_ses" => {
tracing::warn!(
"EMAIL_PROVIDER=amazon_ses but 'amazon_ses' feature is not enabled. \
Add `features = [\"amazon_ses\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "mailtrap")]
"mailtrap" => env::var("MAILTRAP_API_KEY").is_ok(),
#[cfg(not(feature = "mailtrap"))]
"mailtrap" => {
tracing::warn!(
"EMAIL_PROVIDER=mailtrap but 'mailtrap' feature is not enabled. \
Add `features = [\"mailtrap\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "socketlabs")]
"socketlabs" => {
env::var("SOCKETLABS_SERVER_ID").is_ok() && env::var("SOCKETLABS_API_KEY").is_ok()
}
#[cfg(not(feature = "socketlabs"))]
"socketlabs" => {
tracing::warn!(
"EMAIL_PROVIDER=socketlabs but 'socketlabs' feature is not enabled. \
Add `features = [\"socketlabs\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "gmail")]
"gmail" => env::var("GMAIL_ACCESS_TOKEN").is_ok(),
#[cfg(not(feature = "gmail"))]
"gmail" => {
tracing::warn!(
"EMAIL_PROVIDER=gmail but 'gmail' feature is not enabled. \
Add `features = [\"gmail\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "protonbridge")]
"protonbridge" => {
env::var("PROTONBRIDGE_USERNAME").is_ok() && env::var("PROTONBRIDGE_PASSWORD").is_ok()
}
#[cfg(not(feature = "protonbridge"))]
"protonbridge" => {
tracing::warn!(
"EMAIL_PROVIDER=protonbridge but 'protonbridge' feature is not enabled. \
Add `features = [\"protonbridge\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "jmap")]
"jmap" => {
env::var("JMAP_URL").is_ok()
&& (env::var("JMAP_BEARER_TOKEN").is_ok()
|| (env::var("JMAP_USERNAME").is_ok() && env::var("JMAP_PASSWORD").is_ok()))
}
#[cfg(not(feature = "jmap"))]
"jmap" => {
tracing::warn!(
"EMAIL_PROVIDER=jmap but 'jmap' feature is not enabled. \
Add `features = [\"jmap\"]` to Cargo.toml"
);
false
}
#[cfg(feature = "local")]
"local" => true,
#[cfg(not(feature = "local"))]
"local" => {
tracing::warn!(
"EMAIL_PROVIDER=local but 'local' feature is not enabled. \
Add `features = [\"local\"]` to Cargo.toml"
);
false
}
"logger" | "logger_full" => true,
_ => false,
}
}
pub fn init() -> Result<(), MailError> {
if !is_configured() {
return Err(MailError::NotConfigured);
}
let _ = get_mailer()?;
Ok(())
}
fn validate(email: &Email) -> Result<(), MailError> {
if email.from.is_none() && default_from().is_none() {
return Err(MailError::MissingField("from"));
}
if email.to.is_empty() {
return Err(MailError::MissingField("to"));
}
Ok(())
}
fn prepare_email(email: &Email) -> Email {
if email.from.is_none() {
if let Some(from) = default_from() {
let mut e = email.clone();
e.from = Some(from);
return e;
}
}
email.clone()
}
pub async fn deliver(email: &Email) -> Result<DeliveryResult, MailError> {
validate(email)?;
let mailer = get_mailer()?;
let provider = mailer.provider_name();
let email = prepare_email(email);
let span = tracing::info_span!(
"missive.deliver",
provider = provider,
to = ?email.to.iter().map(|a| &a.email).collect::<Vec<_>>(),
subject = %email.subject,
);
let _guard = span.enter();
tracing::debug!("Delivering email");
#[cfg(feature = "metrics")]
let start = Instant::now();
let result = mailer.deliver(&email).await;
#[cfg(feature = "metrics")]
{
let duration = start.elapsed().as_secs_f64();
let status = if result.is_ok() { "success" } else { "error" };
metrics::counter!("missive_emails_total", "provider" => provider, "status" => status)
.increment(1);
metrics::histogram!("missive_delivery_duration_seconds", "provider" => provider)
.record(duration);
}
match &result {
Ok(r) => tracing::info!(message_id = %r.message_id, "Email delivered"),
Err(e) => tracing::error!(error = %e, "Email delivery failed"),
}
result
}
pub async fn deliver_with<M: Mailer>(
email: &Email,
mailer: &M,
) -> Result<DeliveryResult, MailError> {
validate(email)?;
let provider = mailer.provider_name();
let email = prepare_email(email);
let span = tracing::info_span!(
"missive.deliver",
provider = provider,
to = ?email.to.iter().map(|a| &a.email).collect::<Vec<_>>(),
subject = %email.subject,
);
let _guard = span.enter();
tracing::debug!("Delivering email");
#[cfg(feature = "metrics")]
let start = Instant::now();
let result = mailer.deliver(&email).await;
#[cfg(feature = "metrics")]
{
let duration = start.elapsed().as_secs_f64();
let status = if result.is_ok() { "success" } else { "error" };
metrics::counter!("missive_emails_total", "provider" => provider, "status" => status)
.increment(1);
metrics::histogram!("missive_delivery_duration_seconds", "provider" => provider)
.record(duration);
}
match &result {
Ok(r) => tracing::info!(message_id = %r.message_id, "Email delivered"),
Err(e) => tracing::error!(error = %e, "Email delivery failed"),
}
result
}
pub async fn deliver_many(emails: &[Email]) -> Result<Vec<DeliveryResult>, MailError> {
for email in emails {
validate(email)?;
}
let mailer = get_mailer()?;
let provider = mailer.provider_name();
let count = emails.len();
let emails: Vec<Email> = emails.iter().map(prepare_email).collect();
let span = tracing::info_span!("missive.deliver_many", provider = provider, count = count,);
let _guard = span.enter();
#[cfg(feature = "metrics")]
let start = Instant::now();
let result = mailer.deliver_many(&emails).await;
#[cfg(feature = "metrics")]
{
let duration = start.elapsed().as_secs_f64();
let status = if result.is_ok() { "success" } else { "error" };
metrics::counter!("missive_emails_total", "provider" => provider, "status" => status)
.increment(count as u64);
metrics::counter!("missive_batch_total", "provider" => provider, "status" => status)
.increment(1);
metrics::histogram!("missive_delivery_duration_seconds", "provider" => provider, "batch" => "true").record(duration);
metrics::histogram!("missive_batch_size", "provider" => provider).record(count as f64);
}
result
}
pub fn configure<M: Mailer + 'static>(mailer: M) {
let mut guard = MAILER.write();
*guard = Some(Arc::new(mailer));
}
pub fn configure_arc(mailer: Arc<dyn Mailer>) {
let mut guard = MAILER.write();
*guard = Some(mailer);
}
pub fn reset() {
let mut guard = MAILER.write();
*guard = None;
}
pub fn mailer() -> Option<Arc<dyn Mailer>> {
let guard = MAILER.read();
guard.as_ref().cloned()
}
pub mod prelude {
pub use crate::Address;
pub use crate::Attachment;
pub use crate::DeliveryResult;
pub use crate::Email;
pub use crate::MailError;
pub use crate::Mailer;
pub use crate::ToAddress;
pub use crate::{default_from, deliver, deliver_many, deliver_with, is_configured};
#[cfg(feature = "local")]
pub use crate::Storage;
}