mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
feat: use server-actions for authoring flow
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.
This commit is contained in:
@ -1 +1 @@
|
||||
export { render, renderAsync } from '@react-email/components';
|
||||
export {};
|
||||
|
||||
50
packages/email/mailer.ts
Normal file
50
packages/email/mailer.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { createTransport } from 'nodemailer';
|
||||
|
||||
import { MailChannelsTransport } from './transports/mailchannels';
|
||||
|
||||
const getTransport = () => {
|
||||
const transport = process.env.NEXT_PRIVATE_SMTP_TRANSPORT ?? 'smtp-auth';
|
||||
|
||||
if (transport === 'mailchannels') {
|
||||
return createTransport(
|
||||
MailChannelsTransport.makeTransport({
|
||||
apiKey: process.env.NEXT_PRIVATE_MAILCHANNELS_API_KEY,
|
||||
endpoint: process.env.NEXT_PRIVATE_MAILCHANNELS_ENDPOINT,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (transport === 'smtp-api') {
|
||||
if (!process.env.NEXT_PRIVATE_SMTP_HOST || !process.env.NEXT_PRIVATE_SMTP_APIKEY) {
|
||||
throw new Error(
|
||||
'SMTP API transport requires NEXT_PRIVATE_SMTP_HOST and NEXT_PRIVATE_SMTP_APIKEY',
|
||||
);
|
||||
}
|
||||
|
||||
return createTransport({
|
||||
host: process.env.NEXT_PRIVATE_SMTP_HOST,
|
||||
port: Number(process.env.NEXT_PRIVATE_SMTP_PORT) || 587,
|
||||
secure: process.env.NEXT_PRIVATE_SMTP_SECURE === 'true',
|
||||
auth: {
|
||||
user: process.env.NEXT_PRIVATE_SMTP_APIKEY_USER ?? 'apikey',
|
||||
pass: process.env.NEXT_PRIVATE_SMTP_APIKEY ?? '',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!process.env.NEXT_PRIVATE_SMTP_HOST) {
|
||||
throw new Error('SMTP transport requires NEXT_PRIVATE_SMTP_HOST');
|
||||
}
|
||||
|
||||
return createTransport({
|
||||
host: process.env.NEXT_PRIVATE_SMTP_HOST,
|
||||
port: Number(process.env.NEXT_PRIVATE_SMTP_PORT) || 587,
|
||||
secure: process.env.NEXT_PRIVATE_SMTP_SECURE === 'true',
|
||||
auth: {
|
||||
user: process.env.NEXT_PRIVATE_SMTP_USERNAME ?? '',
|
||||
pass: process.env.NEXT_PRIVATE_SMTP_PASSWORD ?? '',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const mailer = getTransport();
|
||||
@ -5,18 +5,26 @@
|
||||
"types": "./index.ts",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"templates/"
|
||||
"templates/",
|
||||
"transports/",
|
||||
"mailer.ts",
|
||||
"render.ts",
|
||||
"index.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "email dev --port 3002 --dir templates"
|
||||
"dev": "email dev --port 3002 --dir templates",
|
||||
"worker:test": "tsup worker/index.ts --format esm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@documenso/tsconfig": "*",
|
||||
"@documenso/tailwind-config": "*",
|
||||
"@documenso/ui": "*",
|
||||
"@react-email/components": "^0.0.7"
|
||||
"@react-email/components": "^0.0.7",
|
||||
"nodemailer": "^6.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"react-email": "^1.9.4"
|
||||
"@types/nodemailer": "^6.4.8",
|
||||
"react-email": "^1.9.4",
|
||||
"tsup": "^7.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
1
packages/email/render.ts
Normal file
1
packages/email/render.ts
Normal file
@ -0,0 +1 @@
|
||||
export { render, renderAsync } from '@react-email/components';
|
||||
@ -1,6 +1,5 @@
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Container,
|
||||
Head,
|
||||
Html,
|
||||
|
||||
154
packages/email/transports/mailchannels.ts
Normal file
154
packages/email/transports/mailchannels.ts
Normal file
@ -0,0 +1,154 @@
|
||||
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,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user