From a5e087f2090b97628e18d2bf595f51eaff302293 Mon Sep 17 00:00:00 2001 From: Mythie Date: Fri, 29 Sep 2023 12:17:41 +1000 Subject: [PATCH] fix: dogfood resend transport --- package-lock.json | 12 +++ packages/email/mailer.ts | 3 +- packages/email/package.json | 1 + packages/email/transports/resend.ts | 145 ---------------------------- 4 files changed, 15 insertions(+), 146 deletions(-) delete mode 100644 packages/email/transports/resend.ts diff --git a/package-lock.json b/package-lock.json index 2904b8297..399c48c9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1869,6 +1869,17 @@ "resolved": "apps/marketing", "link": true }, + "node_modules/@documenso/nodemailer-resend": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@documenso/nodemailer-resend/-/nodemailer-resend-1.0.0.tgz", + "integrity": "sha512-rG+jBbBEsVJUBU6v/2hb+OQD1m3Lhn49TOzQjln73zzL1B/sZsHhYOKpNPlTX0/FafCP7P9fKerndEeIKn54Vw==", + "dependencies": { + "resend": "^1.1.0" + }, + "peerDependencies": { + "nodemailer": "^6.9.3" + } + }, "node_modules/@documenso/prettier-config": { "resolved": "packages/prettier-config", "link": true @@ -20014,6 +20025,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@documenso/nodemailer-resend": "1.0.0", "@react-email/components": "^0.0.7", "nodemailer": "^6.9.3", "react-email": "^1.9.4", diff --git a/packages/email/mailer.ts b/packages/email/mailer.ts index cf359e648..92a30522c 100644 --- a/packages/email/mailer.ts +++ b/packages/email/mailer.ts @@ -1,7 +1,8 @@ import { createTransport } from 'nodemailer'; +import { ResendTransport } from '@documenso/nodemailer-resend'; + import { MailChannelsTransport } from './transports/mailchannels'; -import { ResendTransport } from './transports/resend'; const getTransport = () => { const transport = process.env.NEXT_PRIVATE_SMTP_TRANSPORT ?? 'smtp-auth'; diff --git a/packages/email/package.json b/packages/email/package.json index f9ce13279..4b23512ce 100644 --- a/packages/email/package.json +++ b/packages/email/package.json @@ -17,6 +17,7 @@ "worker:test": "tsup worker/index.ts --format esm" }, "dependencies": { + "@documenso/nodemailer-resend": "1.0.0", "@react-email/components": "^0.0.7", "nodemailer": "^6.9.3", "react-email": "^1.9.4", diff --git a/packages/email/transports/resend.ts b/packages/email/transports/resend.ts deleted file mode 100644 index ab2f0959d..000000000 --- a/packages/email/transports/resend.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { type SentMessageInfo, type Transport } from 'nodemailer'; -import type Mail from 'nodemailer/lib/mailer'; -import type MailMessage from 'nodemailer/lib/mailer/mail-message'; -import { Resend } from 'resend'; - -const VERSION = '1.0.0'; - -type ResendTransportOptions = { - apiKey: string; -}; - -type ResendResponseError = { - statusCode: number; - name: string; - message: string; -}; - -const isResendResponseError = (error: unknown): error is ResendResponseError => { - // We could use Zod here, but it's not worth the extra bundle size - return ( - typeof error === 'object' && - error !== null && - 'statusCode' in error && - typeof error.statusCode === 'number' && - 'name' in error && - typeof error.name === 'string' && - 'message' in error && - typeof error.message === 'string' - ); -}; - -/** - * Transport for sending email via the Resend SDK. - */ -export class ResendTransport implements Transport { - public name = 'ResendMailTransport'; - public version = VERSION; - - private _client: Resend; - private _options: ResendTransportOptions; - - public static makeTransport(options: Partial) { - return new ResendTransport(options); - } - - constructor(options: Partial) { - const { apiKey = '' } = options; - - this._options = { - apiKey, - }; - - this._client = new Resend(apiKey); - } - - public send(mail: MailMessage, callback: (_err: Error | null, _info: SentMessageInfo) => void) { - if (!mail.data.to || !mail.data.from) { - return callback(new Error('Missing required fields "to" or "from"'), null); - } - - this._client - .sendEmail({ - subject: mail.data.subject ?? '', - from: this.toResendFromAddress(mail.data.from), - to: this.toResendAddresses(mail.data.to), - cc: this.toResendAddresses(mail.data.cc), - bcc: this.toResendAddresses(mail.data.bcc), - html: mail.data.html?.toString() || '', - text: mail.data.text?.toString() || '', - attachments: this.toResendAttachments(mail.data.attachments), - }) - .then((response) => { - if (isResendResponseError(response)) { - throw new Error(`[${response.statusCode}]: ${response.name} ${response.message}`); - } - - callback(null, response); - }) - .catch((error) => { - callback(error, null); - }); - } - - private toResendAddresses(addresses: Mail.Options['to']) { - if (!addresses) { - return []; - } - - if (typeof addresses === 'string') { - return [addresses]; - } - - if (Array.isArray(addresses)) { - return addresses.map((address) => { - if (typeof address === 'string') { - return address; - } - - return address.address; - }); - } - - return [addresses.address]; - } - - private toResendFromAddress(address: Mail.Options['from']) { - if (!address) { - return ''; - } - - if (typeof address === 'string') { - return address; - } - - return `${address.name} <${address.address}>`; - } - - private toResendAttachments(attachments: Mail.Options['attachments']) { - if (!attachments) { - return []; - } - - return attachments.map((attachment) => { - if (!attachment.filename || !attachment.content) { - throw new Error('Attachment is missing filename or content'); - } - - if (typeof attachment.content === 'string') { - return { - filename: attachment.filename, - content: Buffer.from(attachment.content), - }; - } - - if (attachment.content instanceof Buffer) { - return { - filename: attachment.filename, - content: attachment.content, - }; - } - - throw new Error('Attachment content must be a string or a buffer'); - }); - } -}