0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

feat(connector): enable custom headers for SMTP connector (#6256)

This commit is contained in:
Darcy Ye 2024-07-17 15:16:54 +08:00 committed by GitHub
parent 0a92bd2fdc
commit 6fca3fe3c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 47 additions and 2 deletions

View file

@ -0,0 +1,5 @@
---
"@logto/connector-smtp": minor
---
enable static custom headers for SMTP connector

View file

@ -198,5 +198,14 @@ export const defaultMetadata: ConnectorMetadata = {
type: ConnectorConfigFormItemType.Switch, type: ConnectorConfigFormItemType.Switch,
required: false, required: false,
}, },
{
key: 'customHeaders',
label: 'Custom Headers',
type: ConnectorConfigFormItemType.Json,
required: false,
defaultValue: {},
description:
'Custom headers to be added to original email headers when sending messages. Both keys and values should be string-typed.',
},
], ],
}; };

View file

@ -79,6 +79,28 @@ describe('SMTP connector', () => {
to: 'baz', to: 'baz',
}); });
}); });
it('should send mail with customer headers', async () => {
const connector = await createConnector({
getConfig: vi.fn().mockResolvedValue({
...mockedConfig,
customHeaders: { 'X-Test': 'test', 'X-Test-Another': ['test1', 'test2', 'test3'] },
}),
});
await connector.sendMessage({
to: 'baz',
type: TemplateType.OrganizationInvitation,
payload: { code: '345678', link: 'https://example.com' },
});
expect(sendMail).toHaveBeenCalledWith({
from: '<notice@test.smtp>',
subject: 'Organization invitation',
text: 'This is for organization invitation. Your link is https://example.com.',
to: 'baz',
headers: { 'X-Test': 'test', 'X-Test-Another': ['test1', 'test2', 'test3'] },
});
});
}); });
describe('Test config guard', () => { describe('Test config guard', () => {

View file

@ -1,4 +1,4 @@
import { assert } from '@silverhand/essentials'; import { assert, conditional } from '@silverhand/essentials';
import type { import type {
GetConnectorConfig, GetConnectorConfig,
@ -14,6 +14,7 @@ import {
replaceSendMessageHandlebars, replaceSendMessageHandlebars,
} from '@logto/connector-kit'; } from '@logto/connector-kit';
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import type Mail from 'nodemailer/lib/mailer';
import type SMTPTransport from 'nodemailer/lib/smtp-transport'; import type SMTPTransport from 'nodemailer/lib/smtp-transport';
import { defaultMetadata } from './constant.js'; import { defaultMetadata } from './constant.js';
@ -44,11 +45,17 @@ const sendMessage =
template.contentType template.contentType
); );
const mailOptions = { const mailOptions: Mail.Options = {
to, to,
from: config.fromEmail, from: config.fromEmail,
replyTo: config.replyTo, replyTo: config.replyTo,
subject: replaceSendMessageHandlebars(template.subject, payload), subject: replaceSendMessageHandlebars(template.subject, payload),
...conditional(
config.customHeaders &&
Object.entries(config.customHeaders).length > 0 && {
headers: config.customHeaders,
}
),
...contentsObject, ...contentsObject,
}; };

View file

@ -35,6 +35,7 @@ export const mockedConfig = {
usageType: 'OrganizationInvitation', usageType: 'OrganizationInvitation',
}, },
], ],
customHeaders: {},
}; };
export const mockedOauth2AuthWithToken = { export const mockedOauth2AuthWithToken = {

View file

@ -125,6 +125,7 @@ export const smtpConfigGuard = z.object({
servername: z.string().optional(), servername: z.string().optional(),
ignoreTLS: z.boolean().optional(), ignoreTLS: z.boolean().optional(),
requireTLS: z.boolean().optional(), requireTLS: z.boolean().optional(),
customHeaders: z.record(z.string().or(z.string().array())).optional(),
}); });
export type SmtpConfig = z.infer<typeof smtpConfigGuard>; export type SmtpConfig = z.infer<typeof smtpConfigGuard>;