Browse Source

org level email provider

Bernt Christian Egeland 7 tháng trước cách đây
mục cha
commit
ac2abb5448

+ 9 - 0
src/app/(authenticated)/settings/email/page.tsx

@@ -0,0 +1,9 @@
+import { getEmailSettings } from "@/features/email/Actions/emailSettingsActions";
+import { EmailSettingsForm } from "@/features/email/Components/EmailSettingsForm";
+
+export default async function EmailSettingsPage() {
+  const result = await getEmailSettings();
+  const settings = result.success && result.data ? result.data : {};
+
+  return <EmailSettingsForm initial={settings} />;
+}

+ 8 - 0
src/app/(authenticated)/settings/settings-nav.tsx

@@ -15,6 +15,7 @@ import {
   Key,
   Key,
   Layout,
   Layout,
   ListPlus,
   ListPlus,
+  Mail,
   Palette,
   Palette,
   UserCog,
   UserCog,
   UsersRound,
   UsersRound,
@@ -89,6 +90,13 @@ const settingsNav: SettingsNavItem[] = [
     description: "Define custom data fields",
     description: "Define custom data fields",
     gate: "customFields",
     gate: "customFields",
   },
   },
+  {
+    title: "Email",
+    href: "/settings/email",
+    icon: Mail,
+    description: "Email provider & sending",
+    gate: "smtp",
+  },
   {
   {
     title: "Workshop",
     title: "Workshop",
     href: "/settings/workshop",
     href: "/settings/workshop",

+ 1 - 0
src/components/page-header.tsx

@@ -47,6 +47,7 @@ const breadcrumbMap: Record<string, { parent?: string; parentHref?: string; labe
   '/settings/currency': { parent: 'Settings', parentHref: '/settings', label: 'Currency' },
   '/settings/currency': { parent: 'Settings', parentHref: '/settings', label: 'Currency' },
   '/settings/workshop': { parent: 'Settings', parentHref: '/settings', label: 'Workshop' },
   '/settings/workshop': { parent: 'Settings', parentHref: '/settings', label: 'Workshop' },
   '/settings/appearance': { parent: 'Settings', parentHref: '/settings', label: 'Appearance' },
   '/settings/appearance': { parent: 'Settings', parentHref: '/settings', label: 'Appearance' },
+  '/settings/email': { parent: 'Settings', parentHref: '/settings', label: 'Email' },
   '/settings/about': { parent: 'Settings', parentHref: '/settings', label: 'About' },
   '/settings/about': { parent: 'Settings', parentHref: '/settings', label: 'About' },
 }
 }
 
 

+ 1 - 1
src/features/admin/Components/admin-settings.tsx

@@ -226,7 +226,7 @@ export function AdminSettings({
         <CardHeader>
         <CardHeader>
           <CardTitle>Email Settings</CardTitle>
           <CardTitle>Email Settings</CardTitle>
           <CardDescription>
           <CardDescription>
-            Choose how the platform sends emails (password resets, invoices, notifications)
+            Platform Email Provider — Used for system-level emails such as password resets and team invitations. This is also the default email provider for all organizations. Organizations can optionally configure their own email provider in Settings &gt; Email.
           </CardDescription>
           </CardDescription>
         </CardHeader>
         </CardHeader>
         <CardContent className="space-y-6">
         <CardContent className="space-y-6">

+ 7 - 7
src/features/email/Actions/emailActions.ts

@@ -1,7 +1,7 @@
 "use server";
 "use server";
 
 
 import { db } from "@/lib/db";
 import { db } from "@/lib/db";
-import { sendMail } from "@/lib/email";
+import { sendOrgMail, getOrgFromAddress } from "@/lib/email";
 import { withAuth } from "@/lib/with-auth";
 import { withAuth } from "@/lib/with-auth";
 import { renderToBuffer } from "@react-pdf/renderer";
 import { renderToBuffer } from "@react-pdf/renderer";
 import React from "react";
 import React from "react";
@@ -106,10 +106,10 @@ export async function sendQuoteEmail(input: {
     const pdfBuffer = await renderToBuffer(element);
     const pdfBuffer = await renderToBuffer(element);
     const quoteNum = quote.quoteNumber || `QT-${quote.id.slice(-8).toUpperCase()}`;
     const quoteNum = quote.quoteNumber || `QT-${quote.id.slice(-8).toUpperCase()}`;
 
 
-    const fromEmail = process.env.SMTP_FROM_EMAIL || settings["workshop.email"] || "noreply@localhost";
+    const from = await getOrgFromAddress(organizationId);
 
 
-    await sendMail({
-      from: `${fromName} <${fromEmail}>`,
+    await sendOrgMail(organizationId, {
+      from,
       to: recipientEmail,
       to: recipientEmail,
       subject: `Quote ${quoteNum} - ${quote.title}`,
       subject: `Quote ${quoteNum} - ${quote.title}`,
       html: `
       html: `
@@ -196,10 +196,10 @@ export async function sendInvoiceEmail(input: {
       ? `${process.env.NEXT_PUBLIC_APP_URL || process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000"}/share/invoice/${organizationId}/${record.publicToken}`
       ? `${process.env.NEXT_PUBLIC_APP_URL || process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000"}/share/invoice/${organizationId}/${record.publicToken}`
       : null;
       : null;
 
 
-    const fromEmail = process.env.SMTP_FROM_EMAIL || settings["workshop.email"] || "noreply@localhost";
+    const from = await getOrgFromAddress(organizationId);
 
 
-    await sendMail({
-      from: `${fromName} <${fromEmail}>`,
+    await sendOrgMail(organizationId, {
+      from,
       to: recipientEmail,
       to: recipientEmail,
       subject: `Invoice ${invoiceNum} - ${record.title}`,
       subject: `Invoice ${invoiceNum} - ${record.title}`,
       html: `
       html: `

+ 99 - 0
src/features/email/Actions/emailSettingsActions.ts

@@ -0,0 +1,99 @@
+"use server";
+
+import { db } from "@/lib/db";
+import { withAuth } from "@/lib/with-auth";
+import { revalidatePath } from "next/cache";
+import { ALL_ORG_EMAIL_KEYS } from "../Schema/emailSettingsSchema";
+import { PermissionAction, PermissionSubject } from "@/lib/permissions";
+import { sendOrgMail, getOrgFromAddress } from "@/lib/email";
+
+export async function getEmailSettings() {
+  return withAuth(
+    async ({ organizationId }) => {
+      const settings = await db.appSetting.findMany({
+        where: { organizationId, key: { in: ALL_ORG_EMAIL_KEYS } },
+      });
+      const map: Record<string, string> = {};
+      for (const s of settings) {
+        map[s.key] = s.value;
+      }
+      return map;
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.READ, subject: PermissionSubject.SETTINGS },
+      ],
+    },
+  );
+}
+
+export async function setEmailSettings(entries: Record<string, string>) {
+  return withAuth(
+    async ({ userId, organizationId }) => {
+      await db.$transaction(
+        Object.entries(entries).map(([key, value]) =>
+          db.appSetting.upsert({
+            where: { organizationId_key: { organizationId, key } },
+            update: { value },
+            create: { userId, organizationId, key, value },
+          }),
+        ),
+      );
+      revalidatePath("/settings/email");
+      return true;
+    },
+    {
+      requiredPermissions: [
+        {
+          action: PermissionAction.UPDATE,
+          subject: PermissionSubject.SETTINGS,
+        },
+      ],
+    },
+  );
+}
+
+export async function testOrgEmailConnection() {
+  return withAuth(
+    async ({ userId, organizationId }) => {
+      const user = await db.user.findUnique({
+        where: { id: userId },
+        select: { email: true },
+      });
+
+      if (!user?.email) {
+        throw new Error("Could not find your email address");
+      }
+
+      const from = await getOrgFromAddress(organizationId);
+
+      await sendOrgMail(organizationId, {
+        from,
+        to: user.email,
+        subject: "Email Test - Torqvoice",
+        html: `
+          <div style="font-family: sans-serif; max-width: 480px; margin: 0 auto;">
+            <h2>Email Configuration Test</h2>
+            <p>This is a test email from your organization's email settings.</p>
+            <p>If you're reading this, your email provider is configured correctly.</p>
+            <hr style="border: none; border-top: 1px solid #e5e7eb; margin: 16px 0;" />
+            <p style="color: #6b7280; font-size: 12px;">
+              Sent to: ${user.email}<br/>
+              Time: ${new Date().toISOString()}
+            </p>
+          </div>
+        `,
+      });
+
+      return { sentTo: user.email };
+    },
+    {
+      requiredPermissions: [
+        {
+          action: PermissionAction.UPDATE,
+          subject: PermissionSubject.SETTINGS,
+        },
+      ],
+    },
+  );
+}

+ 780 - 0
src/features/email/Components/EmailSettingsForm.tsx

@@ -0,0 +1,780 @@
+"use client";
+
+import { useState, useTransition } from "react";
+import { useRouter } from "next/navigation";
+import { toast } from "sonner";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import {
+  Card,
+  CardContent,
+  CardDescription,
+  CardHeader,
+  CardTitle,
+} from "@/components/ui/card";
+import { Loader2, Send, Info } from "lucide-react";
+import { ORG_EMAIL_KEYS } from "../Schema/emailSettingsSchema";
+import {
+  setEmailSettings,
+  testOrgEmailConnection,
+} from "../Actions/emailSettingsActions";
+
+type EmailProviderType =
+  | "smtp"
+  | "resend"
+  | "postmark"
+  | "mailgun"
+  | "sendgrid"
+  | "ses";
+
+export function EmailSettingsForm({
+  initial,
+}: {
+  initial: Record<string, string>;
+}) {
+  const router = useRouter();
+  const [isPending, startTransition] = useTransition();
+  const [isTesting, setIsTesting] = useState(false);
+
+  const hasCustomProvider = !!initial[ORG_EMAIL_KEYS.EMAIL_PROVIDER];
+  const [useCustom, setUseCustom] = useState(hasCustomProvider);
+
+  // Provider
+  const [emailProvider, setEmailProvider] = useState<EmailProviderType>(
+    (initial[ORG_EMAIL_KEYS.EMAIL_PROVIDER] as EmailProviderType) || "smtp",
+  );
+
+  // SMTP
+  const [smtpHost, setSmtpHost] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SMTP_HOST] || "",
+  );
+  const [smtpPort, setSmtpPort] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SMTP_PORT] || "587",
+  );
+  const [smtpUser, setSmtpUser] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SMTP_USER] || "",
+  );
+  const [smtpPass, setSmtpPass] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SMTP_PASS] || "",
+  );
+  const [smtpSecure, setSmtpSecure] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SMTP_SECURE] === "true",
+  );
+  const [smtpFromEmail, setSmtpFromEmail] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SMTP_FROM_EMAIL] || "",
+  );
+  const [smtpFromName, setSmtpFromName] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SMTP_FROM_NAME] || "",
+  );
+  const [smtpRejectUnauthorized, setSmtpRejectUnauthorized] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SMTP_REJECT_UNAUTHORIZED] !== "false",
+  );
+  const [smtpRequireTls, setSmtpRequireTls] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SMTP_REQUIRE_TLS] === "true",
+  );
+
+  // Resend
+  const [resendApiKey, setResendApiKey] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_RESEND_API_KEY] || "",
+  );
+  const [resendFromEmail, setResendFromEmail] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_RESEND_FROM_EMAIL] || "",
+  );
+  const [resendFromName, setResendFromName] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_RESEND_FROM_NAME] || "",
+  );
+
+  // Postmark
+  const [postmarkApiKey, setPostmarkApiKey] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_POSTMARK_API_KEY] || "",
+  );
+  const [postmarkFromEmail, setPostmarkFromEmail] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_POSTMARK_FROM_EMAIL] || "",
+  );
+  const [postmarkFromName, setPostmarkFromName] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_POSTMARK_FROM_NAME] || "",
+  );
+
+  // Mailgun
+  const [mailgunApiKey, setMailgunApiKey] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_MAILGUN_API_KEY] || "",
+  );
+  const [mailgunDomain, setMailgunDomain] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_MAILGUN_DOMAIN] || "",
+  );
+  const [mailgunRegion, setMailgunRegion] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_MAILGUN_REGION] || "us",
+  );
+  const [mailgunFromEmail, setMailgunFromEmail] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_MAILGUN_FROM_EMAIL] || "",
+  );
+  const [mailgunFromName, setMailgunFromName] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_MAILGUN_FROM_NAME] || "",
+  );
+
+  // SendGrid
+  const [sendgridApiKey, setSendgridApiKey] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SENDGRID_API_KEY] || "",
+  );
+  const [sendgridFromEmail, setSendgridFromEmail] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SENDGRID_FROM_EMAIL] || "",
+  );
+  const [sendgridFromName, setSendgridFromName] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SENDGRID_FROM_NAME] || "",
+  );
+
+  // Amazon SES
+  const [sesAccessKeyId, setSesAccessKeyId] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SES_ACCESS_KEY_ID] || "",
+  );
+  const [sesSecretAccessKey, setSesSecretAccessKey] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SES_SECRET_ACCESS_KEY] || "",
+  );
+  const [sesRegion, setSesRegion] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SES_REGION] || "us-east-1",
+  );
+  const [sesFromEmail, setSesFromEmail] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SES_FROM_EMAIL] || "",
+  );
+  const [sesFromName, setSesFromName] = useState(
+    initial[ORG_EMAIL_KEYS.EMAIL_SES_FROM_NAME] || "",
+  );
+
+  const handleSave = () => {
+    startTransition(async () => {
+      if (!useCustom) {
+        // Clear the provider key to revert to platform default
+        const result = await setEmailSettings({
+          [ORG_EMAIL_KEYS.EMAIL_PROVIDER]: "",
+        });
+        if (result.success) {
+          toast.success("Email settings saved — using platform default");
+          router.refresh();
+        } else {
+          toast.error(result.error ?? "Failed to save settings");
+        }
+        return;
+      }
+
+      const data: Record<string, string> = {
+        [ORG_EMAIL_KEYS.EMAIL_PROVIDER]: emailProvider,
+        // SMTP
+        [ORG_EMAIL_KEYS.EMAIL_SMTP_HOST]: smtpHost,
+        [ORG_EMAIL_KEYS.EMAIL_SMTP_PORT]: smtpPort,
+        [ORG_EMAIL_KEYS.EMAIL_SMTP_USER]: smtpUser,
+        [ORG_EMAIL_KEYS.EMAIL_SMTP_PASS]: smtpPass,
+        [ORG_EMAIL_KEYS.EMAIL_SMTP_SECURE]: String(smtpSecure),
+        [ORG_EMAIL_KEYS.EMAIL_SMTP_FROM_EMAIL]: smtpFromEmail,
+        [ORG_EMAIL_KEYS.EMAIL_SMTP_FROM_NAME]: smtpFromName,
+        [ORG_EMAIL_KEYS.EMAIL_SMTP_REJECT_UNAUTHORIZED]: String(
+          smtpRejectUnauthorized,
+        ),
+        [ORG_EMAIL_KEYS.EMAIL_SMTP_REQUIRE_TLS]: String(smtpRequireTls),
+        // Resend
+        [ORG_EMAIL_KEYS.EMAIL_RESEND_API_KEY]: resendApiKey,
+        [ORG_EMAIL_KEYS.EMAIL_RESEND_FROM_EMAIL]: resendFromEmail,
+        [ORG_EMAIL_KEYS.EMAIL_RESEND_FROM_NAME]: resendFromName,
+        // Postmark
+        [ORG_EMAIL_KEYS.EMAIL_POSTMARK_API_KEY]: postmarkApiKey,
+        [ORG_EMAIL_KEYS.EMAIL_POSTMARK_FROM_EMAIL]: postmarkFromEmail,
+        [ORG_EMAIL_KEYS.EMAIL_POSTMARK_FROM_NAME]: postmarkFromName,
+        // Mailgun
+        [ORG_EMAIL_KEYS.EMAIL_MAILGUN_API_KEY]: mailgunApiKey,
+        [ORG_EMAIL_KEYS.EMAIL_MAILGUN_DOMAIN]: mailgunDomain,
+        [ORG_EMAIL_KEYS.EMAIL_MAILGUN_REGION]: mailgunRegion,
+        [ORG_EMAIL_KEYS.EMAIL_MAILGUN_FROM_EMAIL]: mailgunFromEmail,
+        [ORG_EMAIL_KEYS.EMAIL_MAILGUN_FROM_NAME]: mailgunFromName,
+        // SendGrid
+        [ORG_EMAIL_KEYS.EMAIL_SENDGRID_API_KEY]: sendgridApiKey,
+        [ORG_EMAIL_KEYS.EMAIL_SENDGRID_FROM_EMAIL]: sendgridFromEmail,
+        [ORG_EMAIL_KEYS.EMAIL_SENDGRID_FROM_NAME]: sendgridFromName,
+        // Amazon SES
+        [ORG_EMAIL_KEYS.EMAIL_SES_ACCESS_KEY_ID]: sesAccessKeyId,
+        [ORG_EMAIL_KEYS.EMAIL_SES_SECRET_ACCESS_KEY]: sesSecretAccessKey,
+        [ORG_EMAIL_KEYS.EMAIL_SES_REGION]: sesRegion,
+        [ORG_EMAIL_KEYS.EMAIL_SES_FROM_EMAIL]: sesFromEmail,
+        [ORG_EMAIL_KEYS.EMAIL_SES_FROM_NAME]: sesFromName,
+      };
+
+      const result = await setEmailSettings(data);
+      if (result.success) {
+        toast.success("Email settings saved");
+        router.refresh();
+      } else {
+        toast.error(result.error ?? "Failed to save settings");
+      }
+    });
+  };
+
+  const handleTestEmail = async () => {
+    setIsTesting(true);
+    try {
+      const result = await testOrgEmailConnection();
+      if (result.success) {
+        toast.success(`Test email sent to ${result.data?.sentTo}`);
+      } else {
+        toast.error(result.error ?? "Email test failed");
+      }
+    } finally {
+      setIsTesting(false);
+    }
+  };
+
+  const isTestDisabled =
+    isTesting ||
+    !useCustom ||
+    (emailProvider === "smtp" && !smtpHost) ||
+    (emailProvider === "resend" && !resendApiKey) ||
+    (emailProvider === "postmark" && !postmarkApiKey) ||
+    (emailProvider === "mailgun" && !mailgunApiKey) ||
+    (emailProvider === "sendgrid" && !sendgridApiKey) ||
+    (emailProvider === "ses" && !sesAccessKeyId);
+
+  return (
+    <div className="space-y-6">
+      <Card>
+        <CardHeader>
+          <CardTitle>Email Provider</CardTitle>
+          <CardDescription>
+            Configure your organization&apos;s email provider for sending
+            invoices and quotes. If not configured, the platform&apos;s default
+            email provider will be used.
+          </CardDescription>
+        </CardHeader>
+        <CardContent className="space-y-6">
+          <div className="flex items-center justify-between">
+            <div className="space-y-0.5">
+              <Label htmlFor="use-custom-email">
+                Use custom email provider
+              </Label>
+              <p className="text-xs text-muted-foreground">
+                Enable to configure your own email provider instead of using the
+                platform default
+              </p>
+            </div>
+            <Switch
+              id="use-custom-email"
+              checked={useCustom}
+              onCheckedChange={setUseCustom}
+            />
+          </div>
+
+          {!useCustom && (
+            <div className="flex items-start gap-3 rounded-lg border bg-muted/50 p-4">
+              <Info className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
+              <p className="text-sm text-muted-foreground">
+                Your organization is using the platform&apos;s default email
+                provider. Invoices and quotes will be sent using the
+                platform-configured email settings.
+              </p>
+            </div>
+          )}
+
+          {useCustom && (
+            <>
+              <div className="flex flex-wrap gap-2">
+                <Button
+                  type="button"
+                  variant={emailProvider === "smtp" ? "default" : "outline"}
+                  onClick={() => setEmailProvider("smtp")}
+                  className="flex-1"
+                >
+                  SMTP
+                </Button>
+                <Button
+                  type="button"
+                  variant={emailProvider === "resend" ? "default" : "outline"}
+                  onClick={() => setEmailProvider("resend")}
+                  className="flex-1"
+                >
+                  Resend
+                </Button>
+                <Button
+                  type="button"
+                  variant={emailProvider === "postmark" ? "default" : "outline"}
+                  onClick={() => setEmailProvider("postmark")}
+                  className="flex-1"
+                >
+                  Postmark
+                </Button>
+                <Button
+                  type="button"
+                  variant={emailProvider === "mailgun" ? "default" : "outline"}
+                  onClick={() => setEmailProvider("mailgun")}
+                  className="flex-1"
+                >
+                  Mailgun
+                </Button>
+                <Button
+                  type="button"
+                  variant={
+                    emailProvider === "sendgrid" ? "default" : "outline"
+                  }
+                  onClick={() => setEmailProvider("sendgrid")}
+                  className="flex-1"
+                >
+                  SendGrid
+                </Button>
+                <Button
+                  type="button"
+                  variant={emailProvider === "ses" ? "default" : "outline"}
+                  onClick={() => setEmailProvider("ses")}
+                  className="flex-1"
+                >
+                  Amazon SES
+                </Button>
+              </div>
+
+              {emailProvider === "smtp" && (
+                <>
+                  <div className="grid gap-4 sm:grid-cols-2">
+                    <div className="space-y-2">
+                      <Label htmlFor="org-smtp-host">SMTP Host</Label>
+                      <Input
+                        id="org-smtp-host"
+                        placeholder="smtp.example.com"
+                        value={smtpHost}
+                        onChange={(e) => setSmtpHost(e.target.value)}
+                      />
+                    </div>
+                    <div className="space-y-2">
+                      <Label htmlFor="org-smtp-port">SMTP Port</Label>
+                      <Input
+                        id="org-smtp-port"
+                        placeholder="587"
+                        value={smtpPort}
+                        onChange={(e) => setSmtpPort(e.target.value)}
+                      />
+                    </div>
+                  </div>
+
+                  <div className="grid gap-4 sm:grid-cols-2">
+                    <div className="space-y-2">
+                      <Label htmlFor="org-smtp-user">Username</Label>
+                      <Input
+                        id="org-smtp-user"
+                        placeholder="user@example.com"
+                        value={smtpUser}
+                        onChange={(e) => setSmtpUser(e.target.value)}
+                      />
+                    </div>
+                    <div className="space-y-2">
+                      <Label htmlFor="org-smtp-pass">Password</Label>
+                      <Input
+                        id="org-smtp-pass"
+                        type="password"
+                        placeholder="••••••••"
+                        value={smtpPass}
+                        onChange={(e) => setSmtpPass(e.target.value)}
+                      />
+                    </div>
+                  </div>
+
+                  <div className="grid gap-4 sm:grid-cols-2">
+                    <div className="space-y-2">
+                      <Label htmlFor="org-smtp-from-email">From Email</Label>
+                      <Input
+                        id="org-smtp-from-email"
+                        placeholder="noreply@example.com"
+                        value={smtpFromEmail}
+                        onChange={(e) => setSmtpFromEmail(e.target.value)}
+                      />
+                    </div>
+                    <div className="space-y-2">
+                      <Label htmlFor="org-smtp-from-name">From Name</Label>
+                      <Input
+                        id="org-smtp-from-name"
+                        placeholder="Your Workshop"
+                        value={smtpFromName}
+                        onChange={(e) => setSmtpFromName(e.target.value)}
+                      />
+                    </div>
+                  </div>
+
+                  <div className="space-y-4">
+                    <div className="flex items-center justify-between">
+                      <div className="space-y-0.5">
+                        <Label htmlFor="org-smtp-secure">
+                          TLS Connection (Port 465)
+                        </Label>
+                        <p className="text-xs text-muted-foreground">
+                          Enable for implicit TLS. Disable for STARTTLS (port
+                          587/25).
+                        </p>
+                      </div>
+                      <Switch
+                        id="org-smtp-secure"
+                        checked={smtpSecure}
+                        onCheckedChange={setSmtpSecure}
+                      />
+                    </div>
+
+                    <div className="flex items-center justify-between">
+                      <div className="space-y-0.5">
+                        <Label htmlFor="org-smtp-reject-unauthorized">
+                          Verify TLS Certificates
+                        </Label>
+                        <p className="text-xs text-muted-foreground">
+                          Disable to allow self-signed certificates (not
+                          recommended for production)
+                        </p>
+                      </div>
+                      <Switch
+                        id="org-smtp-reject-unauthorized"
+                        checked={smtpRejectUnauthorized}
+                        onCheckedChange={setSmtpRejectUnauthorized}
+                      />
+                    </div>
+
+                    <div className="flex items-center justify-between">
+                      <div className="space-y-0.5">
+                        <Label htmlFor="org-smtp-require-tls">
+                          Require TLS Upgrade
+                        </Label>
+                        <p className="text-xs text-muted-foreground">
+                          Force TLS upgrade on STARTTLS connections
+                        </p>
+                      </div>
+                      <Switch
+                        id="org-smtp-require-tls"
+                        checked={smtpRequireTls}
+                        onCheckedChange={setSmtpRequireTls}
+                      />
+                    </div>
+                  </div>
+                </>
+              )}
+
+              {emailProvider === "resend" && (
+                <>
+                  <div className="space-y-2">
+                    <Label htmlFor="org-resend-api-key">API Key</Label>
+                    <Input
+                      id="org-resend-api-key"
+                      type="password"
+                      placeholder="re_••••••••"
+                      value={resendApiKey}
+                      onChange={(e) => setResendApiKey(e.target.value)}
+                    />
+                    <p className="text-xs text-muted-foreground">
+                      Get your API key from{" "}
+                      <a
+                        href="https://resend.com"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                        className="underline"
+                      >
+                        resend.com
+                      </a>
+                    </p>
+                  </div>
+
+                  <div className="grid gap-4 sm:grid-cols-2">
+                    <div className="space-y-2">
+                      <Label htmlFor="org-resend-from-email">From Email</Label>
+                      <Input
+                        id="org-resend-from-email"
+                        placeholder="noreply@yourdomain.com"
+                        value={resendFromEmail}
+                        onChange={(e) => setResendFromEmail(e.target.value)}
+                      />
+                    </div>
+                    <div className="space-y-2">
+                      <Label htmlFor="org-resend-from-name">From Name</Label>
+                      <Input
+                        id="org-resend-from-name"
+                        placeholder="Your Workshop"
+                        value={resendFromName}
+                        onChange={(e) => setResendFromName(e.target.value)}
+                      />
+                    </div>
+                  </div>
+                </>
+              )}
+
+              {emailProvider === "postmark" && (
+                <>
+                  <div className="space-y-2">
+                    <Label htmlFor="org-postmark-api-key">Server Token</Label>
+                    <Input
+                      id="org-postmark-api-key"
+                      type="password"
+                      placeholder="••••••••-••••-••••-••••-••••••••••••"
+                      value={postmarkApiKey}
+                      onChange={(e) => setPostmarkApiKey(e.target.value)}
+                    />
+                    <p className="text-xs text-muted-foreground">
+                      Get your Server Token from{" "}
+                      <a
+                        href="https://postmarkapp.com"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                        className="underline"
+                      >
+                        postmarkapp.com
+                      </a>
+                    </p>
+                  </div>
+
+                  <div className="grid gap-4 sm:grid-cols-2">
+                    <div className="space-y-2">
+                      <Label htmlFor="org-postmark-from-email">
+                        From Email
+                      </Label>
+                      <Input
+                        id="org-postmark-from-email"
+                        placeholder="noreply@yourdomain.com"
+                        value={postmarkFromEmail}
+                        onChange={(e) => setPostmarkFromEmail(e.target.value)}
+                      />
+                    </div>
+                    <div className="space-y-2">
+                      <Label htmlFor="org-postmark-from-name">From Name</Label>
+                      <Input
+                        id="org-postmark-from-name"
+                        placeholder="Your Workshop"
+                        value={postmarkFromName}
+                        onChange={(e) => setPostmarkFromName(e.target.value)}
+                      />
+                    </div>
+                  </div>
+                </>
+              )}
+
+              {emailProvider === "mailgun" && (
+                <>
+                  <div className="space-y-2">
+                    <Label htmlFor="org-mailgun-api-key">API Key</Label>
+                    <Input
+                      id="org-mailgun-api-key"
+                      type="password"
+                      placeholder="key-••••••••••••••••••••••••••••••••"
+                      value={mailgunApiKey}
+                      onChange={(e) => setMailgunApiKey(e.target.value)}
+                    />
+                    <p className="text-xs text-muted-foreground">
+                      Get your API key from{" "}
+                      <a
+                        href="https://app.mailgun.com"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                        className="underline"
+                      >
+                        app.mailgun.com
+                      </a>
+                    </p>
+                  </div>
+
+                  <div className="grid gap-4 sm:grid-cols-2">
+                    <div className="space-y-2">
+                      <Label htmlFor="org-mailgun-domain">Domain</Label>
+                      <Input
+                        id="org-mailgun-domain"
+                        placeholder="mg.yourdomain.com"
+                        value={mailgunDomain}
+                        onChange={(e) => setMailgunDomain(e.target.value)}
+                      />
+                    </div>
+                    <div className="space-y-2">
+                      <Label htmlFor="org-mailgun-region">Region</Label>
+                      <div className="flex gap-2">
+                        <Button
+                          type="button"
+                          variant={
+                            mailgunRegion === "us" ? "default" : "outline"
+                          }
+                          onClick={() => setMailgunRegion("us")}
+                          className="flex-1"
+                          size="sm"
+                        >
+                          US
+                        </Button>
+                        <Button
+                          type="button"
+                          variant={
+                            mailgunRegion === "eu" ? "default" : "outline"
+                          }
+                          onClick={() => setMailgunRegion("eu")}
+                          className="flex-1"
+                          size="sm"
+                        >
+                          EU
+                        </Button>
+                      </div>
+                    </div>
+                  </div>
+
+                  <div className="grid gap-4 sm:grid-cols-2">
+                    <div className="space-y-2">
+                      <Label htmlFor="org-mailgun-from-email">
+                        From Email
+                      </Label>
+                      <Input
+                        id="org-mailgun-from-email"
+                        placeholder="noreply@yourdomain.com"
+                        value={mailgunFromEmail}
+                        onChange={(e) => setMailgunFromEmail(e.target.value)}
+                      />
+                    </div>
+                    <div className="space-y-2">
+                      <Label htmlFor="org-mailgun-from-name">From Name</Label>
+                      <Input
+                        id="org-mailgun-from-name"
+                        placeholder="Your Workshop"
+                        value={mailgunFromName}
+                        onChange={(e) => setMailgunFromName(e.target.value)}
+                      />
+                    </div>
+                  </div>
+                </>
+              )}
+
+              {emailProvider === "sendgrid" && (
+                <>
+                  <div className="space-y-2">
+                    <Label htmlFor="org-sendgrid-api-key">API Key</Label>
+                    <Input
+                      id="org-sendgrid-api-key"
+                      type="password"
+                      placeholder="SG.••••••••••••••••••••••••••••••••"
+                      value={sendgridApiKey}
+                      onChange={(e) => setSendgridApiKey(e.target.value)}
+                    />
+                    <p className="text-xs text-muted-foreground">
+                      Get your API key from{" "}
+                      <a
+                        href="https://app.sendgrid.com"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                        className="underline"
+                      >
+                        app.sendgrid.com
+                      </a>
+                    </p>
+                  </div>
+
+                  <div className="grid gap-4 sm:grid-cols-2">
+                    <div className="space-y-2">
+                      <Label htmlFor="org-sendgrid-from-email">
+                        From Email
+                      </Label>
+                      <Input
+                        id="org-sendgrid-from-email"
+                        placeholder="noreply@yourdomain.com"
+                        value={sendgridFromEmail}
+                        onChange={(e) => setSendgridFromEmail(e.target.value)}
+                      />
+                    </div>
+                    <div className="space-y-2">
+                      <Label htmlFor="org-sendgrid-from-name">From Name</Label>
+                      <Input
+                        id="org-sendgrid-from-name"
+                        placeholder="Your Workshop"
+                        value={sendgridFromName}
+                        onChange={(e) => setSendgridFromName(e.target.value)}
+                      />
+                    </div>
+                  </div>
+                </>
+              )}
+
+              {emailProvider === "ses" && (
+                <>
+                  <div className="grid gap-4 sm:grid-cols-2">
+                    <div className="space-y-2">
+                      <Label htmlFor="org-ses-access-key">
+                        Access Key ID
+                      </Label>
+                      <Input
+                        id="org-ses-access-key"
+                        type="password"
+                        placeholder="AKIA••••••••••••••••"
+                        value={sesAccessKeyId}
+                        onChange={(e) => setSesAccessKeyId(e.target.value)}
+                      />
+                    </div>
+                    <div className="space-y-2">
+                      <Label htmlFor="org-ses-secret-key">
+                        Secret Access Key
+                      </Label>
+                      <Input
+                        id="org-ses-secret-key"
+                        type="password"
+                        placeholder="••••••••••••••••••••••••••••••••••••••••"
+                        value={sesSecretAccessKey}
+                        onChange={(e) => setSesSecretAccessKey(e.target.value)}
+                      />
+                    </div>
+                  </div>
+
+                  <div className="space-y-2">
+                    <Label htmlFor="org-ses-region">AWS Region</Label>
+                    <Input
+                      id="org-ses-region"
+                      placeholder="us-east-1"
+                      value={sesRegion}
+                      onChange={(e) => setSesRegion(e.target.value)}
+                    />
+                    <p className="text-xs text-muted-foreground">
+                      The AWS region where SES is configured (e.g. us-east-1,
+                      eu-west-1)
+                    </p>
+                  </div>
+
+                  <div className="grid gap-4 sm:grid-cols-2">
+                    <div className="space-y-2">
+                      <Label htmlFor="org-ses-from-email">From Email</Label>
+                      <Input
+                        id="org-ses-from-email"
+                        placeholder="noreply@yourdomain.com"
+                        value={sesFromEmail}
+                        onChange={(e) => setSesFromEmail(e.target.value)}
+                      />
+                    </div>
+                    <div className="space-y-2">
+                      <Label htmlFor="org-ses-from-name">From Name</Label>
+                      <Input
+                        id="org-ses-from-name"
+                        placeholder="Your Workshop"
+                        value={sesFromName}
+                        onChange={(e) => setSesFromName(e.target.value)}
+                      />
+                    </div>
+                  </div>
+                </>
+              )}
+
+              <div className="flex items-center gap-2 pt-2">
+                <Button
+                  type="button"
+                  variant="outline"
+                  onClick={handleTestEmail}
+                  disabled={isTestDisabled}
+                >
+                  {isTesting ? (
+                    <Loader2 className="mr-2 h-4 w-4 animate-spin" />
+                  ) : (
+                    <Send className="mr-2 h-4 w-4" />
+                  )}
+                  Send Test Email
+                </Button>
+                <p className="text-xs text-muted-foreground">
+                  Save settings first, then send a test email to your account
+                </p>
+              </div>
+            </>
+          )}
+        </CardContent>
+      </Card>
+
+      <div className="flex justify-end">
+        <Button onClick={handleSave} disabled={isPending}>
+          {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+          Save Settings
+        </Button>
+      </div>
+    </div>
+  );
+}

