Add Support for Alternate Email Transports (#1580)
* Add support for AWS SES and SMTP email transports Signed-off-by: Erin Allison <eallison@andrettikarting.com> * Correct environment variable names for new email settings Signed-off-by: Erin Allison <eallison@andrettikarting.com> * Correct option names being passed to nodemailer for SMTP Signed-off-by: Erin Allison <eallison@andrettikarting.com> * Remove use of AWS SDK synthetic default export It apparently causes issues when transpiled/bundled Signed-off-by: Erin Allison <eallison@andrettikarting.com> * Add documentation for new email settings Signed-off-by: Erin Allison <eallison@andrettikarting.com> * Move nodemailer types to devDependencies Signed-off-by: Erin Allison <eallison@andrettikarting.com> * Adjust mail transport error handling Gotta keep the linter happy :) Signed-off-by: Erin Allison <eallison@andrettikarting.com> * Fix typecheck error on MailTransportOptions Signed-off-by: Erin Allison <eallison@andrettikarting.com> * Correct environment variable usage for SMTP email transport Signed-off-by: Erin Allison <eallison@andrettikarting.com> --------- Signed-off-by: Erin Allison <eallison@andrettikarting.com>
This commit is contained in:
+25
-3
@@ -31,10 +31,32 @@ DEV_OTEL_BATCH_PROCESSING_ENABLED="0"
|
||||
# AUTH_GITHUB_CLIENT_ID=
|
||||
# AUTH_GITHUB_CLIENT_SECRET=
|
||||
|
||||
# Resend is an email service used for signing in to Trigger.dev via a Magic Link.
|
||||
# Emails will print to the console if you leave these commented out
|
||||
# Configure an email transport to allow users to sign in to Trigger.dev via a Magic Link.
|
||||
# If none are configured, emails will print to the console instead.
|
||||
# Uncomment one of the following blocks to allow delivery of
|
||||
|
||||
# Resend
|
||||
### Visit https://resend.com, create an account and get your API key. Then insert it below along with your From and Reply To email addresses. Visit https://resend.com/docs for more information.
|
||||
# RESEND_API_KEY=<api_key>
|
||||
# EMAIL_TRANSPORT=resend
|
||||
# FROM_EMAIL=
|
||||
# REPLY_TO_EMAIL=
|
||||
# RESEND_API_KEY=
|
||||
|
||||
# Generic SMTP
|
||||
### Enter the configuration provided by your mail provider. Visit https://nodemailer.com/smtp/ for more information
|
||||
### SMTP_SECURE = false will use STARTTLS when connecting to a server that supports it (usually port 587)
|
||||
# EMAIL_TRANSPORT=smtp
|
||||
# FROM_EMAIL=
|
||||
# REPLY_TO_EMAIL=
|
||||
# SMTP_HOST=
|
||||
# SMTP_PORT=587
|
||||
# SMTP_SECURE=false
|
||||
# SMTP_USER=
|
||||
# SMTP_PASSWORD=
|
||||
|
||||
# AWS Simple Email Service
|
||||
### Authentication is configured using the default Node.JS credentials provider chain (https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/#fromnodeproviderchain)
|
||||
# EMAIL_TRANSPORT=aws-ses
|
||||
# FROM_EMAIL=
|
||||
# REPLY_TO_EMAIL=
|
||||
|
||||
|
||||
@@ -44,9 +44,16 @@ const EnvironmentSchema = z.object({
|
||||
HIGHLIGHT_PROJECT_ID: z.string().optional(),
|
||||
AUTH_GITHUB_CLIENT_ID: z.string().optional(),
|
||||
AUTH_GITHUB_CLIENT_SECRET: z.string().optional(),
|
||||
EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(),
|
||||
FROM_EMAIL: z.string().optional(),
|
||||
REPLY_TO_EMAIL: z.string().optional(),
|
||||
RESEND_API_KEY: z.string().optional(),
|
||||
SMTP_HOST: z.string().optional(),
|
||||
SMTP_PORT: z.coerce.number().optional(),
|
||||
SMTP_SECURE: z.coerce.boolean().optional(),
|
||||
SMTP_USER: z.string().optional(),
|
||||
SMTP_PASSWORD: z.string().optional(),
|
||||
|
||||
PLAIN_API_KEY: z.string().optional(),
|
||||
RUNTIME_PLATFORM: z.enum(["docker-compose", "ecs", "local"]).default("local"),
|
||||
WORKER_SCHEMA: z.string().default("graphile_worker"),
|
||||
@@ -195,8 +202,16 @@ const EnvironmentSchema = z.object({
|
||||
ORG_SLACK_INTEGRATION_CLIENT_SECRET: z.string().optional(),
|
||||
|
||||
/** These enable the alerts feature in v3 */
|
||||
ALERT_EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(),
|
||||
ALERT_FROM_EMAIL: z.string().optional(),
|
||||
ALERT_REPLY_TO_EMAIL: z.string().optional(),
|
||||
ALERT_RESEND_API_KEY: z.string().optional(),
|
||||
ALERT_SMTP_HOST: z.string().optional(),
|
||||
ALERT_SMTP_PORT: z.coerce.number().optional(),
|
||||
ALERT_SMTP_SECURE: z.coerce.boolean().optional(),
|
||||
ALERT_SMTP_USER: z.string().optional(),
|
||||
ALERT_SMTP_PASSWORD: z.string().optional(),
|
||||
|
||||
|
||||
MAX_SEQUENTIAL_INDEX_FAILURE_COUNT: z.coerce.number().default(96),
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DeliverEmail, SendPlainTextOptions } from "emails";
|
||||
import { EmailClient } from "emails";
|
||||
import { EmailClient, MailTransportOptions } from "emails";
|
||||
import type { SendEmailOptions } from "remix-auth-email-link";
|
||||
import { redirect } from "remix-typedjson";
|
||||
import { env } from "~/env.server";
|
||||
@@ -13,7 +13,7 @@ const client = singleton(
|
||||
"email-client",
|
||||
() =>
|
||||
new EmailClient({
|
||||
apikey: env.RESEND_API_KEY,
|
||||
transport: buildTransportOptions(),
|
||||
imagesBaseUrl: env.APP_ORIGIN,
|
||||
from: env.FROM_EMAIL ?? "team@email.trigger.dev",
|
||||
replyTo: env.REPLY_TO_EMAIL ?? "help@email.trigger.dev",
|
||||
@@ -24,13 +24,45 @@ const alertsClient = singleton(
|
||||
"alerts-email-client",
|
||||
() =>
|
||||
new EmailClient({
|
||||
apikey: env.ALERT_RESEND_API_KEY,
|
||||
transport: buildTransportOptions(true),
|
||||
imagesBaseUrl: env.APP_ORIGIN,
|
||||
from: env.ALERT_FROM_EMAIL ?? "noreply@alerts.trigger.dev",
|
||||
replyTo: env.REPLY_TO_EMAIL ?? "help@email.trigger.dev",
|
||||
})
|
||||
);
|
||||
|
||||
function buildTransportOptions(alerts?: boolean): MailTransportOptions {
|
||||
const transportType = alerts ? env.ALERT_EMAIL_TRANSPORT : env.EMAIL_TRANSPORT
|
||||
logger.debug(`Constructing email transport '${transportType}' for usage '${alerts?'alerts':'general'}'`)
|
||||
|
||||
switch (transportType) {
|
||||
case "aws-ses":
|
||||
return { type: "aws-ses" };
|
||||
case "resend":
|
||||
return {
|
||||
type: "resend",
|
||||
config: {
|
||||
apiKey: alerts ? env.ALERT_RESEND_API_KEY : env.RESEND_API_KEY,
|
||||
}
|
||||
}
|
||||
case "smtp":
|
||||
return {
|
||||
type: "smtp",
|
||||
config: {
|
||||
host: alerts ? env.ALERT_SMTP_HOST : env.SMTP_HOST,
|
||||
port: alerts ? env.ALERT_SMTP_PORT : env.SMTP_PORT,
|
||||
secure: alerts ? env.ALERT_SMTP_SECURE : env.SMTP_SECURE,
|
||||
auth: {
|
||||
user: alerts ? env.ALERT_SMTP_USER : env.SMTP_USER,
|
||||
pass: alerts ? env.ALERT_SMTP_PASSWORD : env.SMTP_PASSWORD
|
||||
}
|
||||
}
|
||||
};
|
||||
default:
|
||||
return { type: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendMagicLinkEmail(options: SendEmailOptions<AuthUser>): Promise<void> {
|
||||
// Auto redirect when in development mode
|
||||
if (env.NODE_ENV === "development") {
|
||||
|
||||
@@ -269,7 +269,45 @@ TRIGGER_IMAGE_TAG=v3.0.4
|
||||
|
||||
### Auth options
|
||||
|
||||
By default, magic link auth is the only login option. If the `RESEND_API_KEY` env var is not set, the magic links will be logged by the webapp container and not sent via email.
|
||||
By default, magic link auth is the only login option. If the `EMAIL_TRANSPORT` env var is not set, the magic links will be logged by the webapp container and not sent via email.
|
||||
|
||||
Depending on your choice of mail provider/transport, you will want to configure a set of variables like one of the following:
|
||||
|
||||
##### Resend:
|
||||
```bash
|
||||
EMAIL_TRANSPORT=resend
|
||||
FROM_EMAIL=
|
||||
REPLY_TO_EMAIL=
|
||||
RESEND_API_KEY=<your_resend_api_key>
|
||||
```
|
||||
|
||||
##### SMTP
|
||||
|
||||
Note that setting `SMTP_SECURE=false` does _not_ mean the email is sent insecurely.
|
||||
This simply means that the connection is secured using the modern STARTTLS protocol command instead of implicit TLS.
|
||||
You should only set this to true when the SMTP server host directs you to do so (generally when using port 465)
|
||||
|
||||
```bash
|
||||
EMAIL_TRANSPORT=smtp
|
||||
FROM_EMAIL=
|
||||
REPLY_TO_EMAIL=
|
||||
SMTP_HOST=<your_smtp_server>
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=false
|
||||
SMTP_USER=<your_smtp_username>
|
||||
SMTP_PASSWORD=<your_smtp_password>
|
||||
```
|
||||
|
||||
##### AWS Simple Email Service
|
||||
|
||||
Credentials are to be supplied as with any other program using the AWS SDK.
|
||||
In this scenario, you would likely either supply the additional environment variables `AWS_REGION`, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` or, when running on AWS, use credentials supplied by the EC2 IMDS.
|
||||
|
||||
```bash
|
||||
EMAIL_TRANSPORT=aws-ses
|
||||
FROM_EMAIL=
|
||||
REPLY_TO_EMAIL=
|
||||
```
|
||||
|
||||
All email addresses can sign up and log in this way. If you would like to restrict this, you can use the `WHITELISTED_EMAILS` env var. For example:
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
"dev": "PORT=3080 email dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-ses": "^3.716.0",
|
||||
"@react-email/components": "0.0.16",
|
||||
"@react-email/render": "^0.0.12",
|
||||
"nodemailer": "^6.9.16",
|
||||
"react": "^18.2.0",
|
||||
"react-email": "^2.1.1",
|
||||
"resend": "^3.2.0",
|
||||
@@ -19,10 +21,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/react": "18.2.69",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { render } from "@react-email/render";
|
||||
import { ReactElement } from "react";
|
||||
import AlertRunFailureEmail, { AlertRunEmailSchema } from "../emails/alert-run-failure";
|
||||
|
||||
import { z } from "zod";
|
||||
import AlertAttemptFailureEmail, { AlertAttemptEmailSchema } from "../emails/alert-attempt-failure";
|
||||
import AlertRunFailureEmail, { AlertRunEmailSchema } from "../emails/alert-run-failure";
|
||||
import { setGlobalBasePath } from "../emails/components/BasePath";
|
||||
import AlertDeploymentFailureEmail, {
|
||||
AlertDeploymentFailureEmailSchema,
|
||||
@@ -12,9 +13,9 @@ import AlertDeploymentSuccessEmail, {
|
||||
import InviteEmail, { InviteEmailSchema } from "../emails/invite";
|
||||
import MagicLinkEmail from "../emails/magic-link";
|
||||
import WelcomeEmail from "../emails/welcome";
|
||||
import { constructMailTransport, MailTransport, MailTransportOptions } from "./transports";
|
||||
|
||||
import { Resend } from "resend";
|
||||
import { z } from "zod";
|
||||
export { type MailTransportOptions }
|
||||
|
||||
export const DeliverEmailSchema = z
|
||||
.discriminatedUnion("email", [
|
||||
@@ -39,14 +40,20 @@ export type DeliverEmail = z.infer<typeof DeliverEmailSchema>;
|
||||
export type SendPlainTextOptions = { to: string; subject: string; text: string };
|
||||
|
||||
export class EmailClient {
|
||||
#client?: Resend;
|
||||
#transport: MailTransport;
|
||||
|
||||
#imagesBaseUrl: string;
|
||||
#from: string;
|
||||
#replyTo: string;
|
||||
|
||||
constructor(config: { apikey?: string; imagesBaseUrl: string; from: string; replyTo: string }) {
|
||||
this.#client =
|
||||
config.apikey && config.apikey.startsWith("re_") ? new Resend(config.apikey) : undefined;
|
||||
constructor(config: {
|
||||
transport?: MailTransportOptions;
|
||||
imagesBaseUrl: string;
|
||||
from: string;
|
||||
replyTo: string;
|
||||
}) {
|
||||
this.#transport = constructMailTransport(config.transport ?? { type: undefined });
|
||||
|
||||
this.#imagesBaseUrl = config.imagesBaseUrl;
|
||||
this.#from = config.from;
|
||||
this.#replyTo = config.replyTo;
|
||||
@@ -57,25 +64,21 @@ export class EmailClient {
|
||||
|
||||
setGlobalBasePath(this.#imagesBaseUrl);
|
||||
|
||||
return this.#sendEmail({
|
||||
return await this.#transport.send({
|
||||
to: data.to,
|
||||
subject,
|
||||
react: component,
|
||||
from: this.#from,
|
||||
replyTo: this.#replyTo,
|
||||
});
|
||||
}
|
||||
|
||||
async sendPlainText(options: SendPlainTextOptions) {
|
||||
if (this.#client) {
|
||||
await this.#client.emails.send({
|
||||
from: this.#from,
|
||||
to: options.to,
|
||||
reply_to: this.#replyTo,
|
||||
subject: options.subject,
|
||||
text: options.text,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
await this.#transport.sendPlainText({
|
||||
...options,
|
||||
from: this.#from,
|
||||
replyTo: this.#replyTo,
|
||||
});
|
||||
}
|
||||
|
||||
#getTemplate(data: DeliverEmail): {
|
||||
@@ -124,41 +127,4 @@ export class EmailClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #sendEmail({ to, subject, react }: { to: string; subject: string; react: ReactElement }) {
|
||||
if (this.#client) {
|
||||
const result = await this.#client.emails.send({
|
||||
from: this.#from,
|
||||
to,
|
||||
reply_to: this.#replyTo,
|
||||
subject,
|
||||
react,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
console.error(
|
||||
`Failed to send email to ${to}, ${subject}. Error ${result.error.name}: ${result.error.message}`
|
||||
);
|
||||
throw new EmailError(result.error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`
|
||||
##### sendEmail to ${to}, subject: ${subject}
|
||||
|
||||
${render(react, {
|
||||
plainText: true,
|
||||
})}
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
//EmailError type where you can set the name and message
|
||||
export class EmailError extends Error {
|
||||
constructor({ name, message }: { name: string; message: string }) {
|
||||
super(message);
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { render } from "@react-email/render";
|
||||
import { EmailError, MailMessage, MailTransport, PlainTextMailMessage } from "./index";
|
||||
import nodemailer from "nodemailer"
|
||||
import * as awsSes from "@aws-sdk/client-ses"
|
||||
|
||||
export type AwsSesMailTransportOptions = {
|
||||
type: 'aws-ses',
|
||||
}
|
||||
|
||||
export class AwsSesMailTransport implements MailTransport {
|
||||
#client: nodemailer.Transporter;
|
||||
|
||||
constructor(options: AwsSesMailTransportOptions) {
|
||||
const ses = new awsSes.SESClient()
|
||||
|
||||
this.#client = nodemailer.createTransport({
|
||||
SES: {
|
||||
aws: awsSes,
|
||||
ses
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async send({to, from, replyTo, subject, react}: MailMessage): Promise<void> {
|
||||
try {
|
||||
await this.#client.sendMail({
|
||||
from: from,
|
||||
to,
|
||||
replyTo: replyTo,
|
||||
subject,
|
||||
html: render(react),
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(
|
||||
`Failed to send email to ${to}, ${subject}. Error ${error.name}: ${error.message}`
|
||||
);
|
||||
throw new EmailError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async sendPlainText({to, from, replyTo, subject, text}: PlainTextMailMessage): Promise<void> {
|
||||
try {
|
||||
await this.#client.sendMail({
|
||||
from: from,
|
||||
to,
|
||||
replyTo: replyTo,
|
||||
subject,
|
||||
text: text,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(
|
||||
`Failed to send email to ${to}, ${subject}. Error ${error.name}: ${error.message}`
|
||||
);
|
||||
throw new EmailError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { ReactElement } from "react";
|
||||
import { AwsSesMailTransport, AwsSesMailTransportOptions } from "./aws-ses";
|
||||
import { NullMailTransport, NullMailTransportOptions } from "./null";
|
||||
import { ResendMailTransport, ResendMailTransportOptions } from "./resend";
|
||||
import { SmtpMailTransport, SmtpMailTransportOptions } from "./smtp";
|
||||
|
||||
export type MailMessage = {
|
||||
to: string;
|
||||
from: string;
|
||||
replyTo: string;
|
||||
subject: string;
|
||||
react: ReactElement;
|
||||
};
|
||||
|
||||
export type PlainTextMailMessage = {
|
||||
to: string;
|
||||
from: string;
|
||||
replyTo: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface MailTransport {
|
||||
send(message: MailMessage): Promise<void>;
|
||||
sendPlainText(message: PlainTextMailMessage): Promise<void>;
|
||||
}
|
||||
|
||||
export class EmailError extends Error {
|
||||
constructor({ name, message }: { name: string; message: string }) {
|
||||
super(message);
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
export type MailTransportOptions =
|
||||
AwsSesMailTransportOptions |
|
||||
ResendMailTransportOptions |
|
||||
NullMailTransportOptions |
|
||||
SmtpMailTransportOptions
|
||||
|
||||
export function constructMailTransport(options: MailTransportOptions): MailTransport {
|
||||
switch(options.type) {
|
||||
case "aws-ses":
|
||||
return new AwsSesMailTransport(options);
|
||||
case "resend":
|
||||
return new ResendMailTransport(options);
|
||||
case "smtp":
|
||||
return new SmtpMailTransport(options);
|
||||
case undefined:
|
||||
return new NullMailTransport(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { render } from "@react-email/render";
|
||||
import { MailMessage, MailTransport, PlainTextMailMessage } from "./index";
|
||||
|
||||
export type NullMailTransportOptions = {
|
||||
type: undefined,
|
||||
}
|
||||
|
||||
export class NullMailTransport implements MailTransport {
|
||||
constructor(options: NullMailTransportOptions) {
|
||||
}
|
||||
|
||||
async send({to, subject, react}: MailMessage): Promise<void> {
|
||||
console.log(`
|
||||
##### sendEmail to ${to}, subject: ${subject}
|
||||
|
||||
${render(react, {
|
||||
plainText: true,
|
||||
})}
|
||||
`);
|
||||
}
|
||||
|
||||
async sendPlainText({to, subject, text}: PlainTextMailMessage): Promise<void> {
|
||||
console.log(`
|
||||
##### sendEmail to ${to}, subject: ${subject}
|
||||
|
||||
${text}
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { EmailError, MailMessage, MailTransport, PlainTextMailMessage } from "./index";
|
||||
import { Resend } from "resend";
|
||||
|
||||
export type ResendMailTransportOptions = {
|
||||
type: 'resend',
|
||||
config: {
|
||||
apiKey?: string
|
||||
}
|
||||
}
|
||||
|
||||
export class ResendMailTransport implements MailTransport {
|
||||
#client: Resend;
|
||||
|
||||
constructor(options: ResendMailTransportOptions) {
|
||||
this.#client = new Resend(options.config.apiKey)
|
||||
}
|
||||
|
||||
async send({to, from, replyTo, subject, react}: MailMessage): Promise<void> {
|
||||
const result = await this.#client.emails.send({
|
||||
from: from,
|
||||
to,
|
||||
reply_to: replyTo,
|
||||
subject,
|
||||
react,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
console.error(
|
||||
`Failed to send email to ${to}, ${subject}. Error ${result.error.name}: ${result.error.message}`
|
||||
);
|
||||
throw new EmailError(result.error);
|
||||
}
|
||||
}
|
||||
|
||||
async sendPlainText({to, from, replyTo, subject, text}: PlainTextMailMessage): Promise<void> {
|
||||
const result = await this.#client.emails.send({
|
||||
from: from,
|
||||
to,
|
||||
reply_to: replyTo,
|
||||
subject,
|
||||
text,
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
console.error(
|
||||
`Failed to send email to ${to}, ${subject}. Error ${result.error.name}: ${result.error.message}`
|
||||
);
|
||||
throw new EmailError(result.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { render } from "@react-email/render";
|
||||
import nodemailer from "nodemailer";
|
||||
import { EmailError, MailMessage, MailTransport, PlainTextMailMessage } from "./index";
|
||||
|
||||
export type SmtpMailTransportOptions = {
|
||||
type: "smtp";
|
||||
config: {
|
||||
host?: string;
|
||||
port?: number;
|
||||
secure?: boolean;
|
||||
auth?: {
|
||||
user?: string;
|
||||
pass?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export class SmtpMailTransport implements MailTransport {
|
||||
#client: nodemailer.Transporter;
|
||||
|
||||
constructor(options: SmtpMailTransportOptions) {
|
||||
this.#client = nodemailer.createTransport(options.config);
|
||||
}
|
||||
|
||||
async send({ to, from, replyTo, subject, react }: MailMessage): Promise<void> {
|
||||
try {
|
||||
await this.#client.sendMail({
|
||||
from: from,
|
||||
to,
|
||||
replyTo: replyTo,
|
||||
subject,
|
||||
html: render(react),
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(
|
||||
`Failed to send email to ${to}, ${subject}. Error ${error.name}: ${error.message}`
|
||||
);
|
||||
throw new EmailError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async sendPlainText({ to, from, replyTo, subject, text }: PlainTextMailMessage): Promise<void> {
|
||||
try {
|
||||
await this.#client.sendMail({
|
||||
from: from,
|
||||
to,
|
||||
replyTo: replyTo,
|
||||
subject,
|
||||
text: text,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(
|
||||
`Failed to send email to ${to}, ${subject}. Error ${error.name}: ${error.message}`
|
||||
);
|
||||
throw new EmailError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+880
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user