From 3f3900e0739315b0bb41e66a2dee157c4b067abd Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 13:39:09 +0000 Subject: [PATCH] Changes Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com> --- supabase/functions/send-order-email/index.ts | 134 +++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 supabase/functions/send-order-email/index.ts diff --git a/supabase/functions/send-order-email/index.ts b/supabase/functions/send-order-email/index.ts new file mode 100644 index 0000000..a018bb2 --- /dev/null +++ b/supabase/functions/send-order-email/index.ts @@ -0,0 +1,134 @@ +import { z } from "npm:zod@3.23.8"; + +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": + "authorization, x-client-info, apikey, content-type", + "Access-Control-Allow-Methods": "POST, OPTIONS", +}; + +const GATEWAY_URL = "https://connector-gateway.lovable.dev/brevo"; + +const BodySchema = z.object({ + order_number: z.string().min(1).max(100), + customer_email: z.string().email(), + customer_name: z.string().min(1).max(200), + kg_per_week: z.number().min(0), + employees: z.number().int().min(0), + monthly_amount_sek: z.number().min(0), + method: z.enum(["card", "invoice"]), + address: z.string().max(200).optional().default(""), + postal_code: z.string().max(20).optional().default(""), + phone: z.string().max(30).optional().default(""), + comments: z.string().max(1000).optional().default(""), +}); + +function fmtKr(n: number) { + return new Intl.NumberFormat("sv-SE", { + style: "currency", + currency: "SEK", + maximumFractionDigits: 0, + }).format(n); +} + +Deno.serve(async (req) => { + if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders }); + + try { + const LOVABLE_API_KEY = Deno.env.get("LOVABLE_API_KEY"); + const BREVO_API_KEY = Deno.env.get("BREVO_API_KEY"); + if (!LOVABLE_API_KEY || !BREVO_API_KEY) { + throw new Error("Email service not configured"); + } + + const raw = await req.json(); + const parsed = BodySchema.safeParse(raw); + if (!parsed.success) { + return new Response( + JSON.stringify({ error: "Ogiltiga fält", details: parsed.error.flatten().fieldErrors }), + { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }, + ); + } + const o = parsed.data; + + const senderEmail = Deno.env.get("BREVO_SENDER_EMAIL") ?? "noreply@konditorivaror.se"; + const senderName = Deno.env.get("BREVO_SENDER_NAME") ?? "Lennart Svensson Konditorivaror"; + const adminEmail = Deno.env.get("ORDER_NOTIFICATION_EMAIL") ?? senderEmail; + + const methodLabel = o.method === "card" ? "Kort (månadsvis)" : "Faktura (månadsvis)"; + + const customerHtml = ` +
+

Tack för din beställning!

+

Hej ${o.customer_name},

+

Vi har mottagit din beställning av Kakabonnemang. Goda saker är på väg – leverans varje tisdag inom Storstockholm.

+

Orderdetaljer

+ + + + + + +
Ordernummer:${o.order_number}
Mängd:${o.kg_per_week} kg/vecka
Antal anställda:${o.employees}
Månadskostnad (exkl. moms):${fmtKr(o.monthly_amount_sek)}
Betalning:${methodLabel}
+

Har du frågor? Svara bara på detta mejl.

+

Äkta svenskt konditorhantverk

+
`; + + const adminHtml = ` +
+

Ny order: ${o.order_number}

+

Företag: ${o.customer_name}
+ E-post: ${o.customer_email}
+ Telefon: ${o.phone || "-"}

+

Mängd: ${o.kg_per_week} kg/vecka
+ Anställda: ${o.employees}
+ Månadskostnad (exkl moms): ${fmtKr(o.monthly_amount_sek)}
+ Betalning: ${methodLabel}

+

Leverans: ${o.address || "-"}, ${o.postal_code || "-"}

+ ${o.comments ? `

Kommentar: ${o.comments}

` : ""} +
`; + + async function send(to: string, subject: string, html: string) { + const res = await fetch(`${GATEWAY_URL}/v3/smtp/email`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${LOVABLE_API_KEY}`, + "X-Connection-Api-Key": BREVO_API_KEY, + }, + body: JSON.stringify({ + sender: { name: senderName, email: senderEmail }, + to: [{ email: to }], + subject, + htmlContent: html, + }), + }); + if (!res.ok) { + const body = await res.text(); + console.error(`Brevo send failed [${res.status}]:`, body); + throw new Error(`Brevo ${res.status}: ${body}`); + } + return res.json(); + } + + const results = await Promise.allSettled([ + send(o.customer_email, `Orderbekräftelse ${o.order_number} – Lennart Svensson Konditorivaror`, customerHtml), + send(adminEmail, `Ny order: ${o.order_number} (${o.customer_name})`, adminHtml), + ]); + + const errors = results + .map((r, i) => (r.status === "rejected" ? { i, reason: String(r.reason) } : null)) + .filter(Boolean); + + return new Response( + JSON.stringify({ success: errors.length === 0, errors }), + { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } }, + ); + } catch (err: any) { + console.error("send-order-email error:", err); + return new Response( + JSON.stringify({ error: err?.message ?? "Kunde inte skicka mejl" }), + { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }, + ); + } +});