/** * lib/mailcow.ts * Mailcow API client — server-side only. * Uses the single super-admin API key from .env. */ let BASE = process.env.MAILCOW_API_URL?.replace(/\/$/, "") ?? ""; if (BASE && !BASE.startsWith("http")) { BASE = `https://${BASE}`; } const KEY = process.env.MAILCOW_API_KEY ?? ""; async function mfetch(path: string, options: RequestInit = {}) { if (!BASE || !KEY) { console.error("[Mailcow API] MAILCOW_API_URL or MAILCOW_API_KEY is not set in .env"); return new Response(JSON.stringify({ error: "Server configuration error" }), { status: 500 }); } const url = `${BASE}/api/v1${path}`; console.log(`[Mailcow API] ${options.method || "GET"} ${url}`); try { const res = await fetch(url, { ...options, headers: { "Content-Type": "application/json", "X-API-Key": KEY, ...options.headers, }, cache: "no-store", }); if (!res.ok) { const clone = res.clone(); const text = await clone.text(); console.error(`[Mailcow API] Error ${res.status}: ${text}`); } return res; } catch (err: any) { console.error(`[Mailcow API] Fetch failed: ${err.message}`); return new Response(JSON.stringify({ error: "Connection to Mailcow failed" }), { status: 503 }); } } // ─── Types ───────────────────────────────────────────────── export interface MailcowDomain { domain_name: string; description: string; active: string; // "1" | "0" mboxes_in_domain: number; mboxes_left: number; max_num_mboxes_for_domain: number; quota_used_in_domain: string; max_quota_for_domain: number; max_quota_for_mbox: number; aliases_in_domain: number; aliases_left: number; } export interface MailcowMailbox { username: string; // full email e.g. info@domain.com name: string; // display name local_part: string; domain: string; quota: number; // bytes quota_used: number; // bytes active: string; // "1" | "0" created: string; modified: string; } export interface MailcowDomainAdmin { username: string; active: string; domains: string[]; created: string; modified: string; } // ─── Domains ──────────────────────────────────────────────── export async function getDomains(): Promise { const res = await mfetch("/get/domain/all"); if (!res.ok) return []; const data = await res.json(); return Array.isArray(data) ? data : []; } export async function getDomain(domain: string): Promise { const res = await mfetch(`/get/domain/${domain}`); if (!res.ok) return null; const data = await res.json(); return Array.isArray(data) ? data[0] ?? null : data ?? null; } export async function createDomain(payload: { domain: string; description?: string; aliases?: number; mailboxes?: number; defquota?: number; maxquota?: number; quota?: number; active?: number; }) { const body = { active: 1, aliases: 400, mailboxes: 10, defquota: Math.min(payload.quota || 10240, 3072), maxquota: payload.quota || 10240, quota: 10240, ...payload, }; // Double check constraints if (body.maxquota > body.quota) body.maxquota = body.quota; if (body.defquota > body.maxquota) body.defquota = body.maxquota; const res = await mfetch("/add/domain", { method: "POST", body: JSON.stringify(body) }); const data = await res.json(); return { ok: res.ok, data }; } export async function deleteDomain(domain: string) { const res = await mfetch("/delete/domain", { method: "POST", body: JSON.stringify([domain]), }); const data = await res.json(); return { ok: res.ok, data }; } // ─── Domain Admins ────────────────────────────────────────── export async function getDomainAdmins(): Promise { const res = await mfetch("/get/domain-admin/all"); if (!res.ok) return []; const data = await res.json(); return Array.isArray(data) ? data : Object.values(data); } export async function createDomainAdmin(payload: { username: string; password: string; domains: string; // comma-separated or single domain active?: number; }) { const body = { active: 1, password2: payload.password, ...payload, }; const res = await mfetch("/add/domain-admin", { method: "POST", body: JSON.stringify(body) }); const data = await res.json(); return { ok: res.ok, data }; } export async function deleteDomainAdmin(username: string) { const res = await mfetch("/delete/domain-admin", { method: "POST", body: JSON.stringify([username]), }); const data = await res.json(); return { ok: res.ok, data }; } // ─── Mailboxes ────────────────────────────────────────────── export async function getMailboxes(domain: string): Promise { const res = await mfetch(`/get/mailbox/all/${domain}`); if (!res.ok) return []; const data = await res.json(); return Array.isArray(data) ? data : []; } export async function createMailbox(payload: { local_part: string; domain: string; name: string; password: string; quota?: number; active?: number; }) { const body = { quota: 3072, active: 1, password2: payload.password, ...payload, }; const res = await mfetch("/add/mailbox", { method: "POST", body: JSON.stringify(body) }); const data = await res.json(); return { ok: res.ok, data }; } export async function deleteMailbox(emails: string[]) { const res = await mfetch("/delete/mailbox", { method: "POST", body: JSON.stringify(emails), }); const data = await res.json(); return { ok: res.ok, data }; } export async function editMailbox( emails: string[], attr: { password?: string; password2?: string; active?: number; quota?: number; name?: string; } ) { const body = { items: emails, attr }; const res = await mfetch("/edit/mailbox", { method: "POST", body: JSON.stringify(body) }); const data = await res.json(); return { ok: res.ok, data }; } // ─── Status ───────────────────────────────────────────────── export async function getVersionStatus() { const res = await mfetch("/get/status/version"); if (!res.ok) return null; return res.json(); } // ─── DKIM ──────────────────────────────────────────────────── export async function getDKIM(domain: string) { const res = await mfetch(`/get/dkim/${domain}`); if (!res.ok) return null; return res.json(); } // ─── Forwarding / Webhook Setup ────────────────────────────── // Automates the "one-click" configuration of mailbox notifications export async function setupMailboxForwarding(address: string, webhookUrl: string) { // Check if webhookUrl is localhost and warn in console if (webhookUrl.includes("localhost")) { console.warn(`[Setup] WARNING: Using localhost for webhook (${webhookUrl}). Mailcow will not be able to reach this!`); } const body = { address: address, goto: webhookUrl, active: 1, sogo_visible: 0, }; const res = await mfetch("/add/alias", { method: "POST", body: JSON.stringify(body) }); const data = await res.json(); return { ok: res.ok, data }; }