feat(server): Option to configure SMTPS transport (#22833)

* feat(server): Option to configure SMTPS transport

This commit adds a boolean option in the SMTP transport configuration to
enable the so-called "secure" mode. What it does is use SMTP over TLS
instead of relying on the more common STARTTLS option over plain SMTP.

* Add missing field in dto

* Add missing field

* Use a switch instead of text field

* Add field in spec

* chore: regen open-api

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
Clement Martin
2025-10-17 12:21:27 +02:00
committed by GitHub
parent 81554e5ad1
commit 95889a69c9
11 changed files with 33 additions and 1 deletions

View File

@@ -211,6 +211,8 @@
"notification_email_ignore_certificate_errors_description": "Ignore TLS certificate validation errors (not recommended)", "notification_email_ignore_certificate_errors_description": "Ignore TLS certificate validation errors (not recommended)",
"notification_email_password_description": "Password to use when authenticating with the email server", "notification_email_password_description": "Password to use when authenticating with the email server",
"notification_email_port_description": "Port of the email server (e.g 25, 465, or 587)", "notification_email_port_description": "Port of the email server (e.g 25, 465, or 587)",
"notification_email_secure": "SMTPS",
"notification_email_secure_description": "Use SMTPS (SMTP over TLS)",
"notification_email_sent_test_email_button": "Send test email and save", "notification_email_sent_test_email_button": "Send test email and save",
"notification_email_setting_description": "Settings for sending email notifications", "notification_email_setting_description": "Settings for sending email notifications",
"notification_email_test_email": "Send test email", "notification_email_test_email": "Send test email",

View File

@@ -17,6 +17,7 @@ class SystemConfigSmtpTransportDto {
required this.ignoreCert, required this.ignoreCert,
required this.password, required this.password,
required this.port, required this.port,
required this.secure,
required this.username, required this.username,
}); });
@@ -30,6 +31,8 @@ class SystemConfigSmtpTransportDto {
/// Maximum value: 65535 /// Maximum value: 65535
num port; num port;
bool secure;
String username; String username;
@override @override
@@ -38,6 +41,7 @@ class SystemConfigSmtpTransportDto {
other.ignoreCert == ignoreCert && other.ignoreCert == ignoreCert &&
other.password == password && other.password == password &&
other.port == port && other.port == port &&
other.secure == secure &&
other.username == username; other.username == username;
@override @override
@@ -47,10 +51,11 @@ class SystemConfigSmtpTransportDto {
(ignoreCert.hashCode) + (ignoreCert.hashCode) +
(password.hashCode) + (password.hashCode) +
(port.hashCode) + (port.hashCode) +
(secure.hashCode) +
(username.hashCode); (username.hashCode);
@override @override
String toString() => 'SystemConfigSmtpTransportDto[host=$host, ignoreCert=$ignoreCert, password=$password, port=$port, username=$username]'; String toString() => 'SystemConfigSmtpTransportDto[host=$host, ignoreCert=$ignoreCert, password=$password, port=$port, secure=$secure, username=$username]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -58,6 +63,7 @@ class SystemConfigSmtpTransportDto {
json[r'ignoreCert'] = this.ignoreCert; json[r'ignoreCert'] = this.ignoreCert;
json[r'password'] = this.password; json[r'password'] = this.password;
json[r'port'] = this.port; json[r'port'] = this.port;
json[r'secure'] = this.secure;
json[r'username'] = this.username; json[r'username'] = this.username;
return json; return json;
} }
@@ -75,6 +81,7 @@ class SystemConfigSmtpTransportDto {
ignoreCert: mapValueOfType<bool>(json, r'ignoreCert')!, ignoreCert: mapValueOfType<bool>(json, r'ignoreCert')!,
password: mapValueOfType<String>(json, r'password')!, password: mapValueOfType<String>(json, r'password')!,
port: num.parse('${json[r'port']}'), port: num.parse('${json[r'port']}'),
secure: mapValueOfType<bool>(json, r'secure')!,
username: mapValueOfType<String>(json, r'username')!, username: mapValueOfType<String>(json, r'username')!,
); );
} }
@@ -127,6 +134,7 @@ class SystemConfigSmtpTransportDto {
'ignoreCert', 'ignoreCert',
'password', 'password',
'port', 'port',
'secure',
'username', 'username',
}; };
} }

