diff --git a/project.inlang/messages/de.json b/project.inlang/messages/de.json
index 15a9555..e6d8abe 100644
--- a/project.inlang/messages/de.json
+++ b/project.inlang/messages/de.json
@@ -1034,6 +1034,12 @@
"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."
},
+ "notification": {
+ "subject": "Aktivität in Deinem Terminbuchungsportal",
+ "introduction": "in Deinem Terminbuchungsportal gab es eine neue Aktivität. Bitte logge Dich ein, um die Details zu sehen.",
+ "action": "Dashboard öffnen",
+ "reason": "Du erhältst diese E-Mail, weil Du für einen Kanal Benachrichtigungen aktiviert hast."
+ },
"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.",
diff --git a/project.inlang/messages/en.json b/project.inlang/messages/en.json
index dba5a45..d0f8f1e 100644
--- a/project.inlang/messages/en.json
+++ b/project.inlang/messages/en.json
@@ -1049,6 +1049,12 @@
"action": "Login",
"reason": "You are receiving this email because someone changed the PIN-Code for your account."
},
+ "notification": {
+ "subject": "Activity in your appointment booking platform",
+ "introduction": "There is new activity in your appointment booking platform. Please log in to view details.",
+ "action": "Open Dashboard",
+ "reason": "You are receiving this email because you have notifications enabled for a channel."
+ },
"userInvite": {
"subject": "Confirm your E-Mail Address",
"introduction": "welcome our appointment booking platform. Please confirm your e-mail address.",
diff --git a/src/lib/emails/Notification.svelte b/src/lib/emails/Notification.svelte
new file mode 100644
index 0000000..ad4061a
--- /dev/null
+++ b/src/lib/emails/Notification.svelte
@@ -0,0 +1,34 @@
+
+
+
+ {m["emails.greeting"]({ name: user.name })}
+
+ {m["emails.notification.introduction"]()}
+
+ {m["emails.notification.action"]()}
+
+ {m["emails.notification.reason"]()}
+
+
diff --git a/src/lib/server/email/email-service.ts b/src/lib/server/email/email-service.ts
index 6011768..f16ba5e 100644
--- a/src/lib/server/email/email-service.ts
+++ b/src/lib/server/email/email-service.ts
@@ -24,6 +24,7 @@ import Confirmation from "$lib/emails/Confirmation.svelte";
import PinReset from "$lib/emails/PinReset.svelte";
import UserInvite from "$lib/emails/UserInvite.svelte";
import { dev } from "$app/environment";
+import Notification from "$lib/emails/Notification.svelte";
export type SelectClient = {
email: string;
@@ -541,3 +542,34 @@ export async function sendAppointmentCancelledEmail(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await sendEmail(recipient as any, subject, html, text, tenant.longName);
}
+
+/**
+ * Send notification email
+ * @param {SelectClient | SelectUser} user - Database user object or client data
+ * @param {SelectTenant} tenant - Tenant information for branding
+ * @param {SelectAppointment} appointment - Cancelled appointment details
+ * @param {string} [channelTitle] - Optional channel title/name
+ * @throws {Error} When email sending fails
+ * @returns {Promise}
+ */
+export async function sendNotificationEmail(
+ user: SelectUser,
+ tenant: { domain: string; longName: string },
+): Promise {
+ // Create recipient directly for SelectClient type, use helper for SelectUser
+ const { recipient, locale } = await getRecipient(user);
+ // Generate email
+ const subject = m["emails.notification.subject"]();
+ const emailRender = render(Notification, {
+ props: {
+ locale,
+ user,
+ dashboardUrl: dev ? `http://localhost:5173/dashboard` : `https://${tenant.domain}/dashboard`,
+ },
+ });
+ const html = renderOutputToHtml(emailRender);
+ const text = htmlToText(html);
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ await sendEmail(recipient as any, subject, html, text, tenant.longName);
+}
diff --git a/src/lib/server/services/notification-service.ts b/src/lib/server/services/notification-service.ts
index 02c1901..6f49a24 100644
--- a/src/lib/server/services/notification-service.ts
+++ b/src/lib/server/services/notification-service.ts
@@ -1,14 +1,17 @@
-import { getTenantDb } from "../db";
+import { centralDb, getTenantDb } from "../db";
import {
notification,
channelStaff,
notificationTypes,
type NotificationType,
} from "../db/tenant-schema";
-import { eq, and, desc } from "drizzle-orm";
+import { user } from "$lib/server/db/central-schema";
+import { eq, and, desc, inArray } from "drizzle-orm";
import logger from "$lib/logger";
import { z } from "zod";
import { ValidationError, NotFoundError } from "../utils/errors";
+import { sendNotificationEmail } from "../email/email-service";
+import { TenantAdminService } from "./tenant-admin-service";
const notificationCreationSchema = z.object({
channelId: z.uuid({ message: "Invalid UUID format" }),
@@ -105,6 +108,30 @@ export class NotificationService {
.values(notificationsToCreate)
.returning({ id: notification.id });
+ // Send e-mail notifications
+ if (request.type === "APPOINTMENT_REQUESTED") {
+ const userAccounts = await centralDb
+ .select()
+ .from(user)
+ .where(
+ inArray(
+ user.id,
+ notificationsToCreate.map((n) => n.staffId),
+ ),
+ );
+ if (userAccounts.length > 0) {
+ const adminService = await TenantAdminService.getTenantById(this.tenantId);
+ const tenant = adminService.tenantData;
+ if (tenant) {
+ await Promise.all(
+ userAccounts.map((staff) =>
+ sendNotificationEmail(staff, { domain: tenant.domain, longName: tenant.longName }),
+ ),
+ );
+ }
+ }
+ }
+
log.info("Created notifications for channel", {
channelId: request.channelId,
count: createdNotifications.length,
diff --git a/src/routes/(pages)/(clients)/clients/login/+page.svelte b/src/routes/(pages)/(clients)/clients/login/+page.svelte
index a97af2d..091ffbc 100644
--- a/src/routes/(pages)/(clients)/clients/login/+page.svelte
+++ b/src/routes/(pages)/(clients)/clients/login/+page.svelte
@@ -47,6 +47,8 @@
>
{m["login.action"]()}
-
+