From 8ab67683cf9ddd6073e818225dcdf96584a99809 Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Thu, 16 Mar 2023 16:04:54 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Add=20support=20for=20usin?= =?UTF-8?q?g=20SES=20API=20(#573)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/declarations/environment.d.ts | 16 + .../components/forms/user-details-form.tsx | 6 +- packages/emails/package.json | 2 + packages/emails/src/send-email.tsx | 59 +- turbo.json | 6 +- yarn.lock | 739 +++++++++++++++++- 6 files changed, 810 insertions(+), 18 deletions(-) diff --git a/apps/web/declarations/environment.d.ts b/apps/web/declarations/environment.d.ts index 99acfe476..c81634c42 100644 --- a/apps/web/declarations/environment.d.ts +++ b/apps/web/declarations/environment.d.ts @@ -68,6 +68,22 @@ declare global { * "true" to require authentication for creating new polls and accessing admin pages */ AUTH_REQUIRED?: string; + /** + * Determines what email provider to use. "smtp" or "ses" + */ + EMAIL_PROVIDER?: "smtp" | "ses"; + /** + * AWS access key ID + */ + AWS_ACCESS_KEY_ID?: string; + /** + * AWS secret access key + */ + AWS_SECRET_ACCESS_KEY?: string; + /** + * AWS region + */ + AWS_REGION?: string; } } } diff --git a/apps/web/src/components/forms/user-details-form.tsx b/apps/web/src/components/forms/user-details-form.tsx index 19ddbb5f8..8b9ba8b4d 100644 --- a/apps/web/src/components/forms/user-details-form.tsx +++ b/apps/web/src/components/forms/user-details-form.tsx @@ -19,9 +19,11 @@ export const UserDetailsForm: React.FunctionComponent< handleSubmit, register, watch, - formState: { errors }, + formState: { errors, isSubmitting, isSubmitSuccessful }, } = useForm({ defaultValues }); + const isWorking = isSubmitting || isSubmitSuccessful; + React.useEffect(() => { if (onChange) { const subscription = watch(onChange); @@ -44,6 +46,7 @@ export const UserDetailsForm: React.FunctionComponent< className={clsx("input w-full", { "input-error": errors.name, })} + disabled={isWorking} placeholder={t("namePlaceholder")} {...register("name", { validate: requiredString })} /> @@ -58,6 +61,7 @@ export const UserDetailsForm: React.FunctionComponent< className={clsx("input w-full", { "input-error": errors.contact, })} + disabled={isWorking} placeholder={t("emailPlaceholder")} {...register("contact", { validate: validEmail, diff --git a/packages/emails/package.json b/packages/emails/package.json index 78875295d..ee1db4f43 100644 --- a/packages/emails/package.json +++ b/packages/emails/package.json @@ -8,6 +8,8 @@ "main": "./src/index.tsx", "types": "./src/index.tsx", "dependencies": { + "@aws-sdk/client-ses": "^3.292.0", + "@aws-sdk/credential-provider-node": "^3.292.0", "@react-email/components": "0.0.2", "@react-email/render": "0.0.6", "@react-email/tailwind": "0.0.6", diff --git a/packages/emails/src/send-email.tsx b/packages/emails/src/send-email.tsx index eb409539a..b6b079d4e 100644 --- a/packages/emails/src/send-email.tsx +++ b/packages/emails/src/send-email.tsx @@ -1,3 +1,5 @@ +import * as aws from "@aws-sdk/client-ses"; +import { defaultProvider } from "@aws-sdk/credential-provider-node"; import { render } from "@react-email/render"; import { createTransport, Transporter } from "nodemailer"; import React from "react"; @@ -19,22 +21,47 @@ const env = process.env["NODE" + "_ENV"] || "development"; let transport: Transporter; const getTransport = () => { + if (transport) { + // Reuse the transport if it exists + return transport; + } + if (env === "test") { transport = createTransport({ port: 4025 }); - } else { - const hasAuth = process.env.SMTP_USER || process.env.SMTP_PWD; - transport = createTransport({ - host: process.env.SMTP_HOST, - port: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT) : undefined, - secure: process.env.SMTP_SECURE === "true", - auth: hasAuth - ? { - user: process.env.SMTP_USER, - pass: process.env.SMTP_PWD, - } - : undefined, - }); + return transport; } + + switch (process.env.EMAIL_PROVIDER) { + case "ses": + { + const ses = new aws.SES({ + region: process.env["AWS" + "_REGION"], + credentialDefaultProvider: defaultProvider, + }); + + transport = createTransport({ + SES: { ses, aws }, + }); + } + break; + default: { + const hasAuth = process.env.SMTP_USER || process.env.SMTP_PWD; + transport = createTransport({ + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT + ? parseInt(process.env.SMTP_PORT) + : undefined, + secure: process.env.SMTP_SECURE === "true", + auth: hasAuth + ? { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PWD, + } + : undefined, + }); + } + } + return transport; }; @@ -53,11 +80,12 @@ export const sendEmail = async ( console.info("SUPPORT_EMAIL not configured - skipping email send"); return; } + const transport = getTransport(); const Template = templates[templateName] as TemplateComponent; try { - return await transport.sendMail({ + await transport.sendMail({ from: { name: "Rallly", address: process.env.SUPPORT_EMAIL, @@ -67,8 +95,9 @@ export const sendEmail = async ( // eslint-disable-next-line @typescript-eslint/no-explicit-any html: render(