// Hostinger Email API transport (https://api.mail.hostinger.com). // // Send endpoint: POST /api/v1/mailboxes/{mailboxResourceId}/send // Auth: Authorization: Bearer (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 { 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 */ } }