mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-17 08:17:26 +02:00
✨ Use message queue for emails (#1446)
This commit is contained in:
parent
673fc79801
commit
a452e5b764
12 changed files with 150 additions and 35 deletions
|
@ -1,5 +1,5 @@
|
|||
import { previewEmailContext } from "../components/email-context";
|
||||
import NewParticipantConfirmationEmail from "../templates/new-participant-confirmation";
|
||||
import { NewParticipantConfirmationEmail } from "../templates/new-participant-confirmation";
|
||||
|
||||
export default function NewParticipantConfirmationPreview() {
|
||||
return (
|
||||
|
|
9
packages/emails/src/queue.ts
Normal file
9
packages/emails/src/queue.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { Client } from "@upstash/qstash";
|
||||
|
||||
export function createQstashClient() {
|
||||
if (!process.env.QSTASH_TOKEN) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Client({ token: process.env.QSTASH_TOKEN! });
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import * as aws from "@aws-sdk/client-ses";
|
||||
import { defaultProvider } from "@aws-sdk/credential-provider-node";
|
||||
import { absoluteUrl } from "@rallly/utils/absolute-url";
|
||||
import { renderAsync } from "@react-email/render";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { waitUntil } from "@vercel/functions";
|
||||
import type { Transporter } from "nodemailer";
|
||||
import { createTransport } from "nodemailer";
|
||||
|
@ -8,20 +10,9 @@ import type Mail from "nodemailer/lib/mailer";
|
|||
import React from "react";
|
||||
|
||||
import { i18nDefaultConfig, i18nInstance } from "./i18n";
|
||||
import * as templates from "./templates";
|
||||
import type { EmailContext } from "./types";
|
||||
|
||||
type Templates = typeof templates;
|
||||
|
||||
type TemplateName = keyof typeof templates;
|
||||
|
||||
type TemplateProps<T extends TemplateName> = Omit<
|
||||
React.ComponentProps<TemplateComponent<T>>,
|
||||
"ctx"
|
||||
>;
|
||||
type TemplateComponent<T extends TemplateName> = Templates[T] & {
|
||||
getSubject?: (props: TemplateProps<T>, ctx: EmailContext) => string;
|
||||
};
|
||||
import { createQstashClient } from "./queue";
|
||||
import { templates } from "./templates";
|
||||
import type { TemplateComponent, TemplateName, TemplateProps } from "./types";
|
||||
|
||||
type SendEmailOptions<T extends TemplateName> = {
|
||||
to: string;
|
||||
|
@ -81,11 +72,30 @@ export class EmailClient {
|
|||
templateName: T,
|
||||
options: SendEmailOptions<T>,
|
||||
) {
|
||||
return waitUntil(
|
||||
(async () => {
|
||||
const createEmailJob = async () => {
|
||||
const client = createQstashClient();
|
||||
|
||||
if (client) {
|
||||
const queue = client.queue({
|
||||
queueName: "emails",
|
||||
});
|
||||
|
||||
queue
|
||||
.enqueueJSON({
|
||||
url: absoluteUrl("/api/send-email"),
|
||||
body: { templateName, options },
|
||||
})
|
||||
.catch(() => {
|
||||
Sentry.captureException(new Error("Failed to queue email"));
|
||||
// If there's an error queuing the email, send it immediately
|
||||
this.sendTemplate(templateName, options);
|
||||
});
|
||||
} else {
|
||||
this.sendTemplate(templateName, options);
|
||||
})(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
waitUntil(createEmailJob());
|
||||
}
|
||||
|
||||
async sendTemplate<T extends TemplateName>(
|
||||
|
|
|
@ -1,8 +1,26 @@
|
|||
export * from "./templates/finalized-host";
|
||||
export * from "./templates/finalized-participant";
|
||||
export * from "./templates/login";
|
||||
export * from "./templates/new-comment";
|
||||
export * from "./templates/new-participant";
|
||||
export * from "./templates/new-participant-confirmation";
|
||||
export * from "./templates/new-poll";
|
||||
export * from "./templates/register";
|
||||
import { FinalizeHostEmail } from "./templates/finalized-host";
|
||||
import { FinalizeParticipantEmail } from "./templates/finalized-participant";
|
||||
import { LoginEmail } from "./templates/login";
|
||||
import { NewCommentEmail } from "./templates/new-comment";
|
||||
import { NewParticipantEmail } from "./templates/new-participant";
|
||||
import { NewParticipantConfirmationEmail } from "./templates/new-participant-confirmation";
|
||||
import { NewPollEmail } from "./templates/new-poll";
|
||||
import { RegisterEmail } from "./templates/register";
|
||||
import type { TemplateName } from "./types";
|
||||
|
||||
const templates = {
|
||||
FinalizeHostEmail,
|
||||
FinalizeParticipantEmail,
|
||||
LoginEmail,
|
||||
NewCommentEmail,
|
||||
NewParticipantEmail,
|
||||
NewParticipantConfirmationEmail,
|
||||
NewPollEmail,
|
||||
RegisterEmail,
|
||||
};
|
||||
|
||||
export const emailTemplates = Object.keys(templates) as TemplateName[];
|
||||
|
||||
export type EmailTemplates = typeof templates;
|
||||
|
||||
export { templates };
|
||||
|
|
|
@ -15,7 +15,8 @@ interface NewParticipantConfirmationEmailProps {
|
|||
editSubmissionUrl: string;
|
||||
ctx: EmailContext;
|
||||
}
|
||||
export const NewParticipantConfirmationEmail = ({
|
||||
|
||||
const NewParticipantConfirmationEmail = ({
|
||||
title,
|
||||
editSubmissionUrl,
|
||||
ctx,
|
||||
|
@ -96,4 +97,4 @@ NewParticipantConfirmationEmail.getSubject = (
|
|||
});
|
||||
};
|
||||
|
||||
export default NewParticipantConfirmationEmail;
|
||||
export { NewParticipantConfirmationEmail };
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { TFunction } from "i18next";
|
||||
|
||||
import type { I18nInstance } from "./i18n";
|
||||
import type { EmailTemplates } from "./templates";
|
||||
|
||||
export type EmailContext = {
|
||||
logoUrl: string;
|
||||
|
@ -10,3 +11,14 @@ export type EmailContext = {
|
|||
i18n: I18nInstance;
|
||||
t: TFunction;
|
||||
};
|
||||
|
||||
export type TemplateName = keyof EmailTemplates;
|
||||
|
||||
export type TemplateProps<T extends TemplateName> = Omit<
|
||||
React.ComponentProps<EmailTemplates[T]>,
|
||||
"ctx"
|
||||
>;
|
||||
|
||||
export type TemplateComponent<T extends TemplateName> = EmailTemplates[T] & {
|
||||
getSubject?: (props: TemplateProps<T>, ctx: EmailContext) => string;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue