use std::env;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TlsMode {
Implicit,
Starttls,
}
impl TlsMode {
fn from_str_loose(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"implicit" | "implicit_tls" | "smtps" | "tls" => Some(Self::Implicit),
"starttls" | "start_tls" | "submission" => Some(Self::Starttls),
_ => None,
}
}
pub fn default_for_port(port: u16) -> Self {
match port {
587 => Self::Starttls,
_ => Self::Implicit,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Implicit => "implicit",
Self::Starttls => "starttls",
}
}
}
#[derive(Debug, Clone)]
pub struct SmtpConfig {
pub host: String,
pub port: u16,
pub tls_mode: TlsMode,
pub username: String,
pub password: String,
pub from_address: String,
pub from_name: Option<String>,
}
impl SmtpConfig {
pub fn from_env() -> Option<Self> {
let host = env::var("SMTP_HOST").ok().filter(|s| !s.is_empty())?;
let username = env::var("SMTP_USER").ok().filter(|s| !s.is_empty())?;
let password = env::var("SMTP_PASSWORD").ok().filter(|s| !s.is_empty())?;
let from_address = env::var("SMTP_FROM_ADDRESS")
.ok()
.filter(|s| !s.is_empty())?;
let port = env::var("SMTP_PORT")
.ok()
.and_then(|s| s.parse::<u16>().ok())
.unwrap_or(465);
let tls_mode = env::var("SMTP_TLS_MODE")
.ok()
.and_then(|s| TlsMode::from_str_loose(&s))
.unwrap_or_else(|| TlsMode::default_for_port(port));
let from_name = env::var("SMTP_FROM_NAME").ok().filter(|s| !s.is_empty());
Some(SmtpConfig {
host,
port,
tls_mode,
username,
password,
from_address,
from_name,
})
}
pub fn log_startup(config: Option<&SmtpConfig>) {
match config {
Some(c) => {
tracing::info!(
host = %c.host,
port = c.port,
tls_mode = c.tls_mode.as_str(),
from = %c.from_address,
"SMTP configured; email channel ready"
);
}
None => {
tracing::warn!(
"SMTP not configured (set SMTP_HOST, SMTP_USER, SMTP_PASSWORD, \
SMTP_FROM_ADDRESS to enable the email channel). The email \
channel will fail at send time; in-app delivery continues \
to work."
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tls_mode_default_465_is_implicit() {
assert_eq!(TlsMode::default_for_port(465), TlsMode::Implicit);
}
#[test]
fn tls_mode_default_587_is_starttls() {
assert_eq!(TlsMode::default_for_port(587), TlsMode::Starttls);
}
#[test]
fn tls_mode_from_str_accepts_aliases() {
assert_eq!(TlsMode::from_str_loose("implicit"), Some(TlsMode::Implicit));
assert_eq!(TlsMode::from_str_loose("smtps"), Some(TlsMode::Implicit));
assert_eq!(TlsMode::from_str_loose("starttls"), Some(TlsMode::Starttls));
assert_eq!(TlsMode::from_str_loose("submission"), Some(TlsMode::Starttls));
assert_eq!(TlsMode::from_str_loose("garbage"), None);
}
}