Skip to content

Commit

Permalink
feat: welcome email after registration (#1268)
Browse files Browse the repository at this point in the history
* Remove API_KEY env check from entry.client.tsx as its not an env variable for this app context
* Add SYSTEM_MAIL_SENDER env variable to env check in entry.server.tsx
* Refactor mailer functions and templates and send welcome email on verified registration
* catch welcome email error silently and capture with sentry
  • Loading branch information
nac62116 authored Feb 13, 2024
1 parent f3fdda5 commit b269467
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 53 deletions.
10 changes: 5 additions & 5 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ declare global {
SERVICE_ROLE_KEY: string;
MATOMO_URL: string;
MATOMO_SITE_ID: string;
API_KEY: string;
MAILER_HOST: string;
MAILER_PORT: string;
MAILER_USER: string;
MAILER_PASS: string;
SYSTEM_MAIL_SENDER: string;
SUBMISSION_SENDER: string;
NEWSSUBMISSION_RECIPIENT: string;
NEWSSUBMISSION_SUBJECT: string;
Expand Down Expand Up @@ -100,10 +100,6 @@ if (process.env.MATOMO_SITE_ID === undefined) {
throw new Error("'MATOMO_SITE_ID' is required");
}

if (process.env.API_KEY === undefined) {
throw new Error("'API_KEY' is required");
}

if (process.env.MAILER_HOST === undefined) {
throw new Error("'MAILER_HOST' is required");
}
Expand All @@ -120,6 +116,10 @@ if (process.env.MAILER_PASS === undefined) {
throw new Error("'MAILER_PASS' is required");
}

if (process.env.SYSTEM_MAIL_SENDER === undefined) {
throw new Error("'SYSTEM_MAIL_SENDER' is required");
}

if (process.env.SUBMISSION_SENDER === undefined) {
throw new Error("'SUBMISSION_SENDER' is required");
}
Expand Down
26 changes: 17 additions & 9 deletions app/mailer.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,16 @@ type MoveToParticipantsContent = {
};
};

type WelcomeContent = {
firstName: string;
email: string;
};

type TemplatePath =
| "mail-templates/standard-message/html.hbs"
| "mail-templates/standard-message/text.hbs"
| "mail-templates/welcome/html.hbs"
| "mail-templates/welcome/text.hbs"
| "mail-templates/move-to-participants/html.hbs"
| "mail-templates/move-to-participants/text.hbs";

Expand All @@ -80,15 +87,18 @@ type TemplateContent<TemplatePath> = TemplatePath extends
| "mail-templates/move-to-participants/html.hbs"
| "mail-templates/move-to-participants/text.hbs"
? MoveToParticipantsContent
: TemplatePath extends
| "mail-templates/welcome/html.hbs"
| "mail-templates/welcome/text.hbs"
? WelcomeContent
: never;

export async function getCompiledMailTemplate<T extends TemplatePath>(
export function getCompiledMailTemplate<T extends TemplatePath>(
templatePath: TemplatePath,
content: TemplateContent<T>,
baseUrl: string,
type: "text" | "html" = "html"
) {
const bodyTemplateSource = await fs.readFileSync(templatePath, {
const bodyTemplateSource = fs.readFileSync(templatePath, {
encoding: "utf8",
});
const bodyTemplate = Handlebars.compile(bodyTemplateSource, {});
Expand All @@ -97,12 +107,10 @@ export async function getCompiledMailTemplate<T extends TemplatePath>(
return body;
}
// On html templates we have header and footer (see mail-templates/layout.hbs)
const layoutTemplateSource = await fs.readFileSync(
"mail-templates/layout.hbs",
{
encoding: "utf8",
}
);
const baseUrl = process.env.COMMUNITY_BASE_URL;
const layoutTemplateSource = fs.readFileSync("mail-templates/layout.hbs", {
encoding: "utf8",
});
const layoutTemplate = Handlebars.compile(layoutTemplateSource, {});
Handlebars.registerPartial("body", body);
const compiledHtml = layoutTemplate({ baseUrl });
Expand Down
6 changes: 5 additions & 1 deletion app/routes/auth/keycloak.callback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { redirect, type LoaderFunctionArgs } from "@remix-run/node";
import { createAuthClient, getSession } from "~/auth.server";
import { invariantResponse } from "~/lib/utils/response";
import { prismaClient } from "~/prisma.server";
import { createProfile } from "../register/utils.server";
import { createProfile, sendWelcomeMail } from "../register/utils.server";
import * as Sentry from "@sentry/remix";

export const loader = async (args: LoaderFunctionArgs) => {
const { request } = args;
Expand Down Expand Up @@ -36,6 +37,9 @@ export const loader = async (args: LoaderFunctionArgs) => {
"Did not provide necessary user meta data to create a corresponding profile after sign up.",
{ status: 400 }
);
sendWelcomeMail(profile).catch((error) => {
Sentry.captureException(error);
});
return redirect(loginRedirect || `/profile/${profile.username}`, {
headers,
});
Expand Down
6 changes: 5 additions & 1 deletion app/routes/auth/verify.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { redirect, type LoaderFunctionArgs } from "@remix-run/node";
import { type EmailOtpType } from "@supabase/supabase-js";
import { createAuthClient } from "~/auth.server";
import { createProfile } from "../register/utils.server";
import { createProfile, sendWelcomeMail } from "../register/utils.server";
import { updateProfileEmailByUserId } from "./verify.server";
import { invariantResponse } from "~/lib/utils/response";
import * as Sentry from "@sentry/remix";

export async function loader({ request }: LoaderFunctionArgs) {
const requestUrl = new URL(request.url);
Expand Down Expand Up @@ -40,6 +41,9 @@ export async function loader({ request }: LoaderFunctionArgs) {
"Did not provide necessary user meta data to create a corresponding profile after sign up.",
{ status: 400 }
);
sendWelcomeMail(profile).catch((error) => {
Sentry.captureException(error);
});
return redirect(loginRedirect || `/profile/${profile.username}`, {
headers,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,27 +57,8 @@ export const action = async (args: ActionFunctionArgs) => {
await connectParticipantToEvent(event.id, result.data.profileId);
await disconnectFromWaitingListOfEvent(event.id, result.data.profileId);
// Send info mail
const sender = process.env.SYSTEM_MAIL_SENDER;
if (sender === undefined) {
console.error(t("error.env.sender"));
throw json(
{
message: t("error.env.sender"),
},
{ status: 500 }
);
}
const baseUrl = process.env.COMMUNITY_BASE_URL;
if (baseUrl === undefined) {
console.error(t("error.env.url"));
throw json(
{
message: t("error.env.url"),
},
{ status: 500 }
);
}
// -> mail of person which was moved to waitinglist
const sender = process.env.SYSTEM_MAIL_SENDER;
const recipient = profile.email;
const subject = t("email.subject", { title: event.name });
const startTime = utcToZonedTime(event.startTime, "Europe/Berlin");
Expand All @@ -88,7 +69,7 @@ export const action = async (args: ActionFunctionArgs) => {
},
event: {
name: event.name,
url: `${baseUrl}/event/${event.slug}`,
url: `${process.env.COMMUNITY_BASE_URL}/event/${event.slug}`,
startDate: `${startTime.getDate()}.${
startTime.getMonth() + 1
}.${startTime.getFullYear()}`,
Expand All @@ -110,17 +91,15 @@ export const action = async (args: ActionFunctionArgs) => {
},
};
const textTemplatePath = "mail-templates/move-to-participants/text.hbs";
const text = await getCompiledMailTemplate<typeof textTemplatePath>(
const text = getCompiledMailTemplate<typeof textTemplatePath>(
textTemplatePath,
content,
baseUrl,
"text"
);
const htmlTemplatePath = "mail-templates/move-to-participants/html.hbs";
const html = await getCompiledMailTemplate<typeof htmlTemplatePath>(
const html = getCompiledMailTemplate<typeof htmlTemplatePath>(
htmlTemplatePath,
content,
baseUrl,
"html"
);

Expand Down
32 changes: 32 additions & 0 deletions app/routes/register/utils.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { type Profile } from "@prisma/client";
import type { User } from "@supabase/supabase-js";
import { mailerOptions } from "~/lib/submissions/mailer/mailerOptions";
import { getCompiledMailTemplate, mailer } from "~/mailer.server";
import { prismaClient } from "~/prisma.server";

export async function createProfile(user: User) {
Expand All @@ -22,6 +25,12 @@ export async function createProfile(user: User) {
};
// Creates the profile and its corrsponding profileVisibility with default values defined in prisma.schema
const profile = await prismaClient.profile.create({
select: {
id: true,
username: true,
firstName: true,
email: true,
},
data: {
profileVisibility: {
create: {},
Expand All @@ -36,3 +45,26 @@ export async function createProfile(user: User) {
}
return null;
}

export async function sendWelcomeMail(
profile: Pick<Profile, "firstName" | "email">
) {
// TODO: i18n
const subject = "Willkommen auf der MINTvernetzt Community-Plattform";
const sender = process.env.SYSTEM_MAIL_SENDER;
const recipient = profile.email;
const textTemplatePath = "mail-templates/welcome/text.hbs";
const htmlTemplatePath = "mail-templates/welcome/html.hbs";

const text = getCompiledMailTemplate<typeof textTemplatePath>(
textTemplatePath,
profile,
"text"
);
const html = getCompiledMailTemplate<typeof htmlTemplatePath>(
htmlTemplatePath,
profile,
"html"
);
await mailer(mailerOptions, sender, recipient, subject, text, html);
}
26 changes: 14 additions & 12 deletions mail-templates/move-to-participants/html.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
display: inline-block;
vertical-align: top;
width: 100%;" class="mj-column-per-100 mj-outlook-group-fix">
<table width="100%" style="vertical-align: top;" role="presentation" cellspacing="0" cellpadding="0" border="0">
<table width="100%" style="vertical-align: top;" role="presentation" cellspacing="0" cellpadding="0"
border="0">
<tbody>
{{!-- <tr>
<td style="font-size: 0px;
Expand Down Expand Up @@ -45,17 +46,23 @@
text-align: left;
color: #333333;">
Hallo {{recipient.firstName}} {{recipient.lastName}},<br><br>
Du hast Dich für die Veranstaltung <b>{{event.name}}</b> am <b>{{event.startDate}}</b> um <b>{{event.startTime}} Uhr</b> auf die Warteliste setzen lassen.
Nun ist ein Platz frei geworden und wir konnten Dich von der Warteliste auf die Teilnehmendenliste setzen.<br><br>
Du hast Dich für die Veranstaltung <b>{{event.name}}</b> am <b>{{event.startDate}}</b> um
<b>{{event.startTime}} Uhr</b> auf die Warteliste setzen lassen.
Nun ist ein Platz frei geworden und wir konnten Dich von der Warteliste auf die Teilnehmendenliste
setzen.<br><br>

Hat sich bei Dir in der Zwischenzeit etwas geändert und Du möchtest nicht mehr teilnehmen,
dann besuche die <a href="{{event.url}}">Veranstaltungsseite</a> und trage Dich bitte wieder aus.
Da dieser Workshop eine Begrenzung in der Teilnehmendenzahl hat, können wir Deinen Platz an Personen auf der Warteliste freigeben.<br><br>
Da dieser Workshop eine Begrenzung in der Teilnehmendenzahl hat, können wir Deinen Platz an
Personen auf der Warteliste freigeben.<br><br>

Unter dem obigen Event-Link findest Du Detailinformationen wie Du Dich in die digitale Veranstaltung einwählen kannst
Unter dem obigen Event-Link findest Du Detailinformationen wie Du Dich in die digitale
Veranstaltung einwählen kannst
bzw. Details zum Veranstaltungsort bei Formaten in Präsenz.

Sollten Fragen bestehen, melde Dich gerne bei <a href="mailto:{{event.supportContact.email}}">{{event.supportContact.firstName}} {{event.supportContact.lastName}}.
Sollten Fragen bestehen, melde Dich gerne bei <a
href="mailto:{{event.supportContact.email}}">{{event.supportContact.firstName}}
{{event.supportContact.lastName}}</a>.
</div>
</td>
</tr>
Expand Down Expand Up @@ -115,9 +122,4 @@
</tr>
</tbody>
</table>
</div>





</div>
Loading

0 comments on commit b269467

Please sign in to comment.