+ 47 - 0
src/features/email/Schema/emailSettingsSchema.ts

@@ -0,0 +1,47 @@
+export const ORG_EMAIL_KEYS = {
+  EMAIL_PROVIDER: "email.provider",
+
+  // SMTP
+  EMAIL_SMTP_HOST: "email.smtp.host",
+  EMAIL_SMTP_PORT: "email.smtp.port",
+  EMAIL_SMTP_USER: "email.smtp.user",
+  EMAIL_SMTP_PASS: "email.smtp.pass",
+  EMAIL_SMTP_SECURE: "email.smtp.secure",
+  EMAIL_SMTP_FROM_EMAIL: "email.smtp.fromEmail",
+  EMAIL_SMTP_FROM_NAME: "email.smtp.fromName",
+  EMAIL_SMTP_REJECT_UNAUTHORIZED: "email.smtp.rejectUnauthorized",
+  EMAIL_SMTP_REQUIRE_TLS: "email.smtp.requireTls",
+
+  // Resend
+  EMAIL_RESEND_API_KEY: "email.resend.apiKey",
+  EMAIL_RESEND_FROM_EMAIL: "email.resend.fromEmail",
+  EMAIL_RESEND_FROM_NAME: "email.resend.fromName",
+
+  // Postmark
+  EMAIL_POSTMARK_API_KEY: "email.postmark.apiKey",
+  EMAIL_POSTMARK_FROM_EMAIL: "email.postmark.fromEmail",
+  EMAIL_POSTMARK_FROM_NAME: "email.postmark.fromName",
+
+  // Mailgun
+  EMAIL_MAILGUN_API_KEY: "email.mailgun.apiKey",
+  EMAIL_MAILGUN_DOMAIN: "email.mailgun.domain",
+  EMAIL_MAILGUN_REGION: "email.mailgun.region",
+  EMAIL_MAILGUN_FROM_EMAIL: "email.mailgun.fromEmail",
+  EMAIL_MAILGUN_FROM_NAME: "email.mailgun.fromName",
+
+  // SendGrid
+  EMAIL_SENDGRID_API_KEY: "email.sendgrid.apiKey",
+  EMAIL_SENDGRID_FROM_EMAIL: "email.sendgrid.fromEmail",
+  EMAIL_SENDGRID_FROM_NAME: "email.sendgrid.fromName",
+
+  // Amazon SES
+  EMAIL_SES_ACCESS_KEY_ID: "email.ses.accessKeyId",
+  EMAIL_SES_SECRET_ACCESS_KEY: "email.ses.secretAccessKey",
+  EMAIL_SES_REGION: "email.ses.region",
+  EMAIL_SES_FROM_EMAIL: "email.ses.fromEmail",
+  EMAIL_SES_FROM_NAME: "email.ses.fromName",
+} as const;
+
+export type OrgEmailKey = (typeof ORG_EMAIL_KEYS)[keyof typeof ORG_EMAIL_KEYS];
+
+export const ALL_ORG_EMAIL_KEYS = Object.values(ORG_EMAIL_KEYS);

