email-delivery-log.js
2.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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}`)
}