+
Email · SMTP
-
Bring your own SMTP server for outbound email.
+
Send external email from your own mail server.
-
Coming soon
+ {status.configured ?
Connected :
Not connected}
+
+ {status.configured && !editing ? (
+
+
+ - From
- {status.fromName ? `${status.fromName} · ` : ""}{status.fromEmail ?? "—"}
+ - Server
- {status.host ?? "—"}{status.port ? `:${status.port}` : ""}
+ - Status
- {status.enabled ? "Active" : "Disabled"}
+
+
setEditing(true)}>Update credentials
+
+ ) : null}
+
+ {showForm ? (
+
+ ) : null}
+
+ {!live ?
Demo mode — credentials are stored locally and no email is sent.
: null}
);
}
diff --git a/src/lib/smtp-settings-api.ts b/src/lib/smtp-settings-api.ts
new file mode 100644
index 0000000..07a4fb8
--- /dev/null
+++ b/src/lib/smtp-settings-api.ts
@@ -0,0 +1,89 @@
+"use client";
+
+// SMTP settings data layer — a tenant's own outbound email server (BYO). Serves the local mock
+// (Shell not configured) or the live be-crm data door (crm.settings.smtp.*).
+//
+// Live contract (be-crm → IIOS BYO credential store):
+// query crm.settings.smtp.status {} -> { configured, enabled?, hints? }
+// cmd crm.settings.smtp.configure { host, port, secure, user, pass, fromEmail, fromName? } -> masked status
+// The password is write-only: sealed in IIOS, never returned — status carries only non-secret hints.
+
+import { useCallback, useState } from "react";
+import { useAppShell, useQuery } from "@abe-kap/appshell-sdk/react";
+import { isShellConfigured } from "./appshell";
+
+export interface SmtpCredentials {
+ host: string;
+ port: number;
+ secure: boolean;
+ user: string;
+ pass: string;
+ fromEmail: string;
+ fromName?: string;
+}
+
+export interface SmtpStatus {
+ configured: boolean;
+ enabled: boolean;
+ host?: string;
+ port?: number;
+ user?: string;
+ fromEmail?: string;
+ fromName?: string;
+}
+
+export interface SmtpSettingsData {
+ live: boolean;
+ loading: boolean;
+ error: string | null;
+ status: SmtpStatus;
+ configure: (input: SmtpCredentials) => Promise
;
+ refetch: () => void;
+}
+
+interface StatusDTO { configured: boolean; enabled?: boolean; hints?: { host?: string; port?: number; user?: string; fromEmail?: string; fromName?: string } }
+
+function toStatus(dto?: StatusDTO | null): SmtpStatus {
+ return {
+ configured: !!dto?.configured,
+ enabled: dto?.enabled ?? false,
+ host: dto?.hints?.host,
+ port: dto?.hints?.port,
+ user: dto?.hints?.user,
+ fromEmail: dto?.hints?.fromEmail,
+ fromName: dto?.hints?.fromName,
+ };
+}
+
+/* ---- Mock (demo mode) — stores only the non-secret hints ---- */
+function useMockSmtp(): SmtpSettingsData {
+ const [status, setStatus] = useState({ configured: false, enabled: false });
+ const configure = useCallback(async ({ host, port, user, fromEmail, fromName }: SmtpCredentials) => {
+ setStatus({ configured: true, enabled: true, host, port, user, fromEmail, ...(fromName ? { fromName } : {}) });
+ }, []);
+ return { live: false, loading: false, error: null, status, configure, refetch: () => {} };
+}
+
+/* ---- Live (be-crm data door) ---- */
+function useLiveSmtp(): SmtpSettingsData {
+ const { sdk } = useAppShell();
+ const q = useQuery("crm.settings.smtp.status", {});
+ const configure = useCallback(async (input: SmtpCredentials) => {
+ await sdk.command("crm.settings.smtp.configure", { ...input });
+ q.refetch();
+ }, [sdk, q]);
+ return {
+ live: true,
+ loading: q.loading,
+ error: q.error ? String(q.error) : null,
+ status: toStatus(q.data),
+ configure,
+ refetch: q.refetch,
+ };
+}
+
+const SHELL = isShellConfigured();
+
+export function useSmtpSettings(): SmtpSettingsData {
+ return SHELL ? useLiveSmtp() : useMockSmtp();
+}