mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
feat(api): add domain-routed fallback email provider (#3506)
This commit is contained in:
1 parent
c195e2b5de
commit
0b1ddc2695
15 files changed
+346
-30
No files matched your search
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {DomainRoutedEmailProvider} from '@pkgs/email/src/DomainRoutedEmailProvider';
|
||||
import type {EmailMessage} from '@pkgs/email/src/EmailProviderTypes';
|
||||
import {EmailService} from '@pkgs/email/src/EmailService';
|
||||
import {describe, expect, it, vi} from 'vitest';
|
||||
|
||||
function message(to: string): EmailMessage {
|
||||
return {to, from: {email: '[email protected]', name: 'Example'}, subject: 'Hello', text: 'Body'};
|
||||
}
|
||||
|
||||
describe('DomainRoutedEmailProvider', () => {
|
||||
it.each([
|
||||
{to: '[email protected]', route: 'fallback'},
|
||||
{to: '[email protected]', route: 'fallback'},
|
||||
{to: '[email protected]', route: 'fallback'},
|
||||
{to: '[email protected]', route: 'primary'},
|
||||
{to: '[email protected]', route: 'primary'},
|
||||
{to: '[email protected]', route: 'primary'},
|
||||
{to: '"[email protected]"@example.com', route: 'primary'},
|
||||
])('sends $to through the $route provider', async ({to, route}) => {
|
||||
const primary = {sendEmail: vi.fn().mockResolvedValue(true)};
|
||||
const fallback = {sendEmail: vi.fn().mockResolvedValue(false)};
|
||||
const provider = new DomainRoutedEmailProvider(primary, fallback, ['gmail.com', 'googlemail.com']);
|
||||
const sent = message(to);
|
||||
const result = await provider.sendEmail(sent);
|
||||
const [chosen, other] = route === 'fallback' ? [fallback, primary] : [primary, fallback];
|
||||
expect(chosen.sendEmail).toHaveBeenCalledWith(sent);
|
||||
expect(other.sendEmail).not.toHaveBeenCalled();
|
||||
expect(result).toBe(route === 'primary');
|
||||
});
|
||||
|
||||
it('never reaches either provider for a bounced address', async () => {
|
||||
const primary = {sendEmail: vi.fn().mockResolvedValue(true)};
|
||||
const fallback = {sendEmail: vi.fn().mockResolvedValue(true)};
|
||||
const service = new EmailService(
|
||||
{
|
||||
enabled: true,
|
||||
fromEmail: '[email protected]',
|
||||
fromName: 'Example',
|
||||
appBaseUrl: 'https://example.com',
|
||||
appealsEmail: null,
|
||||
safetyEmail: null,
|
||||
supportEmail: null,
|
||||
productName: 'Example',
|
||||
},
|
||||
new DomainRoutedEmailProvider(primary, fallback, ['gmail.com']),
|
||||
{isEmailBounced: async () => true},
|
||||
);
|
||||
expect(await service.sendPasswordResetEmail('[email protected]', 'user', 'token')).toBe(false);
|
||||
expect(primary.sendEmail).not.toHaveBeenCalled();
|
||||
expect(fallback.sendEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {createLogger} from '@fluxer/logger/src/Logger';
|
||||
import type {EmailMessage, IEmailProvider} from '@pkgs/email/src/EmailProviderTypes';
|
||||
|
||||
const logger = createLogger('@pkgs/email/src/DomainRoutedEmailProvider');
|
||||
|
||||
export class DomainRoutedEmailProvider implements IEmailProvider {
|
||||
private readonly primary: IEmailProvider;
|
||||
private readonly fallback: IEmailProvider;
|
||||
private readonly fallbackDomains: ReadonlySet<string>;
|
||||
|
||||
constructor(primary: IEmailProvider, fallback: IEmailProvider, fallbackDomains: ReadonlyArray<string>) {
|
||||
this.primary = primary;
|
||||
this.fallback = fallback;
|
||||
this.fallbackDomains = new Set(fallbackDomains);
|
||||
}
|
||||
|
||||
async sendEmail(message: EmailMessage): Promise<boolean> {
|
||||
const domain = message.to
|
||||
.slice(message.to.lastIndexOf('@') + 1)
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const useFallback = this.fallbackDomains.has(domain);
|
||||
logger.debug({domain, provider: useFallback ? 'fallback' : 'primary'}, 'Routing email');
|
||||
return (useFallback ? this.fallback : this.primary).sendEmail(message);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user