80 lines
2.4 KiB
JavaScript
80 lines
2.4 KiB
JavaScript
// Lightweight Postal API client
|
|
// Sends messages via Postal /api/v1/send/message
|
|
// Requires env: POSTAL_URL, POSTAL_API_KEY, POSTAL_FROM, optional POSTAL_FROM_NAME, POSTAL_REPLY_TO
|
|
|
|
import dotenv from 'dotenv'
|
|
dotenv.config()
|
|
|
|
let _fetch = null
|
|
|
|
async function getFetch() {
|
|
if (typeof globalThis.fetch === 'function') return globalThis.fetch
|
|
if (_fetch) return _fetch
|
|
try {
|
|
const mod = await import('node-fetch')
|
|
_fetch = mod.default
|
|
return _fetch
|
|
} catch (e) {
|
|
throw new Error('fetch is not available and node-fetch is not installed. Please install node-fetch or use Node 18+.')
|
|
}
|
|
}
|
|
|
|
function ensureArray(v) {
|
|
if (!v) return []
|
|
if (Array.isArray(v)) return v
|
|
if (typeof v === 'string') return v.split(/[;,]/).map(s => s.trim()).filter(Boolean)
|
|
return [String(v)]
|
|
}
|
|
|
|
function fmtFromAddress(email, name) {
|
|
const e = String(email || '').trim()
|
|
const n = String(name || '').trim()
|
|
if (!e) return ''
|
|
return n ? `${n} <${e}>` : e
|
|
}
|
|
|
|
export async function sendPostalEmail({ to, subject, text, html, cc = [], bcc = [] }) {
|
|
const base = (process.env.POSTAL_URL || '').replace(/\/$/, '')
|
|
const apiKey = process.env.POSTAL_API_KEY || ''
|
|
const fromEmail = process.env.POSTAL_FROM || ''
|
|
const fromName = process.env.POSTAL_FROM_NAME || ''
|
|
const replyTo = process.env.POSTAL_REPLY_TO || ''
|
|
|
|
if (!base || !apiKey || !fromEmail) {
|
|
throw new Error('Postal configuration missing: POSTAL_URL, POSTAL_API_KEY, POSTAL_FROM are required')
|
|
}
|
|
|
|
const endpoint = `${base}/api/v1/send/message`
|
|
const payload = {
|
|
api_key: apiKey,
|
|
from: fmtFromAddress(fromEmail, fromName),
|
|
to: ensureArray(to),
|
|
subject: subject || '',
|
|
html_body: html || undefined,
|
|
plain_body: text || undefined,
|
|
}
|
|
const ccArr = ensureArray(cc)
|
|
const bccArr = ensureArray(bcc)
|
|
if (ccArr.length) payload.cc = ccArr
|
|
if (bccArr.length) payload.bcc = bccArr
|
|
if (replyTo) payload.reply_to = replyTo
|
|
|
|
const fetchImpl = await getFetch()
|
|
const res = await fetchImpl(endpoint, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
let data
|
|
try {
|
|
data = await res.json()
|
|
} catch (e) {
|
|
throw new Error(`Postal response parse error: HTTP ${res.status}`)
|
|
}
|
|
if (!res.ok || data?.status !== 'success') {
|
|
const errMsg = data?.data?.message || data?.message || JSON.stringify(data)
|
|
throw new Error(`Postal send failed: ${errMsg}`)
|
|
}
|
|
return data
|
|
}
|