View File

@@ -16726,6 +16726,9 @@
"minimum": 0, "minimum": 0,
"type": "number" "type": "number"
}, },
"secure": {
"type": "boolean"
},
"username": { "username": {
"type": "string" "type": "string"
} }
@@ -16735,6 +16738,7 @@
"ignoreCert", "ignoreCert",
"password", "password",
"port", "port",
"secure",
"username" "username"
], ],
"type": "object" "type": "object"

View File

@@ -71,6 +71,7 @@ export type SystemConfigSmtpTransportDto = {
ignoreCert: boolean; ignoreCert: boolean;
password: string; password: string;
port: number; port: number;
secure: boolean;
username: string; username: string;
}; };
export type SystemConfigSmtpDto = { export type SystemConfigSmtpDto = {

View File

@@ -159,6 +159,7 @@ export interface SystemConfig {
ignoreCert: boolean; ignoreCert: boolean;
host: string; host: string;
port: number; port: number;
secure: boolean;
username: string; username: string;
password: string; password: string;
}; };
@@ -356,6 +357,7 @@ export const defaults = Object.freeze<SystemConfig>({
ignoreCert: false, ignoreCert: false,
host: '', host: '',
port: 587, port: 587,
secure: false,
username: '', username: '',
password: '', password: '',
}, },

View File

@@ -463,6 +463,9 @@ class SystemConfigSmtpTransportDto {
@Max(65_535) @Max(65_535)
port!: number; port!: number;
@ValidateBoolean()
secure!: boolean;
@IsString() @IsString()
username!: string; username!: string;

View File

@@ -23,6 +23,7 @@ export type SendEmailOptions = {
export type SmtpOptions = { export type SmtpOptions = {
host: string; host: string;
port?: number; port?: number;
secure?: boolean;
username?: string; username?: string;
password?: string; password?: string;
ignoreCert?: boolean; ignoreCert?: boolean;

View File

@@ -14,6 +14,7 @@ const smtpTransport = Object.freeze<SystemConfig>({
ignoreCert: false, ignoreCert: false,
host: 'localhost', host: 'localhost',
port: 587, port: 587,
secure: false,
username: 'test', username: 'test',
password: 'test', password: 'test',
}, },

View File

@@ -40,6 +40,7 @@ const configs = {
ignoreCert: false, ignoreCert: false,
host: 'localhost', host: 'localhost',
port: 587, port: 587,
secure: false,
username: 'test', username: 'test',
password: 'test', password: 'test',
}, },

View File

@@ -197,6 +197,7 @@ const updatedConfig = Object.freeze<SystemConfig>({
transport: { transport: {
host: '', host: '',
port: 587, port: 587,
secure: false,
username: '', username: '',
password: '', password: '',
ignoreCert: false, ignoreCert: false,

View File

@@ -45,6 +45,7 @@
transport: { transport: {
host: config.notifications.smtp.transport.host, host: config.notifications.smtp.transport.host,
port: config.notifications.smtp.transport.port, port: config.notifications.smtp.transport.port,
secure: config.notifications.smtp.transport.secure,
username: config.notifications.smtp.transport.username, username: config.notifications.smtp.transport.username,
password: config.notifications.smtp.transport.password, password: config.notifications.smtp.transport.password,
ignoreCert: config.notifications.smtp.transport.ignoreCert, ignoreCert: config.notifications.smtp.transport.ignoreCert,
@@ -128,6 +129,13 @@
savedConfig.notifications.smtp.transport.password} savedConfig.notifications.smtp.transport.password}
/> />
<SettingSwitch
title={$t('admin.notification_email_secure')}
subtitle={$t('admin.notification_email_secure_description')}
disabled={disabled || !config.notifications.smtp.enabled}
bind:checked={config.notifications.smtp.transport.secure}
/>
<SettingSwitch <SettingSwitch
title={$t('admin.notification_email_ignore_certificate_errors')} title={$t('admin.notification_email_ignore_certificate_errors')}
subtitle={$t('admin.notification_email_ignore_certificate_errors_description')} subtitle={$t('admin.notification_email_ignore_certificate_errors_description')}