mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-10-01 21:24:51 +02:00
Added used email types to new email system
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# Email System Documentation
|
||||
|
||||
> This doc is partially outdated. New templates can be viewed locally. See routes dir. Example: http://localhost:5173/local-only/e-mail-templates/user-invite Text versions are generated automatically and will be printed to console, when local page is opened.
|
||||
|
||||
The Open Reception appointment booking platform includes a comprehensive email system for automated communications with clients and staff members. This document provides technical details and administration guidance for managing the email system.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -917,6 +917,37 @@
|
||||
"introduction": "Ihr Termin wurde gebucht.",
|
||||
"action": "Termin absagen",
|
||||
"reason": "Sie erhalten diese E-Mail, weil jemand mit Ihrer E-Mail Adresse einen Termin bei gebucht hat."
|
||||
},
|
||||
"appointmentReminder": {
|
||||
"subject": "Erinnerung: Ihr Termin bei {tenant}",
|
||||
"introduction": "Ihr Termin wurde bei {tenant} steht in Kürze bevor.",
|
||||
"action": "Termin absagen",
|
||||
"reason": "Sie erhalten diese E-Mail, weil jemand mit Ihrer E-Mail Adresse einen Termin bei gebucht hat."
|
||||
},
|
||||
"appointmentRequest": {
|
||||
"subject": "{channel} bei {tenant} angefragt",
|
||||
"introduction": "Ihr Termin wurde angefragt. Sie werden benachrichtigt, sobald der Termin bestätigt wurde.",
|
||||
"reason": "Sie erhalten diese E-Mail, weil jemand mit Ihrer E-Mail Adresse einen Termin bei angefragt hat."
|
||||
},
|
||||
"confirmation": {
|
||||
"subject": "E-Mail Adresse bestätigen",
|
||||
"introduction": "willkommen bei unserem Terminbuchungsportal. Bitte bestätige Deine E-Mail Adresse.",
|
||||
"action": "E-Mail Adresse bestätigen",
|
||||
"hint": "Dieser Link ist nur {expirationMinutes} Minuten gültig und kann nur einmal verwendet werden.",
|
||||
"reason": "Du erhältst diese E-Mail, weil jemand für Deine E-Mail Adresse ein Konto registriert hat."
|
||||
},
|
||||
"pinReset": {
|
||||
"subject": "PIN erfolgreich geändert",
|
||||
"introduction": "Sie haben erfolgreich Ihre PIN beim Terminbuchungsportal von {tenant} geändert und können sich ab sofort mit Ihrer PIN anmelden.",
|
||||
"action": "Anmelden",
|
||||
"reason": "Sie erhalten diese E-Mail, weil jemand für die PIN für Ihr Konto geändert hat."
|
||||
},
|
||||
"userInvite": {
|
||||
"subject": "E-Mail Adresse bestätigen für {tenant}",
|
||||
"introduction": "willkommen beim Terminbuchungsportal von {tenant}. Bitte bestätige Deine E-Mail Adresse.",
|
||||
"action": "E-Mail Adresse bestätigen",
|
||||
"hint": "Dieser Link ist nur {expirationMinutes} Minuten gültig und kann nur einmal verwendet werden.",
|
||||
"reason": "Du erhältst diese E-Mail, weil jemand für Deine E-Mail Adresse ein Konto registriert hat."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,7 +562,7 @@
|
||||
"absences": {
|
||||
"title": "Absences",
|
||||
"add": {
|
||||
"title": "Add Anbsence",
|
||||
"title": "Add Absence",
|
||||
"description": "Absences are a way to mark you agents as being unavailbale.",
|
||||
"fields": {
|
||||
"agent": {
|
||||
@@ -926,6 +926,37 @@
|
||||
"introduction": "your appointment for has been booked.",
|
||||
"action": "Cancel appointment",
|
||||
"reason": "You are receiving this email because someone booked an appointment with your e-mail address."
|
||||
},
|
||||
"appointmentReminder": {
|
||||
"subject": "Reminder: Your appointment with {tenant}",
|
||||
"introduction": "your appointment with {tenant} is soon.",
|
||||
"action": "Cancel appointment",
|
||||
"reason": "You are receiving this email because someone booked an appointment with your e-mail address."
|
||||
},
|
||||
"appointmentRequest": {
|
||||
"subject": "{channel} with {tenant} requested",
|
||||
"introduction": "your appointment was requested. You will be notified once it is confirmed.",
|
||||
"reason": "You are receiving this email because someone requested an appointment with your e-mail address."
|
||||
},
|
||||
"confirmation": {
|
||||
"subject": "Confirm your E-Mail Address",
|
||||
"introduction": "welcome our appointment booking platform. Please confirm your e-mail address.",
|
||||
"action": "Confirm E-Mail Address",
|
||||
"hint": "This link is only valid for {expirationMinutes} minutes and can only be used once.",
|
||||
"reason": "You are receiving this email because someone registered an account with your e-mail address."
|
||||
},
|
||||
"pinReset": {
|
||||
"subject": "PIN successfully changed",
|
||||
"introduction": "you've successfully changed your PIN on the {tenant} appointment booking platform. You can now log-in with your new PIN.",
|
||||
"action": "Login",
|
||||
"reason": "You are receiving this email because someone changed the PIN-Code for your account."
|
||||
},
|
||||
"userInvite": {
|
||||
"subject": "Confirm your E-Mail Address",
|
||||
"introduction": "welcome our appointment booking platform. Please confirm your e-mail address.",
|
||||
"action": "Confirm E-Mail Address",
|
||||
"hint": "This link is only valid for {expirationMinutes} minutes and can only be used once.",
|
||||
"reason": "You are receiving this email because someone registered an account with your e-mail address."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import { m } from "$i18n/messages";
|
||||
import { setLocale } from "$i18n/runtime";
|
||||
import type { SupportedLocale } from "$lib/const/locales";
|
||||
import type { SelectTenant } from "$lib/server/db/central-schema";
|
||||
import { type SelectAppointment } from "$lib/server/db/tenant-schema";
|
||||
import type { SelectClient } from "$lib/server/email/email-service";
|
||||
import EmailButton from "./components/EmailButton.svelte";
|
||||
import EmailHeadline from "./components/EmailHeadline.svelte";
|
||||
import EmailLayout from "./components/EmailLayout.svelte";
|
||||
import EmailText from "./components/EmailText.svelte";
|
||||
import { renderAppointmentDate, renderAppointmentTime } from "./utils";
|
||||
|
||||
let {
|
||||
locale,
|
||||
user,
|
||||
tenant,
|
||||
channel,
|
||||
appointment,
|
||||
address,
|
||||
cancelUrl,
|
||||
}: {
|
||||
locale: SupportedLocale;
|
||||
user: SelectClient;
|
||||
tenant: SelectTenant;
|
||||
channel: string;
|
||||
appointment: SelectAppointment & { agentName: string };
|
||||
address: {
|
||||
street: string;
|
||||
number: string;
|
||||
additionalAddressInfo?: string;
|
||||
zip: string;
|
||||
city: string;
|
||||
};
|
||||
cancelUrl: string;
|
||||
} = $props();
|
||||
|
||||
setLocale(locale);
|
||||
</script>
|
||||
|
||||
<EmailLayout>
|
||||
<EmailText variant="md">{m["emails.greeting"]({ name: user.email })}</EmailText>
|
||||
<EmailText variant="md">
|
||||
{m["emails.appointmentReminder.introduction"]({ tenant: tenant.longName })}
|
||||
</EmailText>
|
||||
<EmailHeadline>{channel}</EmailHeadline>
|
||||
<EmailText variant="md">
|
||||
{appointment.agentName}<br />
|
||||
{renderAppointmentDate(appointment.appointmentDate, locale)}<br />
|
||||
{renderAppointmentTime(appointment.appointmentDate, locale)}
|
||||
{m["emails.oclock"]()}
|
||||
</EmailText>
|
||||
<EmailHeadline>{tenant.longName}</EmailHeadline>
|
||||
<EmailText variant="md">
|
||||
{address.street}
|
||||
{address.number}<br />
|
||||
{#if address.additionalAddressInfo}{address.additionalAddressInfo}<br />{/if}
|
||||
{address.zip}
|
||||
{address.city}
|
||||
</EmailText>
|
||||
<EmailButton href={cancelUrl}>{m["emails.appointmentReminder.action"]()}</EmailButton>
|
||||
<EmailText variant="md" color="text-light">
|
||||
{m["emails.appointmentReminder.reason"]()}
|
||||
</EmailText>
|
||||
</EmailLayout>
|
||||
@@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { m } from "$i18n/messages";
|
||||
import { setLocale } from "$i18n/runtime";
|
||||
import type { SupportedLocale } from "$lib/const/locales";
|
||||
import type { SelectTenant } from "$lib/server/db/central-schema";
|
||||
import { type SelectAppointment } from "$lib/server/db/tenant-schema";
|
||||
import type { SelectClient } from "$lib/server/email/email-service";
|
||||
import EmailButton from "./components/EmailButton.svelte";
|
||||
import EmailHeadline from "./components/EmailHeadline.svelte";
|
||||
import EmailLayout from "./components/EmailLayout.svelte";
|
||||
import EmailText from "./components/EmailText.svelte";
|
||||
import { renderAppointmentDate, renderAppointmentTime } from "./utils";
|
||||
|
||||
let {
|
||||
locale,
|
||||
user,
|
||||
tenant,
|
||||
channel,
|
||||
appointment,
|
||||
address,
|
||||
}: {
|
||||
locale: SupportedLocale;
|
||||
user: SelectClient;
|
||||
tenant: SelectTenant;
|
||||
channel: string;
|
||||
appointment: SelectAppointment & { agentName: string };
|
||||
address: {
|
||||
street: string;
|
||||
number: string;
|
||||
additionalAddressInfo?: string;
|
||||
zip: string;
|
||||
city: string;
|
||||
};
|
||||
} = $props();
|
||||
|
||||
setLocale(locale);
|
||||
</script>
|
||||
|
||||
<EmailLayout>
|
||||
<EmailText variant="md">{m["emails.greeting"]({ name: user.email })}</EmailText>
|
||||
<EmailText variant="md">
|
||||
{m["emails.appointmentRequest.introduction"]()}
|
||||
</EmailText>
|
||||
<EmailHeadline>{channel}</EmailHeadline>
|
||||
<EmailText variant="md">
|
||||
{appointment.agentName}<br />
|
||||
{renderAppointmentDate(appointment.appointmentDate, locale)}<br />
|
||||
{renderAppointmentTime(appointment.appointmentDate, locale)}
|
||||
{m["emails.oclock"]()}
|
||||
</EmailText>
|
||||
<EmailHeadline>{tenant.longName}</EmailHeadline>
|
||||
<EmailText variant="md">
|
||||
{address.street}
|
||||
{address.number}<br />
|
||||
{#if address.additionalAddressInfo}{address.additionalAddressInfo}<br />{/if}
|
||||
{address.zip}
|
||||
{address.city}
|
||||
</EmailText>
|
||||
<EmailText variant="md" color="text-light">
|
||||
{m["emails.appointmentRequest.reason"]()}
|
||||
</EmailText>
|
||||
</EmailLayout>
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { m } from "$i18n/messages";
|
||||
import { setLocale } from "$i18n/runtime";
|
||||
import type { SupportedLocale } from "$lib/const/locales";
|
||||
import type { SelectUserEmail } from "$lib/server/email/email-service";
|
||||
import EmailButton from "./components/EmailButton.svelte";
|
||||
import EmailLayout from "./components/EmailLayout.svelte";
|
||||
import EmailText from "./components/EmailText.svelte";
|
||||
|
||||
let {
|
||||
locale,
|
||||
user,
|
||||
confirmUrl,
|
||||
expirationMinutes,
|
||||
}: {
|
||||
locale: SupportedLocale;
|
||||
user: SelectUserEmail;
|
||||
confirmUrl: string;
|
||||
expirationMinutes: number;
|
||||
} = $props();
|
||||
|
||||
setLocale(locale);
|
||||
</script>
|
||||
|
||||
<EmailLayout>
|
||||
<EmailText variant="md">{m["emails.greeting"]({ name: user.name })}</EmailText>
|
||||
<EmailText variant="md">
|
||||
{m["emails.confirmation.introduction"]()}
|
||||
</EmailText>
|
||||
<EmailButton href={confirmUrl}>{m["emails.confirmation.action"]()}</EmailButton>
|
||||
<EmailText variant="md">
|
||||
{m["emails.confirmation.hint"]({ expirationMinutes })}
|
||||
</EmailText>
|
||||
<EmailText variant="md" color="text-light">
|
||||
{m["emails.confirmation.reason"]()}
|
||||
</EmailText>
|
||||
</EmailLayout>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { m } from "$i18n/messages";
|
||||
import { setLocale } from "$i18n/runtime";
|
||||
import type { SupportedLocale } from "$lib/const/locales";
|
||||
import { type SelectTenant } from "$lib/server/db/central-schema";
|
||||
import type { SelectClient } from "$lib/server/email/email-service";
|
||||
import EmailButton from "./components/EmailButton.svelte";
|
||||
import EmailLayout from "./components/EmailLayout.svelte";
|
||||
import EmailText from "./components/EmailText.svelte";
|
||||
|
||||
let {
|
||||
locale,
|
||||
user,
|
||||
tenant,
|
||||
loginUrl,
|
||||
}: {
|
||||
locale: SupportedLocale;
|
||||
user: SelectClient;
|
||||
tenant: SelectTenant;
|
||||
loginUrl: string;
|
||||
} = $props();
|
||||
|
||||
setLocale(locale);
|
||||
</script>
|
||||
|
||||
<EmailLayout>
|
||||
<EmailText variant="md">{m["emails.greeting"]({ name: user.email })}</EmailText>
|
||||
<EmailText variant="md">
|
||||
{m["emails.pinReset.introduction"]({ tenant: tenant.longName })}
|
||||
</EmailText>
|
||||
<EmailButton href={loginUrl}>{m["emails.pinReset.action"]()}</EmailButton>
|
||||
<EmailText variant="md" color="text-light">
|
||||
{m["emails.pinReset.reason"]()}
|
||||
</EmailText>
|
||||
</EmailLayout>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { m } from "$i18n/messages";
|
||||
import { setLocale } from "$i18n/runtime";
|
||||
import type { SupportedLocale } from "$lib/const/locales";
|
||||
import { type SelectTenant } from "$lib/server/db/central-schema";
|
||||
import type { SelectUserEmail } from "$lib/server/email/email-service";
|
||||
import EmailButton from "./components/EmailButton.svelte";
|
||||
import EmailLayout from "./components/EmailLayout.svelte";
|
||||
import EmailText from "./components/EmailText.svelte";
|
||||
|
||||
let {
|
||||
locale,
|
||||
user,
|
||||
tenant,
|
||||
confirmUrl,
|
||||
expirationMinutes,
|
||||
}: {
|
||||
locale: SupportedLocale;
|
||||
user: SelectUserEmail;
|
||||
tenant: SelectTenant;
|
||||
confirmUrl: string;
|
||||
expirationMinutes: number;
|
||||
} = $props();
|
||||
|
||||
setLocale(locale);
|
||||
</script>
|
||||
|
||||
<EmailLayout>
|
||||
<EmailText variant="md">{m["emails.greeting"]({ name: user.name })}</EmailText>
|
||||
<EmailText variant="md">
|
||||
{m["emails.userInvite.introduction"]({ tenant: tenant.longName })}
|
||||
</EmailText>
|
||||
<EmailButton href={confirmUrl}>{m["emails.userInvite.action"]()}</EmailButton>
|
||||
<EmailText variant="md">
|
||||
{m["emails.userInvite.hint"]({ expirationMinutes })}
|
||||
</EmailText>
|
||||
<EmailText variant="md" color="text-light">
|
||||
{m["emails.userInvite.reason"]()}
|
||||
</EmailText>
|
||||
</EmailLayout>
|
||||
@@ -50,10 +50,13 @@
|
||||
margin-top: 0;
|
||||
}
|
||||
.page {
|
||||
padding: 0 16px;
|
||||
padding: 0 1rem;
|
||||
min-height: 100%;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
font-family: "Inter", Arial, sans-serif;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
|
||||
@@ -21,6 +21,8 @@ export type SelectClient = {
|
||||
language: string;
|
||||
};
|
||||
|
||||
export type SelectUserEmail = Pick<SelectUser, "email" | "name" | "language">;
|
||||
|
||||
/**
|
||||
* Get channel title in the user's preferred language
|
||||
* @param {string} tenantId - Tenant ID
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import AppointmentReminder from "$lib/emails/AppointmentReminder.svelte";
|
||||
import { renderOutputToHtml, htmlToText } from "$lib/emails/utils";
|
||||
import type { SelectTenant } from "$lib/server/db/central-schema";
|
||||
import type { SelectAppointment } from "$lib/server/db/tenant-schema";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
import { render } from "svelte/server";
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
const emailRender = render(AppointmentReminder, {
|
||||
props: {
|
||||
locale: "en",
|
||||
user: {
|
||||
email: "max.mustermann@example.com",
|
||||
language: "en",
|
||||
},
|
||||
tenant: { longName: "Praxis Dr. Jane Doe" } as SelectTenant,
|
||||
appointment: {
|
||||
appointmentDate: new Date("2024-06-30T10:00:00"),
|
||||
agentName: "Dr. John Doe",
|
||||
} as SelectAppointment & { agentName: string },
|
||||
channel: "Vaccination Appointment",
|
||||
address: {
|
||||
street: "Musterstraße",
|
||||
number: "1",
|
||||
additionalAddressInfo: "Hinterhaus",
|
||||
zip: "20000",
|
||||
city: "Hamburg",
|
||||
},
|
||||
cancelUrl: "https://open-reception.org/appointments/cancel/abc123",
|
||||
},
|
||||
});
|
||||
const html = renderOutputToHtml(emailRender);
|
||||
const text = htmlToText(html);
|
||||
|
||||
// Output for testing purposes
|
||||
console.log(text);
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import AppointmentRequested from "$lib/emails/AppointmentRequest.svelte";
|
||||
import { renderOutputToHtml, htmlToText } from "$lib/emails/utils";
|
||||
import type { SelectTenant } from "$lib/server/db/central-schema";
|
||||
import type { SelectAppointment } from "$lib/server/db/tenant-schema";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
import { render } from "svelte/server";
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
const emailRender = render(AppointmentRequested, {
|
||||
props: {
|
||||
locale: "en",
|
||||
user: {
|
||||
email: "max.mustermann@example.com",
|
||||
language: "en",
|
||||
},
|
||||
tenant: { longName: "Praxis Dr. Jane Doe" } as SelectTenant,
|
||||
appointment: {
|
||||
appointmentDate: new Date("2024-06-30T10:00:00"),
|
||||
agentName: "Dr. John Doe",
|
||||
} as SelectAppointment & { agentName: string },
|
||||
channel: "Vaccination Appointment",
|
||||
address: {
|
||||
street: "Musterstraße",
|
||||
number: "1",
|
||||
additionalAddressInfo: "Hinterhaus",
|
||||
zip: "20000",
|
||||
city: "Hamburg",
|
||||
},
|
||||
},
|
||||
});
|
||||
const html = renderOutputToHtml(emailRender);
|
||||
const text = htmlToText(html);
|
||||
|
||||
// Output for testing purposes
|
||||
console.log(text);
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import Confirmation from "$lib/emails/Confirmation.svelte";
|
||||
import { htmlToText, renderOutputToHtml } from "$lib/emails/utils";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
import { render } from "svelte/server";
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
const emailRender = render(Confirmation, {
|
||||
props: {
|
||||
locale: "en",
|
||||
user: {
|
||||
email: "max.mustermann@example.com",
|
||||
name: "Max Mustermann",
|
||||
language: "en",
|
||||
},
|
||||
confirmUrl: "https://open-reception.org/confirm/abc123",
|
||||
expirationMinutes: 10,
|
||||
},
|
||||
});
|
||||
const html = renderOutputToHtml(emailRender);
|
||||
const text = htmlToText(html);
|
||||
|
||||
// Output for testing purposes
|
||||
console.log(text);
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import PinReset from "$lib/emails/PinReset.svelte";
|
||||
import { renderOutputToHtml, htmlToText } from "$lib/emails/utils";
|
||||
import type { SelectTenant } from "$lib/server/db/central-schema";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
import { render } from "svelte/server";
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
const emailRender = render(PinReset, {
|
||||
props: {
|
||||
locale: "en",
|
||||
user: {
|
||||
email: "max.mustermann@example.com",
|
||||
language: "en",
|
||||
},
|
||||
tenant: { longName: "Praxis Dr. Jane Doe" } as SelectTenant,
|
||||
loginUrl: "https://open-reception.org/login",
|
||||
},
|
||||
});
|
||||
const html = renderOutputToHtml(emailRender);
|
||||
const text = htmlToText(html);
|
||||
|
||||
// Output for testing purposes
|
||||
console.log(text);
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import UserInvite from "$lib/emails/UserInvite.svelte";
|
||||
import { htmlToText, renderOutputToHtml } from "$lib/emails/utils";
|
||||
import type { SelectTenant } from "$lib/server/db/central-schema";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
import { render } from "svelte/server";
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
const emailRender = render(UserInvite, {
|
||||
props: {
|
||||
locale: "en",
|
||||
user: {
|
||||
email: "max.mustermann@example.com",
|
||||
name: "Max Mustermann",
|
||||
language: "en",
|
||||
},
|
||||
tenant: { longName: "Praxis Dr. Jane Doe" } as SelectTenant,
|
||||
confirmUrl: "https://open-reception.org/confirm/abc123",
|
||||
expirationMinutes: 10,
|
||||
},
|
||||
});
|
||||
const html = renderOutputToHtml(emailRender);
|
||||
const text = htmlToText(html);
|
||||
|
||||
// Output for testing purposes
|
||||
console.log(text);
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user