Use connection url to configure email (fixes #5472) (#5476)

* Use connection url for configure email (fixes #5472)

* clippy
This commit is contained in:
Nutomic 2025-03-04 18:50:19 +00:00 committed by GitHub
parent a6507c169d
commit 1e02dea397
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 19 additions and 63 deletions

View file

@ -70,16 +70,10 @@
}
# Email sending configuration. All options except login/password are mandatory
email: {
# Hostname and port of the smtp server
smtp_server: "localhost:25"
# Login name for smtp server
smtp_login: "string"
# Password to login to the smtp server
smtp_password: "string"
# https://docs.rs/lettre/0.11.14/lettre/transport/smtp/struct.AsyncSmtpTransport.html#method.from_url
connection: "smtps://user:pass@hostname:port"
# Address to send emails from, eg "noreply@your-instance.com"
smtp_from_address: "noreply@example.com"
# Whether or not smtp connections should use tls. Can be none, tls, or starttls
tls_type: "none"
}
# Parameters for automatic configuration of new instance (only used at first start)
setup: {

View file

@ -79,6 +79,7 @@ lettre = { version = "0.11.12", default-features = false, features = [
"builder",
"smtp-transport",
"tokio1-rustls-tls",
"pool",
], optional = true }
markdown-it = { version = "0.6.1", optional = true }
ts-rs = { workspace = true, optional = true }

View file

@ -5,13 +5,13 @@ use crate::{
use html2text;
use lettre::{
message::{Mailbox, MultiPart},
transport::smtp::{authentication::Credentials, extension::ClientId},
transport::smtp::extension::ClientId,
Address,
AsyncTransport,
Message,
};
use rosetta_i18n::{Language, LanguageId};
use std::str::FromStr;
use std::{str::FromStr, sync::OnceLock};
use translations::Lang;
use uuid::Uuid;
@ -28,21 +28,16 @@ pub async fn send_email(
html: &str,
settings: &Settings,
) -> LemmyResult<()> {
static MAILER: OnceLock<AsyncSmtpTransport> = OnceLock::new();
let email_config = settings.email.clone().ok_or(LemmyErrorType::NoEmailSetup)?;
let domain = settings.hostname.clone();
let (smtp_server, smtp_port) = {
let email_and_port = email_config.smtp_server.split(':').collect::<Vec<&str>>();
let email = *email_and_port
.first()
.ok_or(LemmyErrorType::EmailRequired)?;
let port = email_and_port
.get(1)
.ok_or(LemmyErrorType::EmailSmtpServerNeedsAPort)?
.parse::<u16>()?;
(email, port)
};
#[expect(clippy::expect_used)]
let mailer = MAILER.get_or_init(|| {
AsyncSmtpTransport::from_url(&email_config.connection)
.expect("init email transport")
.hello_name(ClientId::Domain(settings.hostname.clone()))
.build()
});
// use usize::MAX as the line wrap length, since lettre handles the wrapping for us
let plain_text = html2text::from_read(html.as_bytes(), usize::MAX)?;
@ -70,24 +65,6 @@ pub async fn send_email(
))
.with_lemmy_type(LemmyErrorType::EmailSendFailed)?;
// don't worry about 'dangeous'. it's just that leaving it at the default configuration
// is bad.
// Set the TLS
let mut builder = match email_config.tls_type.as_str() {
"starttls" => AsyncSmtpTransport::starttls_relay(smtp_server)?.port(smtp_port),
"tls" => AsyncSmtpTransport::relay(smtp_server)?.port(smtp_port),
_ => AsyncSmtpTransport::builder_dangerous(smtp_server).port(smtp_port),
};
// Set the creds if they exist
let smtp_password = email_config.smtp_password();
if let (Some(username), Some(password)) = (email_config.smtp_login, smtp_password) {
builder = builder.credentials(Credentials::new(username, password));
}
let mailer = builder.hello_name(ClientId::Domain(domain)).build();
mailer
.send(email)
.await

View file

@ -74,7 +74,6 @@ pub enum LemmyErrorType {
ObjectNotLocal,
NoEmailSetup,
LocalSiteNotSetup,
EmailSmtpServerNeedsAPort,
InvalidEmailAddress(String),
RateLimitError,
InvalidName,

View file

@ -156,28 +156,13 @@ pub struct DatabaseConfig {
#[derive(Debug, Deserialize, Serialize, Clone, Document, SmartDefault)]
#[serde(default, deny_unknown_fields)]
pub struct EmailConfig {
/// Hostname and port of the smtp server
#[doku(example = "localhost:25")]
pub smtp_server: String,
/// Login name for smtp server
pub smtp_login: Option<String>,
/// Password to login to the smtp server
smtp_password: Option<String>,
#[doku(example = "noreply@example.com")]
/// https://docs.rs/lettre/0.11.14/lettre/transport/smtp/struct.AsyncSmtpTransport.html#method.from_url
#[default("smtp://localhost:25")]
#[doku(example = "smtps://user:pass@hostname:port")]
pub(crate) connection: String,
/// Address to send emails from, eg "noreply@your-instance.com"
pub smtp_from_address: String,
/// Whether or not smtp connections should use tls. Can be none, tls, or starttls
#[default("none")]
#[doku(example = "none")]
pub tls_type: String,
}
impl EmailConfig {
pub fn smtp_password(&self) -> Option<String> {
std::env::var("LEMMY_SMTP_PASSWORD")
.ok()
.or(self.smtp_password.clone())
}
#[doku(example = "noreply@example.com")]
pub(crate) smtp_from_address: String,
}
#[derive(Debug, Deserialize, Serialize, Clone, Default, Document)]