mirror of
https://github.com/documenso/documenso.git
synced 2025-11-14 00:32:43 +10:00
This change actually makes the authoring flow work for the most part by tying in emailing and more. We have also done a number of quality of life updates to simplify the codebase overall making it easier to continue work on the refresh.
155 lines
4.3 KiB
TypeScript
155 lines
4.3 KiB
TypeScript
import { SentMessageInfo, Transport } from 'nodemailer';
|
|
import type { Address } from 'nodemailer/lib/mailer';
|
|
import type MailMessage from 'nodemailer/lib/mailer/mail-message';
|
|
|
|
const VERSION = '1.0.0';
|
|
|
|
type NodeMailerAddress = string | Address | Array<string | Address> | undefined;
|
|
|
|
interface MailChannelsAddress {
|
|
email: string;
|
|
name?: string;
|
|
}
|
|
|
|
interface MailChannelsTransportOptions {
|
|
apiKey: string;
|
|
endpoint: string;
|
|
}
|
|
|
|
/**
|
|
* Transport for sending email through MailChannels via Cloudflare Workers.
|
|
*
|
|
* Optionally allows specifying a custom endpoint and API key so you can setup a worker
|
|
* to proxy requests to MailChannels with added security.
|
|
*
|
|
* @see https://blog.cloudflare.com/sending-email-from-workers-with-mailchannels/
|
|
*/
|
|
export class MailChannelsTransport implements Transport<SentMessageInfo> {
|
|
public name = 'CloudflareMailTransport';
|
|
public version = VERSION;
|
|
|
|
private _options: MailChannelsTransportOptions;
|
|
|
|
public static makeTransport(options: Partial<MailChannelsTransportOptions>) {
|
|
return new MailChannelsTransport(options);
|
|
}
|
|
|
|
constructor(options: Partial<MailChannelsTransportOptions>) {
|
|
const { apiKey = '', endpoint = 'https://api.mailchannels.net/tx/v1/send' } = options;
|
|
|
|
this._options = {
|
|
apiKey,
|
|
endpoint,
|
|
};
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
const mailTo = this.toMailChannelsAddresses(mail.data.to);
|
|
const mailCc = this.toMailChannelsAddresses(mail.data.cc);
|
|
const mailBcc = this.toMailChannelsAddresses(mail.data.bcc);
|
|
|
|
const from: MailChannelsAddress =
|
|
typeof mail.data.from === 'string'
|
|
? { email: mail.data.from }
|
|
: {
|
|
email: mail.data.from?.address,
|
|
name: mail.data.from?.name,
|
|
};
|
|
|
|
const requestHeaders: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
if (this._options.apiKey) {
|
|
requestHeaders['X-Auth-Token'] = this._options.apiKey;
|
|
}
|
|
|
|
fetch(this._options.endpoint, {
|
|
method: 'POST',
|
|
headers: requestHeaders,
|
|
body: JSON.stringify({
|
|
from: from,
|
|
subject: mail.data.subject,
|
|
personalizations: [
|
|
{
|
|
to: mailTo,
|
|
cc: mailCc.length > 0 ? mailCc : undefined,
|
|
bcc: mailBcc.length > 0 ? mailBcc : undefined,
|
|
dkim_domain: process.env.NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN || undefined,
|
|
dkim_selector: process.env.NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR || undefined,
|
|
dkim_private_key: process.env.NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY || undefined,
|
|
},
|
|
],
|
|
content: [
|
|
{
|
|
type: 'text/plain',
|
|
value: mail.data.text?.toString('utf-8') ?? '',
|
|
},
|
|
{
|
|
type: 'text/html',
|
|
value: mail.data.html?.toString('utf-8') ?? '',
|
|
},
|
|
],
|
|
}),
|
|
})
|
|
.then((res) => {
|
|
if (res.status >= 200 && res.status <= 299) {
|
|
return callback(null, {
|
|
messageId: '',
|
|
envelope: {
|
|
from: mail.data.from,
|
|
to: mail.data.to,
|
|
},
|
|
accepted: mail.data.to,
|
|
rejected: [],
|
|
pending: [],
|
|
});
|
|
}
|
|
|
|
res.json().then((data) => {
|
|
return callback(new Error(`MailChannels error: ${data.message}`), null);
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
return callback(err, null);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Converts a nodemailer address(s) to an array of MailChannel compatible address.
|
|
*/
|
|
private toMailChannelsAddresses(address: NodeMailerAddress): Array<MailChannelsAddress> {
|
|
if (!address) {
|
|
return [];
|
|
}
|
|
|
|
if (typeof address === 'string') {
|
|
return [{ email: address }];
|
|
}
|
|
|
|
if (Array.isArray(address)) {
|
|
return address.map((address) => {
|
|
if (typeof address === 'string') {
|
|
return { email: address };
|
|
}
|
|
|
|
return {
|
|
email: address.address,
|
|
name: address.name,
|
|
};
|
|
});
|
|
}
|
|
|
|
return [
|
|
{
|
|
email: address.address,
|
|
name: address.name,
|
|
},
|
|
];
|
|
}
|
|
}
|