mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00: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:
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user