mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-12 23:56:36 +08:00
feat(server): add fallback smtp config (#13377)
fix AF-2749 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for configuring a fallback SMTP server for outgoing emails. * Introduced the ability to specify email domains that will always use the fallback SMTP server. * Enhanced email sending to automatically route messages to the appropriate SMTP server based on recipient domain. * **Documentation** * Updated configuration options and descriptions in the admin interface to reflect new fallback SMTP settings. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -219,6 +219,41 @@
|
||||
"type": "boolean",
|
||||
"description": "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.\n@default false\n@environment `MAILER_IGNORE_TLS`",
|
||||
"default": false
|
||||
},
|
||||
"fallbackDomains": {
|
||||
"type": "array",
|
||||
"description": "The emails from these domains are always sent using the fallback SMTP server.\n@default []",
|
||||
"default": []
|
||||
},
|
||||
"fallbackSMTP.host": {
|
||||
"type": "string",
|
||||
"description": "Host of the email server (e.g. smtp.gmail.com)\n@default \"\"",
|
||||
"default": ""
|
||||
},
|
||||
"fallbackSMTP.port": {
|
||||
"type": "number",
|
||||
"description": "Port of the email server (they commonly are 25, 465 or 587)\n@default 465",
|
||||
"default": 465
|
||||
},
|
||||
"fallbackSMTP.username": {
|
||||
"type": "string",
|
||||
"description": "Username used to authenticate the email server\n@default \"\"",
|
||||
"default": ""
|
||||
},
|
||||
"fallbackSMTP.password": {
|
||||
"type": "string",
|
||||
"description": "Password used to authenticate the email server\n@default \"\"",
|
||||
"default": ""
|
||||
},
|
||||
"fallbackSMTP.sender": {
|
||||
"type": "string",
|
||||
"description": "Sender of all the emails (e.g. \"AFFiNE Team <noreply@affine.pro>\")\n@default \"\"",
|
||||
"default": ""
|
||||
},
|
||||
"fallbackSMTP.ignoreTLS": {
|
||||
"type": "boolean",
|
||||
"description": "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.\n@default false",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import z from 'zod';
|
||||
|
||||
import { defineModuleConfig } from '../../base';
|
||||
|
||||
declare global {
|
||||
@@ -11,6 +13,16 @@ declare global {
|
||||
ignoreTLS: boolean;
|
||||
sender: string;
|
||||
};
|
||||
|
||||
fallbackDomains: ConfigItem<string[]>;
|
||||
fallbackSMTP: {
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
ignoreTLS: boolean;
|
||||
sender: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -46,4 +58,34 @@ defineModuleConfig('mailer', {
|
||||
default: false,
|
||||
env: ['MAILER_IGNORE_TLS', 'boolean'],
|
||||
},
|
||||
|
||||
fallbackDomains: {
|
||||
desc: 'The emails from these domains are always sent using the fallback SMTP server.',
|
||||
default: [],
|
||||
shape: z.array(z.string()),
|
||||
},
|
||||
'fallbackSMTP.host': {
|
||||
desc: 'Host of the email server (e.g. smtp.gmail.com)',
|
||||
default: '',
|
||||
},
|
||||
'fallbackSMTP.port': {
|
||||
desc: 'Port of the email server (they commonly are 25, 465 or 587)',
|
||||
default: 465,
|
||||
},
|
||||
'fallbackSMTP.username': {
|
||||
desc: 'Username used to authenticate the email server',
|
||||
default: '',
|
||||
},
|
||||
'fallbackSMTP.password': {
|
||||
desc: 'Password used to authenticate the email server',
|
||||
default: '',
|
||||
},
|
||||
'fallbackSMTP.sender': {
|
||||
desc: 'Sender of all the emails (e.g. "AFFiNE Team <noreply@affine.pro>")',
|
||||
default: '',
|
||||
},
|
||||
'fallbackSMTP.ignoreTLS': {
|
||||
desc: "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.",
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -36,6 +36,8 @@ function configToSMTPOptions(
|
||||
export class MailSender {
|
||||
private readonly logger = new Logger(MailSender.name);
|
||||
private smtp: Transporter<SMTPTransport.SentMessageInfo> | null = null;
|
||||
private fallbackSMTP: Transporter<SMTPTransport.SentMessageInfo> | null =
|
||||
null;
|
||||
private usingTestAccount = false;
|
||||
constructor(private readonly config: Config) {}
|
||||
|
||||
@@ -61,11 +63,17 @@ export class MailSender {
|
||||
}
|
||||
|
||||
private setup() {
|
||||
const { SMTP } = this.config.mailer;
|
||||
const { SMTP, fallbackDomains, fallbackSMTP } = this.config.mailer;
|
||||
const opts = configToSMTPOptions(SMTP);
|
||||
|
||||
if (SMTP.host) {
|
||||
this.smtp = createTransport(opts);
|
||||
if (fallbackDomains.length > 0 && fallbackSMTP?.host) {
|
||||
this.logger.warn(
|
||||
`Fallback SMTP is configured for domains: ${fallbackDomains.join(', ')}`
|
||||
);
|
||||
this.fallbackSMTP = createTransport(configToSMTPOptions(fallbackSMTP));
|
||||
}
|
||||
} else if (env.dev) {
|
||||
createTestAccount((err, account) => {
|
||||
if (!err) {
|
||||
@@ -83,21 +91,34 @@ export class MailSender {
|
||||
} else {
|
||||
this.logger.warn('Mailer SMTP transport is not configured.');
|
||||
this.smtp = null;
|
||||
this.fallbackSMTP = null;
|
||||
}
|
||||
}
|
||||
|
||||
private getSender(domain: string) {
|
||||
const { SMTP, fallbackSMTP, fallbackDomains } = this.config.mailer;
|
||||
if (this.fallbackSMTP && fallbackDomains.includes(domain)) {
|
||||
return [this.fallbackSMTP, fallbackSMTP.sender] as const;
|
||||
}
|
||||
return [this.smtp, SMTP.sender] as const;
|
||||
}
|
||||
|
||||
async send(name: string, options: SendOptions) {
|
||||
if (!this.smtp) {
|
||||
const [, domain, ...rest] = options.to.split('@');
|
||||
if (rest.length || !domain) {
|
||||
this.logger.error(`Invalid email address: ${options.to}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const [smtpClient, from] = this.getSender(domain);
|
||||
if (!smtpClient) {
|
||||
this.logger.warn(`Mailer SMTP transport is not configured to send mail.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
metrics.mail.counter('send_total').add(1, { name });
|
||||
try {
|
||||
const result = await this.smtp.sendMail({
|
||||
from: this.config.mailer.SMTP.sender,
|
||||
...options,
|
||||
});
|
||||
const result = await smtpClient.sendMail({ from, ...options });
|
||||
|
||||
if (result.rejected.length > 0) {
|
||||
metrics.mail.counter('rejected_total').add(1, { name });
|
||||
|
||||
@@ -114,6 +114,34 @@
|
||||
"type": "Boolean",
|
||||
"desc": "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates.",
|
||||
"env": "MAILER_IGNORE_TLS"
|
||||
},
|
||||
"fallbackDomains": {
|
||||
"type": "Array",
|
||||
"desc": "The emails from these domains are always sent using the fallback SMTP server."
|
||||
},
|
||||
"fallbackSMTP.host": {
|
||||
"type": "String",
|
||||
"desc": "Host of the email server (e.g. smtp.gmail.com)"
|
||||
},
|
||||
"fallbackSMTP.port": {
|
||||
"type": "Number",
|
||||
"desc": "Port of the email server (they commonly are 25, 465 or 587)"
|
||||
},
|
||||
"fallbackSMTP.username": {
|
||||
"type": "String",
|
||||
"desc": "Username used to authenticate the email server"
|
||||
},
|
||||
"fallbackSMTP.password": {
|
||||
"type": "String",
|
||||
"desc": "Password used to authenticate the email server"
|
||||
},
|
||||
"fallbackSMTP.sender": {
|
||||
"type": "String",
|
||||
"desc": "Sender of all the emails (e.g. \"AFFiNE Team <noreply@affine.pro>\")"
|
||||
},
|
||||
"fallbackSMTP.ignoreTLS": {
|
||||
"type": "Boolean",
|
||||
"desc": "Whether ignore email server's TSL certification verification. Enable it for self-signed certificates."
|
||||
}
|
||||
},
|
||||
"doc": {
|
||||
|
||||
Reference in New Issue
Block a user