export type PostalMessageInput = { to: string[]; subject: string; plainBody: string; htmlBody: string; }; export type PostalClient = { enabled: boolean; sendMessage: (input: PostalMessageInput) => Promise; }; export type CreatePostalClientOptions = { baseUrl: string; apiKey: string; from: string; fetchImpl?: typeof fetch; }; export type CreatePostalClientFromEnvOptions = { baseUrlEnv?: string; apiKeyEnv?: string; fromEnv?: string; env?: Record; fetchImpl?: typeof fetch; }; export function createPostalClient(options: CreatePostalClientOptions): PostalClient { const baseUrl = options.baseUrl.trim(); const apiKey = options.apiKey.trim(); const from = options.from.trim(); const fetchImpl = options.fetchImpl ?? fetch; const enabled = baseUrl.length > 0 && apiKey.length > 0 && from.length > 0; return { enabled, async sendMessage(input: PostalMessageInput): Promise { if (!enabled || input.to.length === 0) { return; } const endpoint = new URL("/api/v1/send/message", ensureTrailingSlash(baseUrl)); const response = await fetchImpl(endpoint, { method: "POST", headers: { "Content-Type": "application/json", "X-Server-API-Key": apiKey }, body: JSON.stringify({ from, to: input.to, subject: input.subject, plain_body: input.plainBody, html_body: input.htmlBody }) }); const payload = (await response.json().catch(() => null)) as { status?: string } | null; if (!response.ok || payload?.status !== "success") { throw new Error(`postal_send_failed:${response.status}`); } } }; } export function createPostalClientFromEnv(options: CreatePostalClientFromEnvOptions = {}): PostalClient { const env = options.env ?? process.env; return createPostalClient({ baseUrl: env[options.baseUrlEnv ?? "POSTAL_URL"] ?? "", apiKey: env[options.apiKeyEnv ?? "POSTAL_API_KEY"] ?? "", from: env[options.fromEnv ?? "POSTAL_MESSAGE_FROM"] ?? "", fetchImpl: options.fetchImpl }); } function ensureTrailingSlash(value: string): string { return value.endsWith("/") ? value : `${value}/`; }