From b269467a512eff6cfc0cb3fca32424c91c18da23 Mon Sep 17 00:00:00 2001 From: nac62116 <56918308+nac62116@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:30:46 +0100 Subject: [PATCH] feat: welcome email after registration (#1268) * 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 --- app/entry.server.tsx | 10 +- app/mailer.server.ts | 26 ++-- app/routes/auth/keycloak.callback.tsx | 6 +- app/routes/auth/verify.tsx | 6 +- .../waiting-list/move-to-participants.tsx | 29 +--- app/routes/register/utils.server.ts | 32 ++++ mail-templates/move-to-participants/html.hbs | 26 ++-- mail-templates/welcome/html.hbs | 138 ++++++++++++++++++ mail-templates/welcome/text.hbs | 25 ++++ 9 files changed, 245 insertions(+), 53 deletions(-) create mode 100644 mail-templates/welcome/html.hbs create mode 100644 mail-templates/welcome/text.hbs diff --git a/app/entry.server.tsx b/app/entry.server.tsx index b4566ad0d..14be012a2 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -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; @@ -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"); } @@ -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"); } diff --git a/app/mailer.server.ts b/app/mailer.server.ts index 8a28f7723..56419affa 100644 --- a/app/mailer.server.ts +++ b/app/mailer.server.ts @@ -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"; @@ -80,15 +87,18 @@ type TemplateContent = 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( +export function getCompiledMailTemplate( templatePath: TemplatePath, content: TemplateContent, - baseUrl: string, type: "text" | "html" = "html" ) { - const bodyTemplateSource = await fs.readFileSync(templatePath, { + const bodyTemplateSource = fs.readFileSync(templatePath, { encoding: "utf8", }); const bodyTemplate = Handlebars.compile(bodyTemplateSource, {}); @@ -97,12 +107,10 @@ export async function getCompiledMailTemplate( 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 }); diff --git a/app/routes/auth/keycloak.callback.tsx b/app/routes/auth/keycloak.callback.tsx index 59795838c..0d14f8f57 100644 --- a/app/routes/auth/keycloak.callback.tsx +++ b/app/routes/auth/keycloak.callback.tsx @@ -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; @@ -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, }); diff --git a/app/routes/auth/verify.tsx b/app/routes/auth/verify.tsx index 7f15a9dce..f29f30140 100644 --- a/app/routes/auth/verify.tsx +++ b/app/routes/auth/verify.tsx @@ -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); @@ -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, }); diff --git a/app/routes/event/$slug/settings/waiting-list/move-to-participants.tsx b/app/routes/event/$slug/settings/waiting-list/move-to-participants.tsx index 480a9400a..6eca35491 100644 --- a/app/routes/event/$slug/settings/waiting-list/move-to-participants.tsx +++ b/app/routes/event/$slug/settings/waiting-list/move-to-participants.tsx @@ -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"); @@ -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()}`, @@ -110,17 +91,15 @@ export const action = async (args: ActionFunctionArgs) => { }, }; const textTemplatePath = "mail-templates/move-to-participants/text.hbs"; - const text = await getCompiledMailTemplate( + const text = getCompiledMailTemplate( textTemplatePath, content, - baseUrl, "text" ); const htmlTemplatePath = "mail-templates/move-to-participants/html.hbs"; - const html = await getCompiledMailTemplate( + const html = getCompiledMailTemplate( htmlTemplatePath, content, - baseUrl, "html" ); diff --git a/app/routes/register/utils.server.ts b/app/routes/register/utils.server.ts index 2678d9660..45dff9e0f 100644 --- a/app/routes/register/utils.server.ts +++ b/app/routes/register/utils.server.ts @@ -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) { @@ -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: {}, @@ -36,3 +45,26 @@ export async function createProfile(user: User) { } return null; } + +export async function sendWelcomeMail( + profile: Pick +) { + // 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( + textTemplatePath, + profile, + "text" + ); + const html = getCompiledMailTemplate( + htmlTemplatePath, + profile, + "html" + ); + await mailer(mailerOptions, sender, recipient, subject, text, html); +} diff --git a/mail-templates/move-to-participants/html.hbs b/mail-templates/move-to-participants/html.hbs index f06a17e9e..9901a1ee4 100644 --- a/mail-templates/move-to-participants/html.hbs +++ b/mail-templates/move-to-participants/html.hbs @@ -13,7 +13,8 @@ display: inline-block; vertical-align: top; width: 100%;" class="mj-column-per-100 mj-outlook-group-fix"> - +
{{!-- @@ -115,9 +122,4 @@
Hallo {{recipient.firstName}} {{recipient.lastName}},

- Du hast Dich für die Veranstaltung {{event.name}} am {{event.startDate}} um {{event.startTime}} Uhr auf die Warteliste setzen lassen. - Nun ist ein Platz frei geworden und wir konnten Dich von der Warteliste auf die Teilnehmendenliste setzen.

+ Du hast Dich für die Veranstaltung {{event.name}} am {{event.startDate}} um + {{event.startTime}} Uhr auf die Warteliste setzen lassen. + Nun ist ein Platz frei geworden und wir konnten Dich von der Warteliste auf die Teilnehmendenliste + setzen.

Hat sich bei Dir in der Zwischenzeit etwas geändert und Du möchtest nicht mehr teilnehmen, dann besuche die Veranstaltungsseite 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.

+ Da dieser Workshop eine Begrenzung in der Teilnehmendenzahl hat, können wir Deinen Platz an + Personen auf der Warteliste freigeben.

- 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 {{event.supportContact.firstName}} {{event.supportContact.lastName}}. + Sollten Fragen bestehen, melde Dich gerne bei {{event.supportContact.firstName}} + {{event.supportContact.lastName}}.
- - - - - - + \ No newline at end of file diff --git a/mail-templates/welcome/html.hbs b/mail-templates/welcome/html.hbs new file mode 100644 index 000000000..7da0700f2 --- /dev/null +++ b/mail-templates/welcome/html.hbs @@ -0,0 +1,138 @@ +
+ + + + + + +
+ +
+ + + {{!-- + + --}} + + + + {{!-- + + --}} + + + + +
+
+

+ {{headline}} +

+
+
+
+ Hallo {{firstName}},

+ + herzlich willkommen auf der MINTvernetzt Community-Plattform. Wir freuen uns, dass Du da + bist.

+ + Hast du Dein Profil schon ausgefüllt? Je weiter Dein Profil ausgefüllt ist, desto + besser können andere MINT-Akteur:innen Dich finden. + Deine Eingaben sind zudem die Basis für eins unserer nächsten Features: das Matching. + Hiermit wollen wir Dir zukünftig basierend auf Deinen Interessen passende andere MINT-Akteur:innen + und spannende Projekte, Events und Organisationen vorschlagen.

+ + Du kannst zudem Deine eigene Organisation anlegen und Deine Kolleg:innen als Teammitglieder + hinzufügen. + Wurde Deine Organisation schon angelegt, dann sprich eine Person in Deinem Team an, Dich der + Organisation auf unserer Plattform hinzuzufügen.

+ + Unter Veranstaltungen findest Du alle anstehenden Events von MINTvernetzt und MINT-Campus. + Wir freuen uns, Dich bei einer unserer nächsten Veranstaltungen begrüßen zu dürfen.

+ + Über Updates auf der Plattform halten wir Dich per E-Mail auf dem Laufenden. + Hast du Feedback an uns oder brauchst du Hilfe, dann melde Dich gerne unter support@mint-vernetzt.de bei uns.

+ + Wir freuen uns, Dich bald wieder auf der Plattform zu sehen.

+ + Last but not least: Kennst du schon den MINTvernetzt Newsletter? + Einmal im Monat informieren wir Dich in unserem MINTvernetzt-Newsletter zu Neuigkeiten, + Befragungen, spannenden Events, lesenswerten Artikeln und Vielem mehr! + Hier kannst Du Dich anmelden: MINTvernetzt informiert | + MINTvernetzt (mint-vernetzt.de). +
+
+ + + + + + +
+ + {{buttonText}} + +
+
+
+ Herzliche Grüße

Dein MINTvernetzt-Team +
+
+
+ +
+
\ No newline at end of file diff --git a/mail-templates/welcome/text.hbs b/mail-templates/welcome/text.hbs new file mode 100644 index 000000000..9f9908b9f --- /dev/null +++ b/mail-templates/welcome/text.hbs @@ -0,0 +1,25 @@ +Hallo {{firstName}}, + +herzlich willkommen auf der MINTvernetzt Community-Plattform. Wir freuen uns, dass Du da bist. + +Hast du Dein Profil schon ausgefüllt? Je weiter Dein Profil ausgefüllt ist, desto besser können andere MINT-Akteur:innen Dich finden. +Deine Eingaben sind zudem die Basis für eins unserer nächsten Features: das Matching. +Hiermit wollen wir Dir zukünftig basierend auf Deinen Interessen passende andere MINT-Akteur:innen und spannende Projekte, Events und Organisationen vorschlagen. + +Du kannst zudem Deine eigene Organisation anlegen und Deine Kolleg:innen als Teammitglieder hinzufügen. +Wurde Deine Organisation schon angelegt, dann sprich eine Person in Deinem Team an, Dich der Organisation auf unserer Plattform hinzuzufügen. + +Unter Veranstaltungen findest Du alle anstehenden Events von MINTvernetzt und MINT-Campus. +Wir freuen uns, Dich bei einer unserer nächsten Veranstaltungen begrüßen zu dürfen. + +Über Updates auf der Plattform halten wir Dich per E-Mail auf dem Laufenden. +Hast du Feedback an uns oder brauchst du Hilfe, dann melde Dich gerne unter support@mint-vernetzt.de bei uns. + +Wir freuen uns, Dich bald wieder auf der Plattform zu sehen. + +Last but not least: Kennst du schon den MINTvernetzt Newsletter? +Einmal im Monat informieren wir Dich in unserem MINTvernetzt-Newsletter zu Neuigkeiten, Befragungen, spannenden Events, lesenswerten Artikeln und Vielem mehr! +Hier kannst Du Dich anmelden: https://mint-vernetzt.de/mintvernetzt-informiert/#newsletter. + +Herzliche Grüße +Dein MINTvernetzt-Team \ No newline at end of file