From 6fca3fe3c4deb63171f1c697a5d5d530ef9992af Mon Sep 17 00:00:00 2001 From: Darcy Ye Date: Wed, 17 Jul 2024 15:16:54 +0800 Subject: [PATCH] feat(connector): enable custom headers for SMTP connector (#6256) --- .changeset/slow-boxes-greet.md | 5 +++++ .../connectors/connector-smtp/src/constant.ts | 9 ++++++++ .../connector-smtp/src/index.test.ts | 22 +++++++++++++++++++ .../connectors/connector-smtp/src/index.ts | 11 ++++++++-- .../connectors/connector-smtp/src/mock.ts | 1 + .../connectors/connector-smtp/src/types.ts | 1 + 6 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 .changeset/slow-boxes-greet.md diff --git a/.changeset/slow-boxes-greet.md b/.changeset/slow-boxes-greet.md new file mode 100644 index 000000000..3114f181f --- /dev/null +++ b/.changeset/slow-boxes-greet.md @@ -0,0 +1,5 @@ +--- +"@logto/connector-smtp": minor +--- + +enable static custom headers for SMTP connector diff --git a/packages/connectors/connector-smtp/src/constant.ts b/packages/connectors/connector-smtp/src/constant.ts index d2078b048..28d9a4276 100644 --- a/packages/connectors/connector-smtp/src/constant.ts +++ b/packages/connectors/connector-smtp/src/constant.ts @@ -198,5 +198,14 @@ export const defaultMetadata: ConnectorMetadata = { type: ConnectorConfigFormItemType.Switch, 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.', + }, ], }; diff --git a/packages/connectors/connector-smtp/src/index.test.ts b/packages/connectors/connector-smtp/src/index.test.ts index a28137dd3..b57ad02e1 100644 --- a/packages/connectors/connector-smtp/src/index.test.ts +++ b/packages/connectors/connector-smtp/src/index.test.ts @@ -79,6 +79,28 @@ describe('SMTP connector', () => { 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: '', + 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', () => { diff --git a/packages/connectors/connector-smtp/src/index.ts b/packages/connectors/connector-smtp/src/index.ts index 8aed0e05d..f447f420e 100644 --- a/packages/connectors/connector-smtp/src/index.ts +++ b/packages/connectors/connector-smtp/src/index.ts @@ -1,4 +1,4 @@ -import { assert } from '@silverhand/essentials'; +import { assert, conditional } from '@silverhand/essentials'; import type { GetConnectorConfig, @@ -14,6 +14,7 @@ import { replaceSendMessageHandlebars, } from '@logto/connector-kit'; import nodemailer from 'nodemailer'; +import type Mail from 'nodemailer/lib/mailer'; import type SMTPTransport from 'nodemailer/lib/smtp-transport'; import { defaultMetadata } from './constant.js'; @@ -44,11 +45,17 @@ const sendMessage = template.contentType ); - const mailOptions = { + const mailOptions: Mail.Options = { to, from: config.fromEmail, replyTo: config.replyTo, subject: replaceSendMessageHandlebars(template.subject, payload), + ...conditional( + config.customHeaders && + Object.entries(config.customHeaders).length > 0 && { + headers: config.customHeaders, + } + ), ...contentsObject, }; diff --git a/packages/connectors/connector-smtp/src/mock.ts b/packages/connectors/connector-smtp/src/mock.ts index 87f2017d3..bef4a38b1 100644 --- a/packages/connectors/connector-smtp/src/mock.ts +++ b/packages/connectors/connector-smtp/src/mock.ts @@ -35,6 +35,7 @@ export const mockedConfig = { usageType: 'OrganizationInvitation', }, ], + customHeaders: {}, }; export const mockedOauth2AuthWithToken = { diff --git a/packages/connectors/connector-smtp/src/types.ts b/packages/connectors/connector-smtp/src/types.ts index 263fe8028..cf1862210 100644 --- a/packages/connectors/connector-smtp/src/types.ts +++ b/packages/connectors/connector-smtp/src/types.ts @@ -125,6 +125,7 @@ export const smtpConfigGuard = z.object({ servername: z.string().optional(), ignoreTLS: z.boolean().optional(), requireTLS: z.boolean().optional(), + customHeaders: z.record(z.string().or(z.string().array())).optional(), }); export type SmtpConfig = z.infer;