diff --git a/src/lib/emails/AppointmentBooked.svelte b/src/lib/emails/AppointmentBooked.svelte
new file mode 100644
index 0000000..fd8ec61
--- /dev/null
+++ b/src/lib/emails/AppointmentBooked.svelte
@@ -0,0 +1,33 @@
+
+
+
+ {m["absences.add.description"]()} {name},
+ your appointment has been booked.
+ Vaccination Appointment
+
+ Dr. John Doe
+ June 30, 2024
+ 10:00 AM - 10:15 AM
+
+ Praxis Dr. Jane Doe
+
+ Musterstraße 10
+ Etage 4
+ 12345 Musterstadt
+
+ View Appointments
+
+ You are receiving this email because someone booked an appointment with your e-mail address.
+
+
diff --git a/src/lib/emails/components/EmailButton.svelte b/src/lib/emails/components/EmailButton.svelte
new file mode 100644
index 0000000..e16724e
--- /dev/null
+++ b/src/lib/emails/components/EmailButton.svelte
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/src/lib/emails/components/EmailLayout.svelte b/src/lib/emails/components/EmailLayout.svelte
new file mode 100644
index 0000000..04346b6
--- /dev/null
+++ b/src/lib/emails/components/EmailLayout.svelte
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+ {@render children?.()}
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/emails/components/EmailText.svelte b/src/lib/emails/components/EmailText.svelte
new file mode 100644
index 0000000..95734c9
--- /dev/null
+++ b/src/lib/emails/components/EmailText.svelte
@@ -0,0 +1,29 @@
+
+
+{@render children?.()}
+
+
+
+
diff --git a/src/lib/emails/utils.ts b/src/lib/emails/utils.ts
new file mode 100644
index 0000000..b7ce3f4
--- /dev/null
+++ b/src/lib/emails/utils.ts
@@ -0,0 +1,21 @@
+// is not exported from svelte
+export type RenderOutput = {
+ head: string;
+ body: string;
+};
+
+export const renderOutputToHtml = (renderOutput: RenderOutput) => {
+ return `
+
+
+
+
+
+ ${renderOutput.head}
+
+
+ ${renderOutput.body}
+
+
+ `;
+};
diff --git a/src/params/dev.ts b/src/params/dev.ts
new file mode 100644
index 0000000..12653b8
--- /dev/null
+++ b/src/params/dev.ts
@@ -0,0 +1,7 @@
+import { dev } from "$app/environment";
+import type { ParamMatcher } from "@sveltejs/kit";
+
+export const match: ParamMatcher = (param) => {
+ // Doesn't have to be include, but needs to return a boolean
+ return dev && param.includes("local-only");
+};
diff --git a/src/routes/(local-only)/[test=dev]/e-mail-templates/+server.ts b/src/routes/(local-only)/[test=dev]/e-mail-templates/+server.ts
new file mode 100644
index 0000000..18d3e6b
--- /dev/null
+++ b/src/routes/(local-only)/[test=dev]/e-mail-templates/+server.ts
@@ -0,0 +1,14 @@
+import AppointmentBooked from "$lib/emails/AppointmentBooked.svelte";
+import { renderOutputToHtml } from "$lib/emails/utils";
+import type { RequestHandler } from "@sveltejs/kit";
+import { render } from "svelte/server";
+
+export const GET: RequestHandler = async () => {
+ const emailRender = render(AppointmentBooked, { props: { name: "John Doe", locale: "de" } });
+
+ return new Response(renderOutputToHtml(emailRender), {
+ headers: {
+ "Content-Type": "text/html",
+ },
+ });
+};
diff --git a/src/server-hooks/i18nHandle.ts b/src/server-hooks/i18nHandle.ts
index dd4d410..600e928 100644
--- a/src/server-hooks/i18nHandle.ts
+++ b/src/server-hooks/i18nHandle.ts
@@ -11,6 +11,12 @@ import type { Handle } from "@sveltejs/kit";
* @returns {Promise} The response with applied headers and rate limiting
*/
export const i18nHandle: Handle = ({ event, resolve }) => {
+ // Disable i18n for API and local-only routes
+ // Avoids using i18n middleware where emails are rendered
+ if (event.url.pathname.startsWith("/api") || event.url.pathname.startsWith("/local-only")) {
+ return resolve(event);
+ }
+
// Determine browser locale and use it
const cookieLocale = event.cookies.get(cookieName);
let locale: Locale | undefined = cookieLocale as Locale;