From d59f87961ecdd8865fb5b02767e9b3de254ad9a8 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 00:52:05 +0000 Subject: [PATCH 1/3] Work in progress --- bun.lockb | Bin 339791 -> 339791 bytes src/routeTree.gen.ts | 9 +++++++++ 2 files changed, 9 insertions(+) diff --git a/bun.lockb b/bun.lockb index 2b9882d3b27999c24a3a2772f175604a36843758..e560d2fd65c5189f75c4365a18052f096f99a30e 100755 GIT binary patch delta 1565 zcmYk6SxA&o6vr9+Tb8YmF@+Vqw3u1WXl6QQ*zS!%x`9u6O zULY&e7E7_ka8i3F_9@Rhjy=qW3K2`q$8l77UKVyJ`?8R%Jeh@^%7xi#chX3<*r%E} z{{9o#rkYnLuu*ws4o)fe=3tBR%3P!}Pu0Oeow=eH{+%ne30LGHjy0>!vt}d@E<II_F3OEv9o#3)a?rBu0^L!}Z^ zj?c#lr@>MzSK*Q}tH(8EaH#!b8MZ4QD93JQIrmVxm1J!N-m5*n5{V`!1|L+y9ioej zGiGx*y^A8wA)YswQ|ZZB%%qNUdKMRjR$D1+tM$rUG*PXZzlLv(bc;iz&{z!;%y2Hs zs6`sHt)DOXb*-jxQTzoNMoHg$K?Y4Yq0Y+iuug}iRKs=ETMZ9uK$O}&4Jc=p#x7_? zhH`78G@L^_=trZZ7tOJY;w$|8BJQzX`WUzb<4p0mSFY67v=<{XKaNr%3-y{RZoJfOBSZ5~mnx8c4^~)^sb{a)rkyZ*n zs={n~c18LlhPEoqrMPBGky-`N^JXnOjl>XjRo~XM;ptT!3Qlm%YG~Ir88nH#@VXT? za2}5Me9&;Z-(oqfXtjEU;9w^dpz z>Sb-#=0qiX-fk1mcWwANT~ct?O}=BTuf79hGXF?AaYRL*I`Q84YjyM?nSQ;-9h2+% zBSVn#M*7OzN%Z6m4zrzSwt_!ghm(5xWnN@^_JF)K;q*Sff=mAyz7Alk89FJLJBU^D zdEpz8K>S1;)IN$ZiW-$htHF4o%3(BP%$z1w2OGwaY&?2H68vTlO^;ALFBVbC($EFe I925HXAB5xdPXGV_ delta 1565 zcmYk6TTISz7{~YK8zsjLds(z@9FjvPNxcgJiuvc<+m zY_#{rTu{oDxu_OnE{QGS(#G?D{yqD<`hR}U@B18n&+qyD-+o&4_R}g?i><|{Wx+FK zoVG<$SOFra=QIv0FHJ`Zb6P&as4X2Qm6v8-n z*s5^`a}cU*%SEi}%W|y%REYSM}%rW~J- z6F_4{Sg*o0#a552iV?2%55?H4e6$1ynC0B#C03HnrI=QGR2gDSj`7|vgVRSBsTa-W zaC!%=zKkf|U{0i=OIS#~m-Q?T@~yN|R#)nkIcTO*HGd7?D(M!7h^MJ4M4RCplv<4> zX4@!V^2=&X#ToZ!fE$bHk4sPQpmI0j z*-19PyJhf&omcgS9rXFCXgKgb%4wDXV~(M*W^6GNy39`!4Y$ZF@^%u1wn{67A6CGh zp0rAT#L!)VB^23aDH1CHdfKLCCy^LJ+VySC8y>goP;i0{tD*fJGH4Qe#WgFe=Nb-a zarREj_jx%tVxUvHDo%EfmFcEO{up8pye=4`1%@q%+RsktT6;H() + +import type { getRouter } from './router.tsx' +import type { createStart } from '@tanstack/react-start' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + } +} From 1b454bf090cb0825a965491514f7eee6646a9058 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 00:52:30 +0000 Subject: [PATCH 2/3] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/integrations/supabase/types.ts | 60 +++++++++++++++++++ src/routeTree.gen.ts | 9 --- ...7_b33318a9-974b-43ed-8d91-2a11bc98e11b.sql | 44 ++++++++++++++ 3 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 supabase/migrations/20260418005227_b33318a9-974b-43ed-8d91-2a11bc98e11b.sql diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index d940019..9c91749 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -968,6 +968,63 @@ export type Database = { }, ] } + email_logs: { + Row: { + bcc_addresses: string[] + body_html: string | null + body_text: string | null + case_id: string | null + cc_addresses: string[] + context: string | null + created_at: string + error_message: string | null + from_address: string | null + id: string + reply_to: string | null + sent_at: string + sent_by: string | null + status: string + subject: string + to_addresses: string[] + } + Insert: { + bcc_addresses?: string[] + body_html?: string | null + body_text?: string | null + case_id?: string | null + cc_addresses?: string[] + context?: string | null + created_at?: string + error_message?: string | null + from_address?: string | null + id?: string + reply_to?: string | null + sent_at?: string + sent_by?: string | null + status?: string + subject: string + to_addresses?: string[] + } + Update: { + bcc_addresses?: string[] + body_html?: string | null + body_text?: string | null + case_id?: string | null + cc_addresses?: string[] + context?: string | null + created_at?: string + error_message?: string | null + from_address?: string | null + id?: string + reply_to?: string | null + sent_at?: string + sent_by?: string | null + status?: string + subject?: string + to_addresses?: string[] + } + Relationships: [] + } expenses: { Row: { amount: number @@ -1789,6 +1846,7 @@ export type Database = { smtp_settings: { Row: { created_at: string + default_bcc: string | null enabled: boolean from_email: string from_name: string | null @@ -1804,6 +1862,7 @@ export type Database = { } Insert: { created_at?: string + default_bcc?: string | null enabled?: boolean from_email: string from_name?: string | null @@ -1819,6 +1878,7 @@ export type Database = { } Update: { created_at?: string + default_bcc?: string | null enabled?: boolean from_email?: string from_name?: string | null diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index da410ca..fb097a5 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -742,12 +742,3 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() - -import type { getRouter } from './router.tsx' -import type { createStart } from '@tanstack/react-start' -declare module '@tanstack/react-start' { - interface Register { - ssr: true - router: Awaited> - } -} diff --git a/supabase/migrations/20260418005227_b33318a9-974b-43ed-8d91-2a11bc98e11b.sql b/supabase/migrations/20260418005227_b33318a9-974b-43ed-8d91-2a11bc98e11b.sql new file mode 100644 index 0000000..37c26e5 --- /dev/null +++ b/supabase/migrations/20260418005227_b33318a9-974b-43ed-8d91-2a11bc98e11b.sql @@ -0,0 +1,44 @@ +-- Add BCC default to SMTP settings +ALTER TABLE public.smtp_settings + ADD COLUMN IF NOT EXISTS default_bcc text; + +-- Email log table +CREATE TABLE IF NOT EXISTS public.email_logs ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + sent_at timestamptz NOT NULL DEFAULT now(), + sent_by uuid, + to_addresses text[] NOT NULL DEFAULT '{}', + cc_addresses text[] NOT NULL DEFAULT '{}', + bcc_addresses text[] NOT NULL DEFAULT '{}', + reply_to text, + from_address text, + subject text NOT NULL, + body_html text, + body_text text, + status text NOT NULL DEFAULT 'sent', + error_message text, + context text, + case_id uuid, + created_at timestamptz NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS email_logs_sent_at_idx ON public.email_logs (sent_at DESC); +CREATE INDEX IF NOT EXISTS email_logs_sent_by_idx ON public.email_logs (sent_by); +CREATE INDEX IF NOT EXISTS email_logs_case_id_idx ON public.email_logs (case_id); + +ALTER TABLE public.email_logs ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS email_logs_select ON public.email_logs; +CREATE POLICY email_logs_select ON public.email_logs + FOR SELECT TO authenticated + USING (public.is_admin(auth.uid()) OR sent_by = auth.uid()); + +DROP POLICY IF EXISTS email_logs_insert ON public.email_logs; +CREATE POLICY email_logs_insert ON public.email_logs + FOR INSERT TO authenticated + WITH CHECK (auth.uid() IS NOT NULL); + +DROP POLICY IF EXISTS email_logs_delete ON public.email_logs; +CREATE POLICY email_logs_delete ON public.email_logs + FOR DELETE TO authenticated + USING (public.is_admin(auth.uid())); From fbf23c1c2f54b72e84ca5e1e035a62b4c1c96e34 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 00:53:41 +0000 Subject: [PATCH 3/3] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.smtp.tsx | 106 ++++++++++++++++++-- supabase/functions/send-smtp-email/index.ts | 98 ++++++++++++++---- 2 files changed, 177 insertions(+), 27 deletions(-) diff --git a/src/routes/settings.smtp.tsx b/src/routes/settings.smtp.tsx index 742a8a8..0b3b889 100644 --- a/src/routes/settings.smtp.tsx +++ b/src/routes/settings.smtp.tsx @@ -8,7 +8,8 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; -import { Loader2, Mail, Send, KeyRound } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Loader2, Mail, Send, KeyRound, History } from "lucide-react"; import { toast } from "sonner"; export const Route = createFileRoute("/settings/smtp")({ @@ -23,10 +24,22 @@ const EMPTY = { from_email: "", from_name: "", reply_to: "", + default_bcc: "", enabled: true, notes: "", }; +interface EmailLog { + id: string; + sent_at: string; + to_addresses: string[]; + bcc_addresses: string[]; + subject: string; + status: string; + error_message: string | null; + context: string | null; +} + function SmtpSettingsPage() { const { user, isAdmin } = useAuth(); const [loading, setLoading] = useState(true); @@ -35,15 +48,24 @@ function SmtpSettingsPage() { const [recordId, setRecordId] = useState(null); const [form, setForm] = useState({ ...EMPTY }); const [testTo, setTestTo] = useState(""); + const [logs, setLogs] = useState([]); const load = async () => { setLoading(true); - const { data } = await supabase - .from("smtp_settings") - .select("*") - .order("updated_at", { ascending: false }) - .limit(1) - .maybeSingle(); + const [settingsRes, logsRes] = await Promise.all([ + supabase + .from("smtp_settings") + .select("*") + .order("updated_at", { ascending: false }) + .limit(1) + .maybeSingle(), + supabase + .from("email_logs") + .select("id, sent_at, to_addresses, bcc_addresses, subject, status, error_message, context") + .order("sent_at", { ascending: false }) + .limit(25), + ]); + const data = settingsRes.data; if (data) { setRecordId(data.id); setForm({ @@ -54,10 +76,12 @@ function SmtpSettingsPage() { from_email: data.from_email ?? "", from_name: data.from_name ?? "", reply_to: data.reply_to ?? "", + default_bcc: (data as any).default_bcc ?? "", enabled: data.enabled ?? true, notes: data.notes ?? "", }); } + setLogs((logsRes.data as EmailLog[]) ?? []); setLoading(false); }; @@ -83,6 +107,7 @@ function SmtpSettingsPage() { from_email: form.from_email.trim(), from_name: form.from_name || null, reply_to: form.reply_to || null, + default_bcc: form.default_bcc.trim() || null, enabled: form.enabled, notes: form.notes || null, updated_by: user?.id, @@ -111,6 +136,8 @@ function SmtpSettingsPage() { subject: "SMTP test from your firm CRM", html: `

This is a test email sent from ${form.from_email} via ${form.host}:${form.port}.

If you received this, your SMTP settings are working.

`, text: `Test email from ${form.from_email} via ${form.host}:${form.port}.`, + test: true, + context: "smtp_test", }, }); setTesting(false); @@ -118,9 +145,11 @@ function SmtpSettingsPage() { toast.error("Send failed", { description: (error as any)?.message || (data as any)?.error, }); + load(); return; } toast.success(`Test email sent to ${testTo}`); + load(); }; if (!isAdmin) { @@ -218,6 +247,16 @@ function SmtpSettingsPage() { onChange={(e) => update("reply_to", e.target.value)} /> + + update("default_bcc", e.target.value)} + placeholder="archive@yourfirm.com, partner@yourfirm.com" + /> +

+ Every outbound email will be silently BCC'd to these addresses (comma-separated). +

+