Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-18 00:53:41 +00:00
co-authored by renee-png
parent 1b454bf090
commit fbf23c1c2f
2 changed files with 177 additions and 27 deletions
+78 -20
View File
@@ -18,29 +18,47 @@ interface SendBody {
bcc?: string | string[];
replyTo?: string;
test?: boolean;
context?: string;
case_id?: string;
}
function toArray(v: string | string[] | undefined | null): string[] {
if (!v) return [];
if (Array.isArray(v)) return v.map((s) => s.trim()).filter(Boolean);
return v
.split(/[,;]/)
.map((s) => s.trim())
.filter(Boolean);
}
Deno.serve(async (req) => {
if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const anonKey = Deno.env.get("SUPABASE_ANON_KEY")!;
const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
const admin = createClient(supabaseUrl, serviceKey);
let userId: string | null = null;
let body: SendBody | null = null;
let fromAddr = "";
let toList: string[] = [];
let ccList: string[] = [];
let bccList: string[] = [];
try {
const authHeader = req.headers.get("Authorization");
if (!authHeader) {
return json({ error: "Missing authorization" }, 401);
}
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const anonKey = Deno.env.get("SUPABASE_ANON_KEY")!;
const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
// Verify caller is authenticated
const userClient = createClient(supabaseUrl, anonKey, {
global: { headers: { Authorization: authHeader } },
});
const { data: userData, error: userErr } = await userClient.auth.getUser();
if (userErr || !userData.user) return json({ error: "Unauthorized" }, 401);
const admin = createClient(supabaseUrl, serviceKey);
userId = userData.user.id;
// Load latest enabled SMTP settings
const { data: settings, error: setErr } = await admin
@@ -57,7 +75,7 @@ Deno.serve(async (req) => {
const password = Deno.env.get("SMTP_PASSWORD") ?? "";
if (!password) return json({ error: "SMTP_PASSWORD secret not set" }, 400);
const body = (await req.json()) as SendBody;
body = (await req.json()) as SendBody;
if (!body.to || !body.subject) {
return json({ error: "to and subject are required" }, 400);
}
@@ -69,13 +87,6 @@ Deno.serve(async (req) => {
if (settings.port === 465) useTls = true;
else if (settings.port === 587 || settings.port === 25) useTls = false;
console.log("SMTP connecting", {
host: settings.host,
port: settings.port,
tls: useTls,
hasAuth: !!settings.username,
});
const client = new SMTPClient({
connection: {
hostname: settings.host,
@@ -87,15 +98,25 @@ Deno.serve(async (req) => {
},
});
const fromAddr = settings.from_name
fromAddr = settings.from_name
? `${settings.from_name} <${settings.from_email}>`
: settings.from_email;
toList = toArray(body.to);
ccList = toArray(body.cc);
// Merge per-message BCC with default BCC from settings
bccList = [
...toArray(body.bcc),
...toArray(settings.default_bcc ?? null),
];
// dedupe
bccList = Array.from(new Set(bccList));
await client.send({
from: fromAddr,
to: body.to,
cc: body.cc,
bcc: body.bcc,
to: toList,
cc: ccList.length ? ccList : undefined,
bcc: bccList.length ? bccList : undefined,
replyTo: body.replyTo ?? settings.reply_to ?? undefined,
subject: body.subject,
content: body.text ?? "auto",
@@ -103,10 +124,47 @@ Deno.serve(async (req) => {
});
await client.close();
// Log success
await admin.from("email_logs").insert({
sent_by: userId,
to_addresses: toList,
cc_addresses: ccList,
bcc_addresses: bccList,
reply_to: body.replyTo ?? settings.reply_to ?? null,
from_address: fromAddr,
subject: body.subject,
body_html: body.html ?? null,
body_text: body.text ?? null,
status: "sent",
context: body.context ?? (body.test ? "test" : null),
case_id: body.case_id ?? null,
});
return json({ ok: true });
} catch (e) {
console.error("send-smtp-email error", e);
return json({ error: e instanceof Error ? e.message : String(e) }, 500);
const message = e instanceof Error ? e.message : String(e);
console.error("send-smtp-email error", message);
// Best-effort failure log
try {
await admin.from("email_logs").insert({
sent_by: userId,
to_addresses: toList,
cc_addresses: ccList,
bcc_addresses: bccList,
reply_to: body?.replyTo ?? null,
from_address: fromAddr || null,
subject: body?.subject ?? "(unknown)",
body_html: body?.html ?? null,
body_text: body?.text ?? null,
status: "failed",
error_message: message,
context: body?.context ?? (body?.test ? "test" : null),
case_id: body?.case_id ?? null,
});
} catch (_) {
// ignore log failure
}
return json({ error: message }, 500);
}
});