mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
fix(api): add custom headers to email fallback SMTP (#3507)
This commit is contained in:
1 parent
0b1ddc2695
commit
557efe1c7d
13 files changed
+132
-4
No files matched your search
@@ -25,4 +25,33 @@ describe('SmtpEmailProvider', () => {
|
||||
new SmtpEmailProvider({host: 'smtp.example.com', port, username: 'user', password: 'pass', tlsMode});
|
||||
expect(createTransport).toHaveBeenCalledWith(expect.objectContaining(expected));
|
||||
});
|
||||
|
||||
const message = {
|
||||
to: '[email protected]',
|
||||
from: {email: '[email protected]', name: 'Example'},
|
||||
subject: 'Hi',
|
||||
text: 'Body',
|
||||
};
|
||||
|
||||
it('sends the configured headers verbatim', async () => {
|
||||
const provider = new SmtpEmailProvider({
|
||||
host: 'smtp.example.com',
|
||||
port: 587,
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
headers: {'X-Tracking': '{"enable":0}'},
|
||||
});
|
||||
await provider.sendEmail(message);
|
||||
const {sendMail} = createTransport.mock.results[0].value;
|
||||
expect(sendMail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({headers: {'X-Tracking': {prepared: true, value: '{"enable":0}'}}}),
|
||||
);
|
||||
});
|
||||
|
||||
it('sends no extra headers when none are configured', async () => {
|
||||
const provider = new SmtpEmailProvider({host: 'smtp.example.com', port: 587, username: 'user', password: 'pass'});
|
||||
await provider.sendEmail(message);
|
||||
const {sendMail} = createTransport.mock.results[0].value;
|
||||
expect(sendMail.mock.calls[0][0]).not.toHaveProperty('headers');
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,7 @@ interface SmtpEmailConfig {
|
||||
username: string;
|
||||
password: string;
|
||||
tlsMode?: SmtpTlsMode | null;
|
||||
headers?: Record<string, string>;
|
||||
connectionTimeoutMs?: number;
|
||||
greetingTimeoutMs?: number;
|
||||
socketTimeoutMs?: number;
|
||||
@@ -23,11 +24,17 @@ export class SmtpEmailProvider implements IEmailProvider {
|
||||
private readonly host: string;
|
||||
private readonly port: number;
|
||||
private readonly tlsMode: SmtpTlsMode;
|
||||
private readonly headers: Record<string, {prepared: true; value: string}> | undefined;
|
||||
|
||||
constructor(config: SmtpEmailConfig) {
|
||||
this.host = config.host;
|
||||
this.port = config.port;
|
||||
this.tlsMode = resolveSmtpTlsMode(config.tlsMode, config.port);
|
||||
this.headers = config.headers
|
||||
? Object.fromEntries(
|
||||
Object.entries(config.headers).map(([name, value]) => [name, {prepared: true as const, value}]),
|
||||
)
|
||||
: undefined;
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
@@ -55,6 +62,7 @@ export class SmtpEmailProvider implements IEmailProvider {
|
||||
to: message.to,
|
||||
from: `${message.from.name} <${message.from.email}>`,
|
||||
...(message.replyTo ? {replyTo: message.replyTo} : {}),
|
||||
...(this.headers ? {headers: this.headers} : {}),
|
||||
subject: message.subject,
|
||||
text: message.text,
|
||||
});
|
||||
|
||||
@@ -307,7 +307,10 @@ export function buildAPIConfigFromMaster(master: MasterConfig): APIConfig {
|
||||
fallback: master.integrations.email.fallback
|
||||
? {
|
||||
domains: master.integrations.email.fallback.domains,
|
||||
smtp: toSmtpConfig(master.integrations.email.fallback.smtp),
|
||||
smtp: {
|
||||
...toSmtpConfig(master.integrations.email.fallback.smtp),
|
||||
headers: master.integrations.email.fallback.smtp.headers,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
|
||||
@@ -173,7 +173,7 @@ export interface APIConfig {
|
||||
smtp?: SmtpConfig;
|
||||
fallback?: {
|
||||
domains: Array<string>;
|
||||
smtp: SmtpConfig;
|
||||
smtp: SmtpConfig & {headers?: Record<string, string>};
|
||||
};
|
||||
};
|
||||
blocklistFeeds: {
|
||||
|
||||
@@ -4,7 +4,8 @@ import type {APIConfig} from '@app/api/config/APIConfig';
|
||||
import {createEmailProvider} from '@app/api/email/EmailProviderFactory';
|
||||
import {DomainRoutedEmailProvider} from '@pkgs/email/src/DomainRoutedEmailProvider';
|
||||
import {SmtpEmailProvider} from '@pkgs/email/src/SmtpEmailProvider';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import nodemailer, {type Transporter} from 'nodemailer';
|
||||
import {describe, expect, it, vi} from 'vitest';
|
||||
|
||||
const smtp = {host: 'smtp.example.com', port: 587, username: 'user', password: 'pass', tlsMode: null};
|
||||
|
||||
@@ -33,6 +34,18 @@ describe('createEmailProvider', () => {
|
||||
expect(provider).toBeInstanceOf(DomainRoutedEmailProvider);
|
||||
});
|
||||
|
||||
it('adds the fallback headers only to mail through the fallback', async () => {
|
||||
const sendMail = vi.fn();
|
||||
vi.spyOn(nodemailer, 'createTransport').mockReturnValue({sendMail} as unknown as Transporter);
|
||||
const headers = {'X-Tracking': '{"enable":0}'};
|
||||
const provider = createEmailProvider(emailConfig({fallback: {domains: ['gmail.com'], smtp: {...smtp, headers}}}));
|
||||
const from = {email: '[email protected]', name: 'Example'};
|
||||
await provider?.sendEmail({to: '[email protected]', from, subject: 'Hi', text: 'Body'});
|
||||
await provider?.sendEmail({to: '[email protected]', from, subject: 'Hi', text: 'Body'});
|
||||
expect(sendMail.mock.calls[0][0].headers).toEqual({'X-Tracking': {prepared: true, value: '{"enable":0}'}});
|
||||
expect(sendMail.mock.calls[1][0]).not.toHaveProperty('headers');
|
||||
});
|
||||
|
||||
it.each([{enabled: false}, {provider: 'none' as const}, {smtp: undefined}])(
|
||||
'returns no provider for %j even with a fallback',
|
||||
(overrides) => {
|
||||
|
||||
Reference in new issue
Block a user