mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
b1486a0b2a
Replace Lovable-bound email transport and auth webhook so the platform sends all automated email through its own infrastructure. - process-email-queue: drop sendLovableEmail/LOVABLE_API_KEY; send via the Hostinger Email API (primary) with automatic SMTP fallback. Shared transports added in _shared/hostinger-mail.ts and _shared/smtp-send.ts. - auth-email-hook: verify Supabase's native Send Email hook signature (Standard Webhooks via SEND_EMAIL_HOOK_SECRET) instead of Lovable's libs; build the GoTrue verify URL; keep enqueue → process-email-queue. - Recreate the 6 auth email templates under _shared/email-templates/ that previously only existed in the deployed function. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
// Hostinger Email API transport (https://api.mail.hostinger.com).
|
|
//
|
|
// Send endpoint: POST /api/v1/mailboxes/{mailboxResourceId}/send
|
|
// Auth: Authorization: Bearer <token> (token + mailbox are scoped to one order)
|
|
// The From identity is implicit — it is the mailbox the token is authorized for,
|
|
// so the body carries no `from` field.
|
|
//
|
|
// `sendViaHostingerMail` throws on any non-2xx so the caller can fall back to
|
|
// SMTP and/or let the queue retry. The thrown Error carries `.status` so HTTP
|
|
// 429 can be mapped to the dispatcher's existing rate-limit backoff.
|
|
|
|
export type HostingerMailConfig = {
|
|
token: string;
|
|
mailboxResourceId: string;
|
|
};
|
|
|
|
export type HostingerMessage = {
|
|
to: string;
|
|
subject: string;
|
|
html: string;
|
|
text?: string;
|
|
};
|
|
|
|
const BASE_URL = 'https://api.mail.hostinger.com';
|
|
|
|
export class HostingerMailError extends Error {
|
|
status: number;
|
|
constructor(message: string, status: number) {
|
|
super(message);
|
|
this.name = 'HostingerMailError';
|
|
this.status = status;
|
|
}
|
|
}
|
|
|
|
export async function sendViaHostingerMail(
|
|
cfg: HostingerMailConfig,
|
|
msg: HostingerMessage,
|
|
): Promise<void> {
|
|
if (!cfg.token || !cfg.mailboxResourceId) {
|
|
throw new Error('Hostinger mail config incomplete (token and mailboxResourceId required)');
|
|
}
|
|
|
|
const url = `${BASE_URL}/api/v1/mailboxes/${encodeURIComponent(cfg.mailboxResourceId)}/send`;
|
|
const body = {
|
|
to: [msg.to],
|
|
subject: msg.subject,
|
|
html: msg.html,
|
|
text: msg.text && msg.text.trim() ? msg.text : undefined,
|
|
};
|
|
|
|
const resp = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${cfg.token}`,
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json',
|
|
},
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (!resp.ok) {
|
|
let detail = '';
|
|
try { detail = (await resp.text()).slice(0, 500); } catch { /* ignore */ }
|
|
throw new HostingerMailError(
|
|
`Hostinger mail API ${resp.status} ${resp.statusText}: ${detail}`,
|
|
resp.status,
|
|
);
|
|
}
|
|
// Drain the body so the connection can be reused.
|
|
try { await resp.text(); } catch { /* ignore */ }
|
|
}
|