import { createHash, randomUUID } from 'node:crypto' /** * @param {import('knex').Knex|undefined} db * @param {{ * id?: string, * provider?: string, * messageId?: string|null, * purpose: string, * toEmail?: string, * status: string, * template?: string, * errorMessage?: string, * metadata?: any * }} record */ export async function recordEmailDelivery(db, record) { if (!db || typeof db !== 'function') throw new Error('recordEmailDelivery requires db') if (!(await db.schema.hasTable('email_delivery_logs'))) throw new Error('recordEmailDelivery requires email_delivery_logs table') const toEmail = requiredString(record.toEmail, 'toEmail') const purpose = requiredString(record.purpose, 'purpose') const status = requiredString(record.status, 'status') const row = { id: record.id || deliveryLogId(record), provider: record.provider || null, message_id: record.messageId || null, purpose, to_email: toEmail, status, template: record.template || null, error_message: record.errorMessage || null, metadata_json: record.metadata ? JSON.stringify(record.metadata) : null, updated_at: db.fn.now(), } await db('email_delivery_logs') .insert(row) .onConflict('id') .merge({ provider: row.provider, message_id: row.message_id, purpose: row.purpose, to_email: row.to_email, status: row.status, template: row.template, error_message: row.error_message, metadata_json: row.metadata_json, updated_at: row.updated_at, }) } /** * @param {{ purpose: string, toEmail?: string }} record */ function deliveryLogId(record) { const purpose = String(record.purpose || 'email').replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 32) const toEmail = String(record.toEmail || '').trim().toLowerCase() const entropy = `${Date.now()}:${randomUUID()}` const digest = createHash('sha1').update(`${purpose}:${toEmail}:${entropy}`).digest('hex').slice(0, 24) return `email_${purpose}_${digest}` } /** * @param {unknown} value * @param {string} field */ function requiredString(value, field) { if (typeof value === 'string' && value.trim().length > 0) return value throw new Error(`recordEmailDelivery requires ${field}`) }