Files
acmcc/supabase/functions/test-smtp-connection/index.ts
T
2026-06-01 20:19:26 -04:00

306 lines
11 KiB
TypeScript

import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.1";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type, x-supabase-client-platform, x-supabase-client-platform-version, x-supabase-client-runtime, x-supabase-client-runtime-version",
};
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
try {
// Verify auth
const authHeader = req.headers.get("Authorization");
if (!authHeader) {
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), {
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const supabaseAnonKey = Deno.env.get("SUPABASE_ANON_KEY")!;
const supabase = createClient(supabaseUrl, supabaseAnonKey, {
global: { headers: { Authorization: authHeader } },
});
const { data: { user }, error: userError } = await supabase.auth.getUser();
if (userError || !user) {
return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), {
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const body = await req.json();
const { action, smtp_host, smtp_port, smtp_username, smtp_password, test_recipient } = body;
if (!smtp_host || !smtp_port || !smtp_username || !smtp_password) {
return new Response(JSON.stringify({ success: false, error: "Missing SMTP credentials" }), {
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
if (action === "test_connection") {
try {
const useSSL = Number(smtp_port) === 465;
let conn: Deno.Conn | Deno.TlsConn;
if (useSSL) {
conn = await Deno.connectTls({
hostname: smtp_host,
port: Number(smtp_port),
});
} else {
conn = await Deno.connect({
hostname: smtp_host,
port: Number(smtp_port),
});
}
const read = async (): Promise<string> => {
const buf = new Uint8Array(4096);
const n = await conn.read(buf);
return new TextDecoder().decode(buf.subarray(0, n || 0));
};
const write = async (cmd: string) => {
await conn.write(new TextEncoder().encode(cmd + "\r\n"));
};
const greeting = await read();
if (!greeting.startsWith("220")) {
conn.close();
return new Response(JSON.stringify({ success: false, error: `Bad greeting: ${greeting.trim()}` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
await write("EHLO test.local");
let ehloResponse = await read();
if (!useSSL && ehloResponse.includes("STARTTLS")) {
await write("STARTTLS");
const tlsResp = await read();
if (!tlsResp.startsWith("220")) {
conn.close();
return new Response(JSON.stringify({ success: false, error: `STARTTLS rejected: ${tlsResp.trim()}` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
conn = await Deno.startTls(conn as Deno.TcpConn, { hostname: smtp_host });
await write("EHLO test.local");
ehloResponse = await read();
}
await write("QUIT");
conn.close();
return new Response(
JSON.stringify({
success: true,
message: `Connection successful to ${smtp_host}:${smtp_port}`,
details: {
greeting: greeting.trim(),
ehlo: ehloResponse.trim(),
secure: useSSL ? "implicit_tls" : ehloResponse.includes("STARTTLS") ? "starttls_available" : "plain_smtp",
},
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (connError) {
return new Response(
JSON.stringify({
success: false,
error: `Connection failed: ${connError.message}`,
message: `Could not connect to ${smtp_host}:${smtp_port}. Please verify the host/port and SSL/TLS mode.`,
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
}
if (action === "send_test_email") {
const recipient = test_recipient || user.email;
if (!recipient) {
return new Response(JSON.stringify({ success: false, error: "No recipient email available" }), {
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
try {
const useSSL = Number(smtp_port) === 465;
let conn: Deno.Conn;
if (useSSL) {
conn = await Deno.connectTls({
hostname: smtp_host,
port: Number(smtp_port),
});
} else {
conn = await Deno.connect({
hostname: smtp_host,
port: Number(smtp_port),
});
}
const read = async (): Promise<string> => {
const buf = new Uint8Array(4096);
const n = await conn.read(buf);
return new TextDecoder().decode(buf.subarray(0, n || 0));
};
const write = async (cmd: string) => {
await conn.write(new TextEncoder().encode(cmd + "\r\n"));
};
// Read greeting
const greeting = await read();
if (!greeting.startsWith("220")) {
conn.close();
return new Response(JSON.stringify({ success: false, error: `Bad greeting: ${greeting.trim()}` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
// EHLO
await write(`EHLO test.local`);
let ehloResp = await read();
// STARTTLS for non-SSL ports
if (!useSSL && ehloResp.includes("STARTTLS")) {
await write("STARTTLS");
const tlsResp = await read();
if (tlsResp.startsWith("220")) {
conn = await Deno.startTls(conn as Deno.TcpConn, { hostname: smtp_host });
await write(`EHLO test.local`);
ehloResp = await read();
}
}
// AUTH LOGIN
await write("AUTH LOGIN");
const authResp = await read();
if (!authResp.startsWith("334")) {
conn.close();
return new Response(JSON.stringify({ success: false, error: `Auth not supported: ${authResp.trim()}` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
// Send username (base64)
await write(btoa(smtp_username));
const userResp = await read();
if (!userResp.startsWith("334")) {
conn.close();
return new Response(JSON.stringify({ success: false, error: `Username rejected: ${userResp.trim()}` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
// Send password (base64)
await write(btoa(smtp_password));
const passResp = await read();
if (!passResp.startsWith("235")) {
conn.close();
return new Response(JSON.stringify({ success: false, error: `Authentication failed. Please check your username and password.` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
// MAIL FROM
await write(`MAIL FROM:<${smtp_username}>`);
const fromResp = await read();
if (!fromResp.startsWith("250")) {
conn.close();
return new Response(JSON.stringify({ success: false, error: `MAIL FROM rejected: ${fromResp.trim()}` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
// RCPT TO
await write(`RCPT TO:<${recipient}>`);
const rcptResp = await read();
if (!rcptResp.startsWith("250")) {
conn.close();
return new Response(JSON.stringify({ success: false, error: `RCPT TO rejected: ${rcptResp.trim()}` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
// DATA
await write("DATA");
const dataResp = await read();
if (!dataResp.startsWith("354")) {
conn.close();
return new Response(JSON.stringify({ success: false, error: `DATA rejected: ${dataResp.trim()}` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const now = new Date().toUTCString();
const emailContent = [
`From: ${smtp_username}`,
`To: ${recipient}`,
`Subject: SMTP Test Email`,
`Date: ${now}`,
`Content-Type: text/html; charset=utf-8`,
``,
`<html><body>`,
`<h2>SMTP Test Successful</h2>`,
`<p>This is a test email sent from your email server settings.</p>`,
`<p><strong>Server:</strong> ${smtp_host}:${smtp_port}</p>`,
`<p><strong>Username:</strong> ${smtp_username}</p>`,
`<p><strong>Sent at:</strong> ${now}</p>`,
`<hr/>`,
`<p style="color:#888;font-size:12px;">This email confirms your SMTP configuration is working correctly.</p>`,
`</body></html>`,
`.`,
].join("\r\n");
await conn.write(new TextEncoder().encode(emailContent + "\r\n"));
const sendResp = await read();
await write("QUIT");
conn.close();
const emailSent = sendResp.startsWith("250");
return new Response(
JSON.stringify({
success: emailSent,
message: emailSent
? `Test email sent successfully to ${recipient}`
: `Email delivery uncertain: ${sendResp.trim()}`,
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (sendError) {
return new Response(
JSON.stringify({
success: false,
error: `Failed to send test email: ${sendError.message}`,
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
}
return new Response(
JSON.stringify({ success: false, error: "Invalid action. Use 'test_connection' or 'send_test_email'." }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error) {
return new Response(
JSON.stringify({ success: false, error: error.message }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
});