+ 338 - 145
src/lib/email.ts

@@ -7,6 +7,7 @@ import sgMail, { type MailDataRequired } from "@sendgrid/mail";
 import { SESClient, SendRawEmailCommand } from "@aws-sdk/client-ses";
 import { SESClient, SendRawEmailCommand } from "@aws-sdk/client-ses";
 import { db } from "./db";
 import { db } from "./db";
 import { SYSTEM_SETTING_KEYS } from "@/features/admin/Schema/systemSettingsSchema";
 import { SYSTEM_SETTING_KEYS } from "@/features/admin/Schema/systemSettingsSchema";
+import { ORG_EMAIL_KEYS } from "@/features/email/Schema/emailSettingsSchema";
 
 
 export type EmailProvider =
 export type EmailProvider =
   | "smtp"
   | "smtp"
@@ -27,6 +28,29 @@ export interface SendMailOptions {
   }[];
   }[];
 }
 }
 
 
+// ─── Settings map helper ────────────────────────────────────────────────────
+
+type SettingsMap = Map<string, string>;
+
+async function getSystemSettings(keys: string[]): Promise<SettingsMap> {
+  const rows = await db.systemSetting.findMany({
+    where: { key: { in: keys } },
+  });
+  return new Map(rows.map((r) => [r.key, r.value]));
+}
+
+async function getOrgSettings(
+  organizationId: string,
+  keys: string[],
+): Promise<SettingsMap> {
+  const rows = await db.appSetting.findMany({
+    where: { organizationId, key: { in: keys } },
+  });
+  return new Map(rows.map((r) => [r.key, r.value]));
+}
+
+// ─── System-level helpers (read from SystemSetting + env) ───────────────────
+
 async function getEmailProvider(): Promise<EmailProvider> {
 async function getEmailProvider(): Promise<EmailProvider> {
   const setting = await db.systemSetting.findUnique({
   const setting = await db.systemSetting.findUnique({
     where: { key: SYSTEM_SETTING_KEYS.EMAIL_PROVIDER },
     where: { key: SYSTEM_SETTING_KEYS.EMAIL_PROVIDER },
@@ -47,10 +71,7 @@ async function getEmailProvider(): Promise<EmailProvider> {
 export async function getFromAddress(): Promise<string> {
 export async function getFromAddress(): Promise<string> {
   const provider = await getEmailProvider();
   const provider = await getEmailProvider();
 
 
-  const keyMap: Record<
-    EmailProvider,
-    { email: string; name: string }
-  > = {
+  const keyMap: Record<EmailProvider, { email: string; name: string }> = {
     smtp: {
     smtp: {
       email: SYSTEM_SETTING_KEYS.SMTP_FROM_EMAIL,
       email: SYSTEM_SETTING_KEYS.SMTP_FROM_EMAIL,
       name: SYSTEM_SETTING_KEYS.SMTP_FROM_NAME,
       name: SYSTEM_SETTING_KEYS.SMTP_FROM_NAME,
@@ -92,81 +113,99 @@ export async function getFromAddress(): Promise<string> {
 
 
 // ─── SMTP ──────────────────────────────────────────────────────────────────
 // ─── SMTP ──────────────────────────────────────────────────────────────────
 
 
-async function getSmtpSettings() {
-  const keys = [
-    SYSTEM_SETTING_KEYS.SMTP_HOST,
-    SYSTEM_SETTING_KEYS.SMTP_PORT,
-    SYSTEM_SETTING_KEYS.SMTP_USER,
-    SYSTEM_SETTING_KEYS.SMTP_PASS,
-    SYSTEM_SETTING_KEYS.SMTP_SECURE,
-    SYSTEM_SETTING_KEYS.SMTP_REJECT_UNAUTHORIZED,
-    SYSTEM_SETTING_KEYS.SMTP_REQUIRE_TLS,
-  ];
-
-  const rows = await db.systemSetting.findMany({
-    where: { key: { in: keys } },
-  });
-
-  const map = new Map(rows.map((r) => [r.key, r.value]));
-  return map;
+interface SmtpConfig {
+  host: string;
+  port: number;
+  user?: string;
+  pass?: string;
+  secure: boolean;
+  rejectUnauthorized: boolean;
+  requireTls: boolean;
 }
 }
 
 
-async function getTransporter() {
-  const settings = await getSmtpSettings();
-
-  const host =
-    settings.get(SYSTEM_SETTING_KEYS.SMTP_HOST) || process.env.SMTP_HOST;
+function buildSmtpConfig(
+  settings: SettingsMap,
+  keys: {
+    host: string;
+    port: string;
+    user: string;
+    pass: string;
+    secure: string;
+    rejectUnauthorized: string;
+    requireTls: string;
+  },
+  useEnvFallback: boolean,
+): SmtpConfig {
+  const host = settings.get(keys.host) || (useEnvFallback ? process.env.SMTP_HOST : undefined);
   const port =
   const port =
-    Number(settings.get(SYSTEM_SETTING_KEYS.SMTP_PORT)) ||
-    Number(process.env.SMTP_PORT) ||
+    Number(settings.get(keys.port)) ||
+    (useEnvFallback ? Number(process.env.SMTP_PORT) : 0) ||
     587;
     587;
-  const user =
-    settings.get(SYSTEM_SETTING_KEYS.SMTP_USER) || process.env.SMTP_USER;
-  const pass =
-    settings.get(SYSTEM_SETTING_KEYS.SMTP_PASS) || process.env.SMTP_PASS;
+  const user = settings.get(keys.user) || (useEnvFallback ? process.env.SMTP_USER : undefined);
+  const pass = settings.get(keys.pass) || (useEnvFallback ? process.env.SMTP_PASS : undefined);
 
 
-  const secureSetting = settings.get(SYSTEM_SETTING_KEYS.SMTP_SECURE);
+  const secureSetting = settings.get(keys.secure);
   const secure =
   const secure =
     secureSetting !== undefined
     secureSetting !== undefined
       ? secureSetting === "true"
       ? secureSetting === "true"
-      : process.env.SMTP_SECURE === "true";
+      : useEnvFallback
+        ? process.env.SMTP_SECURE === "true"
+        : false;
 
 
-  const rejectSetting = settings.get(
-    SYSTEM_SETTING_KEYS.SMTP_REJECT_UNAUTHORIZED,
-  );
+  const rejectSetting = settings.get(keys.rejectUnauthorized);
   const rejectUnauthorized =
   const rejectUnauthorized =
     rejectSetting !== undefined
     rejectSetting !== undefined
       ? rejectSetting !== "false"
       ? rejectSetting !== "false"
-      : process.env.SMTP_REJECT_UNAUTHORIZED !== "false";
+      : useEnvFallback
+        ? process.env.SMTP_REJECT_UNAUTHORIZED !== "false"
+        : true;
 
 
-  const requireTlsSetting = settings.get(SYSTEM_SETTING_KEYS.SMTP_REQUIRE_TLS);
+  const requireTlsSetting = settings.get(keys.requireTls);
   const requireTls = requireTlsSetting === "true";
   const requireTls = requireTlsSetting === "true";
 
 
   if (!host) {
   if (!host) {
     throw new Error(
     throw new Error(
-      "SMTP is not configured. Configure SMTP in Admin Settings or add SMTP_HOST to your .env file.",
+      "SMTP is not configured. Configure SMTP in your email settings.",
     );
     );
   }
   }
 
 
+  return { host, port, user, pass, secure, rejectUnauthorized, requireTls };
+}
+
+function createSmtpTransporter(config: SmtpConfig) {
   return nodemailer.createTransport({
   return nodemailer.createTransport({
-    host,
-    port,
-    secure,
-    auth: user
+    host: config.host,
+    port: config.port,
+    secure: config.secure,
+    auth: config.user
       ? {
       ? {
-          user,
-          pass,
+          user: config.user,
+          pass: config.pass,
         }
         }
       : undefined,
       : undefined,
     tls: {
     tls: {
-      rejectUnauthorized,
+      rejectUnauthorized: config.rejectUnauthorized,
     },
     },
-    requireTLS: requireTls,
+    requireTLS: config.requireTls,
   });
   });
 }
 }
 
 
-async function sendViaSmtp(options: SendMailOptions) {
-  const transporter = await getTransporter();
+async function sendViaSmtpWithSettings(
+  options: SendMailOptions,
+  settings: SettingsMap,
+  keys: {
+    host: string;
+    port: string;
+    user: string;
+    pass: string;
+    secure: string;
+    rejectUnauthorized: string;
+    requireTls: string;
+  },
+  useEnvFallback: boolean,
+) {
+  const config = buildSmtpConfig(settings, keys, useEnvFallback);
+  const transporter = createSmtpTransporter(config);
   await transporter.sendMail({
   await transporter.sendMail({
     from: options.from,
     from: options.from,
     to: options.to,
     to: options.to,
@@ -179,35 +218,41 @@ async function sendViaSmtp(options: SendMailOptions) {
   });
   });
 }
 }
 
 
-// ─── Resend ────────────────────────────────────────────────────────────────
-
-async function getResendSettings() {
+async function sendViaSmtp(options: SendMailOptions) {
   const keys = [
   const keys = [
-    SYSTEM_SETTING_KEYS.RESEND_API_KEY,
-    SYSTEM_SETTING_KEYS.RESEND_FROM_EMAIL,
-    SYSTEM_SETTING_KEYS.RESEND_FROM_NAME,
+    SYSTEM_SETTING_KEYS.SMTP_HOST,
+    SYSTEM_SETTING_KEYS.SMTP_PORT,
+    SYSTEM_SETTING_KEYS.SMTP_USER,
+    SYSTEM_SETTING_KEYS.SMTP_PASS,
+    SYSTEM_SETTING_KEYS.SMTP_SECURE,
+    SYSTEM_SETTING_KEYS.SMTP_REJECT_UNAUTHORIZED,
+    SYSTEM_SETTING_KEYS.SMTP_REQUIRE_TLS,
   ];
   ];
-
-  const rows = await db.systemSetting.findMany({
-    where: { key: { in: keys } },
-  });
-
-  const map = new Map(rows.map((r) => [r.key, r.value]));
-  return map;
+  const settings = await getSystemSettings(keys);
+  await sendViaSmtpWithSettings(options, settings, {
+    host: SYSTEM_SETTING_KEYS.SMTP_HOST,
+    port: SYSTEM_SETTING_KEYS.SMTP_PORT,
+    user: SYSTEM_SETTING_KEYS.SMTP_USER,
+    pass: SYSTEM_SETTING_KEYS.SMTP_PASS,
+    secure: SYSTEM_SETTING_KEYS.SMTP_SECURE,
+    rejectUnauthorized: SYSTEM_SETTING_KEYS.SMTP_REJECT_UNAUTHORIZED,
+    requireTls: SYSTEM_SETTING_KEYS.SMTP_REQUIRE_TLS,
+  }, true);
 }
 }
 
 
-async function sendViaResend(options: SendMailOptions) {
-  const settings = await getResendSettings();
-  const apiKey = settings.get(SYSTEM_SETTING_KEYS.RESEND_API_KEY);
+// ─── Resend ────────────────────────────────────────────────────────────────
 
 
+async function sendViaResendWithSettings(
+  options: SendMailOptions,
+  settings: SettingsMap,
+  apiKeyField: string,
+) {
+  const apiKey = settings.get(apiKeyField);
   if (!apiKey) {
   if (!apiKey) {
-    throw new Error(
-      "Resend is not configured. Add your Resend API key in Admin Settings.",
-    );
+    throw new Error("Resend is not configured. Add your Resend API key.");
   }
   }
 
 
   const resend = new Resend(apiKey);
   const resend = new Resend(apiKey);
-
   await resend.emails.send({
   await resend.emails.send({
     from: options.from,
     from: options.from,
     to: options.to,
     to: options.to,
@@ -220,21 +265,21 @@ async function sendViaResend(options: SendMailOptions) {
   });
   });
 }
 }
 
 
-// ─── Postmark ──────────────────────────────────────────────────────────────
+async function sendViaResend(options: SendMailOptions) {
+  const settings = await getSystemSettings([SYSTEM_SETTING_KEYS.RESEND_API_KEY]);
+  await sendViaResendWithSettings(options, settings, SYSTEM_SETTING_KEYS.RESEND_API_KEY);
+}
 
 
-async function sendViaPostmark(options: SendMailOptions) {
-  const rows = await db.systemSetting.findMany({
-    where: {
-      key: { in: [SYSTEM_SETTING_KEYS.POSTMARK_API_KEY] },
-    },
-  });
-  const map = new Map(rows.map((r) => [r.key, r.value]));
-  const apiKey = map.get(SYSTEM_SETTING_KEYS.POSTMARK_API_KEY);
+// ─── Postmark ──────────────────────────────────────────────────────────────
 
 
+async function sendViaPostmarkWithSettings(
+  options: SendMailOptions,
+  settings: SettingsMap,
+  apiKeyField: string,
+) {
+  const apiKey = settings.get(apiKeyField);
   if (!apiKey) {
   if (!apiKey) {
-    throw new Error(
-      "Postmark is not configured. Add your Server Token in Admin Settings.",
-    );
+    throw new Error("Postmark is not configured. Add your Server Token.");
   }
   }
 
 
   const client = new PostmarkClient(apiKey);
   const client = new PostmarkClient(apiKey);
@@ -262,27 +307,24 @@ async function sendViaPostmark(options: SendMailOptions) {
   }
   }
 }
 }
 
 
-// ─── Mailgun ───────────────────────────────────────────────────────────────
+async function sendViaPostmark(options: SendMailOptions) {
+  const settings = await getSystemSettings([SYSTEM_SETTING_KEYS.POSTMARK_API_KEY]);
+  await sendViaPostmarkWithSettings(options, settings, SYSTEM_SETTING_KEYS.POSTMARK_API_KEY);
+}
 
 
-async function sendViaMailgun(options: SendMailOptions) {
-  const keys = [
-    SYSTEM_SETTING_KEYS.MAILGUN_API_KEY,
-    SYSTEM_SETTING_KEYS.MAILGUN_DOMAIN,
-    SYSTEM_SETTING_KEYS.MAILGUN_REGION,
-  ];
-  const rows = await db.systemSetting.findMany({
-    where: { key: { in: keys } },
-  });
-  const map = new Map(rows.map((r) => [r.key, r.value]));
+// ─── Mailgun ───────────────────────────────────────────────────────────────
 
 
-  const apiKey = map.get(SYSTEM_SETTING_KEYS.MAILGUN_API_KEY);
-  const domain = map.get(SYSTEM_SETTING_KEYS.MAILGUN_DOMAIN);
-  const region = map.get(SYSTEM_SETTING_KEYS.MAILGUN_REGION) || "us";
+async function sendViaMailgunWithSettings(
+  options: SendMailOptions,
+  settings: SettingsMap,
+  keys: { apiKey: string; domain: string; region: string },
+) {
+  const apiKey = settings.get(keys.apiKey);
+  const domain = settings.get(keys.domain);
+  const region = settings.get(keys.region) || "us";
 
 
   if (!apiKey || !domain) {
   if (!apiKey || !domain) {
-    throw new Error(
-      "Mailgun is not configured. Add your API key and domain in Admin Settings.",
-    );
+    throw new Error("Mailgun is not configured. Add your API key and domain.");
   }
   }
 
 
   const mailgun = new Mailgun(FormData);
   const mailgun = new Mailgun(FormData);
@@ -306,21 +348,30 @@ async function sendViaMailgun(options: SendMailOptions) {
   });
   });
 }
 }
 
 
-// ─── SendGrid ──────────────────────────────────────────────────────────────
-
-async function sendViaSendGrid(options: SendMailOptions) {
-  const rows = await db.systemSetting.findMany({
-    where: {
-      key: { in: [SYSTEM_SETTING_KEYS.SENDGRID_API_KEY] },
-    },
+async function sendViaMailgun(options: SendMailOptions) {
+  const keys = [
+    SYSTEM_SETTING_KEYS.MAILGUN_API_KEY,
+    SYSTEM_SETTING_KEYS.MAILGUN_DOMAIN,
+    SYSTEM_SETTING_KEYS.MAILGUN_REGION,
+  ];
+  const settings = await getSystemSettings(keys);
+  await sendViaMailgunWithSettings(options, settings, {
+    apiKey: SYSTEM_SETTING_KEYS.MAILGUN_API_KEY,
+    domain: SYSTEM_SETTING_KEYS.MAILGUN_DOMAIN,
+    region: SYSTEM_SETTING_KEYS.MAILGUN_REGION,
   });
   });
-  const map = new Map(rows.map((r) => [r.key, r.value]));
-  const apiKey = map.get(SYSTEM_SETTING_KEYS.SENDGRID_API_KEY);
+}
 
 
+// ─── SendGrid ──────────────────────────────────────────────────────────────
+
+async function sendViaSendGridWithSettings(
+  options: SendMailOptions,
+  settings: SettingsMap,
+  apiKeyField: string,
+) {
+  const apiKey = settings.get(apiKeyField);
   if (!apiKey) {
   if (!apiKey) {
-    throw new Error(
-      "SendGrid is not configured. Add your API key in Admin Settings.",
-    );
+    throw new Error("SendGrid is not configured. Add your API key.");
   }
   }
 
 
   sgMail.setApiKey(apiKey);
   sgMail.setApiKey(apiKey);
@@ -344,30 +395,26 @@ async function sendViaSendGrid(options: SendMailOptions) {
   await sgMail.send(msg);
   await sgMail.send(msg);
 }
 }
 
 
-// ─── Amazon SES ────────────────────────────────────────────────────────────
+async function sendViaSendGrid(options: SendMailOptions) {
+  const settings = await getSystemSettings([SYSTEM_SETTING_KEYS.SENDGRID_API_KEY]);
+  await sendViaSendGridWithSettings(options, settings, SYSTEM_SETTING_KEYS.SENDGRID_API_KEY);
+}
 
 
-async function sendViaSes(options: SendMailOptions) {
-  const keys = [
-    SYSTEM_SETTING_KEYS.SES_ACCESS_KEY_ID,
-    SYSTEM_SETTING_KEYS.SES_SECRET_ACCESS_KEY,
-    SYSTEM_SETTING_KEYS.SES_REGION,
-  ];
-  const rows = await db.systemSetting.findMany({
-    where: { key: { in: keys } },
-  });
-  const map = new Map(rows.map((r) => [r.key, r.value]));
+// ─── Amazon SES ────────────────────────────────────────────────────────────
 
 
-  const accessKeyId = map.get(SYSTEM_SETTING_KEYS.SES_ACCESS_KEY_ID);
-  const secretAccessKey = map.get(SYSTEM_SETTING_KEYS.SES_SECRET_ACCESS_KEY);
-  const region = map.get(SYSTEM_SETTING_KEYS.SES_REGION) || "us-east-1";
+async function sendViaSesWithSettings(
+  options: SendMailOptions,
+  settings: SettingsMap,
+  keys: { accessKeyId: string; secretAccessKey: string; region: string },
+) {
+  const accessKeyId = settings.get(keys.accessKeyId);
+  const secretAccessKey = settings.get(keys.secretAccessKey);
+  const region = settings.get(keys.region) || "us-east-1";
 
 
   if (!accessKeyId || !secretAccessKey) {
   if (!accessKeyId || !secretAccessKey) {
-    throw new Error(
-      "Amazon SES is not configured. Add your credentials in Admin Settings.",
-    );
+    throw new Error("Amazon SES is not configured. Add your credentials.");
   }
   }
 
 
-  // Build raw email via nodemailer (supports attachments) then send through SES
   const transporter = nodemailer.createTransport({ streamTransport: true });
   const transporter = nodemailer.createTransport({ streamTransport: true });
   const info = await transporter.sendMail({
   const info = await transporter.sendMail({
     from: options.from,
     from: options.from,
@@ -394,6 +441,20 @@ async function sendViaSes(options: SendMailOptions) {
   );
   );
 }
 }
 
 
+async function sendViaSes(options: SendMailOptions) {
+  const keys = [
+    SYSTEM_SETTING_KEYS.SES_ACCESS_KEY_ID,
+    SYSTEM_SETTING_KEYS.SES_SECRET_ACCESS_KEY,
+    SYSTEM_SETTING_KEYS.SES_REGION,
+  ];
+  const settings = await getSystemSettings(keys);
+  await sendViaSesWithSettings(options, settings, {
+    accessKeyId: SYSTEM_SETTING_KEYS.SES_ACCESS_KEY_ID,
+    secretAccessKey: SYSTEM_SETTING_KEYS.SES_SECRET_ACCESS_KEY,
+    region: SYSTEM_SETTING_KEYS.SES_REGION,
+  });
+}
+
 function streamToBuffer(
 function streamToBuffer(
   stream: NodeJS.ReadableStream,
   stream: NodeJS.ReadableStream,
 ): Promise<Uint8Array> {
 ): Promise<Uint8Array> {
@@ -405,29 +466,161 @@ function streamToBuffer(
   });
   });
 }
 }
 
 
-// ─── Router ────────────────────────────────────────────────────────────────
-
-export async function sendMail(options: SendMailOptions) {
-  const provider = await getEmailProvider();
+// ─── Provider dispatch (shared by both system and org) ──────────────────────
+
+function sendWithProvider(
+  provider: EmailProvider,
+  options: SendMailOptions,
+  settings: SettingsMap,
+  keySet: "system" | "org",
+) {
+  if (keySet === "org") {
+    switch (provider) {
+      case "smtp":
+        return sendViaSmtpWithSettings(options, settings, {
+          host: ORG_EMAIL_KEYS.EMAIL_SMTP_HOST,
+          port: ORG_EMAIL_KEYS.EMAIL_SMTP_PORT,
+          user: ORG_EMAIL_KEYS.EMAIL_SMTP_USER,
+          pass: ORG_EMAIL_KEYS.EMAIL_SMTP_PASS,
+          secure: ORG_EMAIL_KEYS.EMAIL_SMTP_SECURE,
+          rejectUnauthorized: ORG_EMAIL_KEYS.EMAIL_SMTP_REJECT_UNAUTHORIZED,
+          requireTls: ORG_EMAIL_KEYS.EMAIL_SMTP_REQUIRE_TLS,
+        }, false);
+      case "resend":
+        return sendViaResendWithSettings(options, settings, ORG_EMAIL_KEYS.EMAIL_RESEND_API_KEY);
+      case "postmark":
+        return sendViaPostmarkWithSettings(options, settings, ORG_EMAIL_KEYS.EMAIL_POSTMARK_API_KEY);
+      case "mailgun":
+        return sendViaMailgunWithSettings(options, settings, {
+          apiKey: ORG_EMAIL_KEYS.EMAIL_MAILGUN_API_KEY,
+          domain: ORG_EMAIL_KEYS.EMAIL_MAILGUN_DOMAIN,
+          region: ORG_EMAIL_KEYS.EMAIL_MAILGUN_REGION,
+        });
+      case "sendgrid":
+        return sendViaSendGridWithSettings(options, settings, ORG_EMAIL_KEYS.EMAIL_SENDGRID_API_KEY);
+      case "ses":
+        return sendViaSesWithSettings(options, settings, {
+          accessKeyId: ORG_EMAIL_KEYS.EMAIL_SES_ACCESS_KEY_ID,
+          secretAccessKey: ORG_EMAIL_KEYS.EMAIL_SES_SECRET_ACCESS_KEY,
+          region: ORG_EMAIL_KEYS.EMAIL_SES_REGION,
+        });
+    }
+  }
 
 
+  // system keySet
   switch (provider) {
   switch (provider) {
     case "resend":
     case "resend":
-      await sendViaResend(options);
-      break;
+      return sendViaResend(options);
     case "postmark":
     case "postmark":
-      await sendViaPostmark(options);
-      break;
+      return sendViaPostmark(options);
     case "mailgun":
     case "mailgun":
-      await sendViaMailgun(options);
-      break;
+      return sendViaMailgun(options);
     case "sendgrid":
     case "sendgrid":
-      await sendViaSendGrid(options);
-      break;
+      return sendViaSendGrid(options);
     case "ses":
     case "ses":
-      await sendViaSes(options);
-      break;
+      return sendViaSes(options);
     default:
     default:
-      await sendViaSmtp(options);
-      break;
+      return sendViaSmtp(options);
   }
   }
 }
 }
+
+// ─── System-level router (unchanged behavior) ──────────────────────────────
+
+export async function sendMail(options: SendMailOptions) {
+  const provider = await getEmailProvider();
+  await sendWithProvider(provider, options, new Map(), "system");
+}
+
+// ─── Org-level email ────────────────────────────────────────────────────────
+
+async function getOrgEmailProvider(
+  organizationId: string,
+): Promise<EmailProvider | null> {
+  const setting = await db.appSetting.findUnique({
+    where: {
+      organizationId_key: {
+        organizationId,
+        key: ORG_EMAIL_KEYS.EMAIL_PROVIDER,
+      },
+    },
+  });
+  const value = setting?.value;
+  if (
+    value === "smtp" ||
+    value === "resend" ||
+    value === "postmark" ||
+    value === "mailgun" ||
+    value === "sendgrid" ||
+    value === "ses"
+  ) {
+    return value;
+  }
+  return null;
+}
+
+export async function getOrgFromAddress(
+  organizationId: string,
+): Promise<string> {
+  const provider = await getOrgEmailProvider(organizationId);
+
+  if (!provider) {
+    return getFromAddress();
+  }
+
+  const keyMap: Record<EmailProvider, { email: string; name: string }> = {
+    smtp: {
+      email: ORG_EMAIL_KEYS.EMAIL_SMTP_FROM_EMAIL,
+      name: ORG_EMAIL_KEYS.EMAIL_SMTP_FROM_NAME,
+    },
+    resend: {
+      email: ORG_EMAIL_KEYS.EMAIL_RESEND_FROM_EMAIL,
+      name: ORG_EMAIL_KEYS.EMAIL_RESEND_FROM_NAME,
+    },
+    postmark: {
+      email: ORG_EMAIL_KEYS.EMAIL_POSTMARK_FROM_EMAIL,
+      name: ORG_EMAIL_KEYS.EMAIL_POSTMARK_FROM_NAME,
+    },
+    mailgun: {
+      email: ORG_EMAIL_KEYS.EMAIL_MAILGUN_FROM_EMAIL,
+      name: ORG_EMAIL_KEYS.EMAIL_MAILGUN_FROM_NAME,
+    },
+    sendgrid: {
+      email: ORG_EMAIL_KEYS.EMAIL_SENDGRID_FROM_EMAIL,
+      name: ORG_EMAIL_KEYS.EMAIL_SENDGRID_FROM_NAME,
+    },
+    ses: {
+      email: ORG_EMAIL_KEYS.EMAIL_SES_FROM_EMAIL,
+      name: ORG_EMAIL_KEYS.EMAIL_SES_FROM_NAME,
+    },
+  };
+
+  const keys = keyMap[provider];
+  const settings = await getOrgSettings(organizationId, [
+    keys.email,
+    keys.name,
+  ]);
+
+  const fromEmail = settings.get(keys.email) || "noreply@example.com";
+  const fromName = settings.get(keys.name) || "Torqvoice";
+
+  return `${fromName} <${fromEmail}>`;
+}
+
+export async function sendOrgMail(
+  organizationId: string,
+  options: SendMailOptions,
+) {
+  const provider = await getOrgEmailProvider(organizationId);
+
+  if (!provider) {
+    // Fall back to global platform email
+    await sendMail(options);
+    return;
+  }
+
+  // Load all org email settings
+  const allKeys = Object.values(ORG_EMAIL_KEYS);
+  const settings = await getOrgSettings(organizationId, allKeys);
+
+  await sendWithProvider(provider, options, settings, "org");
+}