Files
meet-hub/supabase/functions/send-email-v2/index.ts
dwindown 053465afa3 Fix email system and implement OTP confirmation flow
Email System Fixes:
- Fix email sending after payment: handle-order-paid now calls send-notification
  instead of send-email-v2 directly, properly processing template variables
- Fix order_created email timing: sent immediately after order creation,
  before payment QR code generation
- Update email templates to use short order ID (8 chars) instead of full UUID
- Add working "Akses Sekarang" buttons to payment_success and access_granted emails
- Add platform_url column to platform_settings for email links

OTP Verification Flow:
- Create dedicated /confirm-otp page for users who close registration modal
- Add link in checkout modal and email to dedicated OTP page
- Update OTP email template with better copywriting and dedicated page link
- Fix send-auth-otp to fetch platform settings for dynamic brand_name and platform_url
- Auto-login users after OTP verification in checkout flow

Admin Features:
- Add delete user functionality with cascade deletion of all related data
- Update IntegrasiTab to read/write email settings from platform_settings only
- Add test email template for email configuration testing

Cleanup:
- Remove obsolete send-consultation-reminder and send-test-email functions
- Update send-email-v2 to read email config from platform_settings
- Remove footer links (Ubah Preferensi/Unsubscribe) from email templates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 18:02:25 +07:00

131 lines
4.3 KiB
TypeScript

import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
interface EmailRequest {
recipient: string;
subject: string;
content: string;
}
// Send via Mailketing API
async function sendViaMailketing(
request: EmailRequest,
apiToken: string,
fromName: string,
fromEmail: string
): Promise<{ success: boolean; message: string }> {
const { recipient, subject, content } = request;
// Build form-encoded body (http_build_query format)
const params = new URLSearchParams();
params.append('api_token', apiToken);
params.append('from_name', fromName);
params.append('from_email', fromEmail);
params.append('recipient', recipient);
params.append('subject', subject);
params.append('content', content);
console.log(`Sending email via Mailketing to ${recipient}`);
const response = await fetch('https://api.mailketing.co.id/api/v1/send', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString(),
});
if (!response.ok) {
const errorText = await response.text();
console.error('Mailketing API error:', response.status, errorText);
throw new Error(`Mailketing API error: ${response.status} ${errorText}`);
}
const result = await response.json();
console.log('Mailketing API response:', result);
return {
success: true,
message: result.response || 'Email sent successfully via Mailketing'
};
}
serve(async (req: Request): Promise<Response> => {
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
try {
// Initialize Supabase client
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
const supabase = createClient(supabaseUrl, supabaseServiceKey);
// Fetch email settings from platform_settings
const { data: settings, error: settingsError } = await supabase
.from('platform_settings')
.select('*')
.single();
if (settingsError || !settings) {
console.error('Error fetching platform settings:', settingsError);
throw new Error('Failed to fetch email configuration from platform_settings');
}
const apiToken = settings.integration_email_api_token;
const fromName = settings.integration_email_from_name || settings.brand_name;
const fromEmail = settings.integration_email_from_email;
if (!apiToken || !fromEmail) {
return new Response(
JSON.stringify({ success: false, message: "Email not configured. Please set API token and from email in platform settings." }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const body: EmailRequest = await req.json();
// Validate required fields
if (!body.recipient || !body.subject || !body.content) {
return new Response(
JSON.stringify({ success: false, message: "Missing required fields: recipient, subject, content" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(body.recipient) || !emailRegex.test(fromEmail)) {
return new Response(
JSON.stringify({ success: false, message: "Invalid email format" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
console.log(`Attempting to send email to: ${body.recipient}`);
console.log(`From: ${fromName} <${fromEmail}>`);
console.log(`Subject: ${body.subject}`);
const result = await sendViaMailketing(body, apiToken, fromName, fromEmail);
return new Response(
JSON.stringify(result),
{ status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error: any) {
console.error("Error sending email:", error);
return new Response(
JSON.stringify({
success: false,
message: error.message || "Failed to send email"
}),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
});