@contentgrowth/content-emailing 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/EmailService.cjs +237 -14
- package/dist/backend/EmailService.cjs.map +1 -1
- package/dist/backend/EmailService.d.cts +4 -1
- package/dist/backend/EmailService.d.ts +4 -1
- package/dist/backend/EmailService.js +237 -14
- package/dist/backend/EmailService.js.map +1 -1
- package/dist/backend/routes/index.cjs +237 -14
- package/dist/backend/routes/index.cjs.map +1 -1
- package/dist/backend/routes/index.js +237 -14
- package/dist/backend/routes/index.js.map +1 -1
- package/dist/index.cjs +241 -234
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +241 -234
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -570,6 +570,226 @@ function createDOCacheProvider(doStub, instanceName = "global") {
|
|
|
570
570
|
};
|
|
571
571
|
}
|
|
572
572
|
|
|
573
|
+
// src/backend/EmailLogger.js
|
|
574
|
+
var EmailLogger = class {
|
|
575
|
+
/**
|
|
576
|
+
* @param {Object} db - D1 database binding
|
|
577
|
+
* @param {Object} options - Configuration options
|
|
578
|
+
* @param {string} [options.tableName='system_email_logs'] - Table name for logs
|
|
579
|
+
*/
|
|
580
|
+
constructor(db, options = {}) {
|
|
581
|
+
this.db = db;
|
|
582
|
+
this.tableName = options.tableName || "system_email_logs";
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Creates a logger callback function for use with EmailService.
|
|
586
|
+
* Usage: new EmailService(env, { emailLogger: emailLogger.createCallback() })
|
|
587
|
+
*/
|
|
588
|
+
createCallback() {
|
|
589
|
+
return async (entry) => {
|
|
590
|
+
await this.log(entry);
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Log an email event (pending, sent, or failed)
|
|
595
|
+
* @param {Object} entry - Log entry
|
|
596
|
+
*/
|
|
597
|
+
async log(entry) {
|
|
598
|
+
const {
|
|
599
|
+
event,
|
|
600
|
+
recipientEmail,
|
|
601
|
+
recipientUserId,
|
|
602
|
+
templateId,
|
|
603
|
+
subject,
|
|
604
|
+
provider,
|
|
605
|
+
messageId,
|
|
606
|
+
batchId,
|
|
607
|
+
error,
|
|
608
|
+
errorCode,
|
|
609
|
+
metadata
|
|
610
|
+
} = entry;
|
|
611
|
+
try {
|
|
612
|
+
if (event === "pending") {
|
|
613
|
+
const id = crypto.randomUUID().replace(/-/g, "");
|
|
614
|
+
await this.db.prepare(`
|
|
615
|
+
INSERT INTO ${this.tableName}
|
|
616
|
+
(id, batch_id, recipient_email, recipient_user_id, template_id, subject, status, metadata, created_at)
|
|
617
|
+
VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, strftime('%s', 'now'))
|
|
618
|
+
`).bind(
|
|
619
|
+
id,
|
|
620
|
+
batchId || null,
|
|
621
|
+
recipientEmail,
|
|
622
|
+
recipientUserId || null,
|
|
623
|
+
templateId || "direct",
|
|
624
|
+
subject || null,
|
|
625
|
+
metadata ? JSON.stringify(metadata) : null
|
|
626
|
+
).run();
|
|
627
|
+
} else if (event === "sent") {
|
|
628
|
+
await this.db.prepare(`
|
|
629
|
+
UPDATE ${this.tableName}
|
|
630
|
+
SET status = 'sent',
|
|
631
|
+
provider = ?,
|
|
632
|
+
provider_message_id = ?,
|
|
633
|
+
sent_at = strftime('%s', 'now')
|
|
634
|
+
WHERE recipient_email = ?
|
|
635
|
+
AND template_id = ?
|
|
636
|
+
AND status = 'pending'
|
|
637
|
+
ORDER BY created_at DESC
|
|
638
|
+
LIMIT 1
|
|
639
|
+
`).bind(
|
|
640
|
+
provider || null,
|
|
641
|
+
messageId || null,
|
|
642
|
+
recipientEmail,
|
|
643
|
+
templateId || "direct"
|
|
644
|
+
).run();
|
|
645
|
+
} else if (event === "failed") {
|
|
646
|
+
await this.db.prepare(`
|
|
647
|
+
UPDATE ${this.tableName}
|
|
648
|
+
SET status = 'failed',
|
|
649
|
+
provider = ?,
|
|
650
|
+
error_message = ?,
|
|
651
|
+
error_code = ?
|
|
652
|
+
WHERE recipient_email = ?
|
|
653
|
+
AND template_id = ?
|
|
654
|
+
AND status = 'pending'
|
|
655
|
+
ORDER BY created_at DESC
|
|
656
|
+
LIMIT 1
|
|
657
|
+
`).bind(
|
|
658
|
+
provider || null,
|
|
659
|
+
error || null,
|
|
660
|
+
errorCode || null,
|
|
661
|
+
recipientEmail,
|
|
662
|
+
templateId || "direct"
|
|
663
|
+
).run();
|
|
664
|
+
}
|
|
665
|
+
} catch (e) {
|
|
666
|
+
console.error("[EmailLogger] Failed to log:", e);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Query email logs with filtering
|
|
671
|
+
* @param {Object} options - Query options
|
|
672
|
+
*/
|
|
673
|
+
async query(options = {}) {
|
|
674
|
+
const {
|
|
675
|
+
recipientEmail,
|
|
676
|
+
recipientUserId,
|
|
677
|
+
templateId,
|
|
678
|
+
status,
|
|
679
|
+
batchId,
|
|
680
|
+
limit = 50,
|
|
681
|
+
offset = 0
|
|
682
|
+
} = options;
|
|
683
|
+
const conditions = [];
|
|
684
|
+
const bindings = [];
|
|
685
|
+
if (recipientEmail) {
|
|
686
|
+
conditions.push("recipient_email = ?");
|
|
687
|
+
bindings.push(recipientEmail);
|
|
688
|
+
}
|
|
689
|
+
if (recipientUserId) {
|
|
690
|
+
conditions.push("recipient_user_id = ?");
|
|
691
|
+
bindings.push(recipientUserId);
|
|
692
|
+
}
|
|
693
|
+
if (templateId) {
|
|
694
|
+
conditions.push("template_id = ?");
|
|
695
|
+
bindings.push(templateId);
|
|
696
|
+
}
|
|
697
|
+
if (status) {
|
|
698
|
+
conditions.push("status = ?");
|
|
699
|
+
bindings.push(status);
|
|
700
|
+
}
|
|
701
|
+
if (batchId) {
|
|
702
|
+
conditions.push("batch_id = ?");
|
|
703
|
+
bindings.push(batchId);
|
|
704
|
+
}
|
|
705
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
706
|
+
const countResult = await this.db.prepare(
|
|
707
|
+
`SELECT COUNT(*) as count FROM ${this.tableName} ${whereClause}`
|
|
708
|
+
).bind(...bindings).first();
|
|
709
|
+
const { results } = await this.db.prepare(`
|
|
710
|
+
SELECT id, batch_id, recipient_email, recipient_user_id, template_id, subject,
|
|
711
|
+
status, provider, provider_message_id, error_message, error_code, metadata,
|
|
712
|
+
created_at, sent_at
|
|
713
|
+
FROM ${this.tableName}
|
|
714
|
+
${whereClause}
|
|
715
|
+
ORDER BY created_at DESC
|
|
716
|
+
LIMIT ? OFFSET ?
|
|
717
|
+
`).bind(...bindings, limit, offset).all();
|
|
718
|
+
const logs = (results || []).map((row) => ({
|
|
719
|
+
id: row.id,
|
|
720
|
+
batchId: row.batch_id,
|
|
721
|
+
recipientEmail: row.recipient_email,
|
|
722
|
+
recipientUserId: row.recipient_user_id,
|
|
723
|
+
templateId: row.template_id,
|
|
724
|
+
subject: row.subject,
|
|
725
|
+
status: row.status,
|
|
726
|
+
provider: row.provider,
|
|
727
|
+
providerMessageId: row.provider_message_id,
|
|
728
|
+
errorMessage: row.error_message,
|
|
729
|
+
errorCode: row.error_code,
|
|
730
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
731
|
+
createdAt: row.created_at,
|
|
732
|
+
sentAt: row.sent_at
|
|
733
|
+
}));
|
|
734
|
+
return { logs, total: countResult?.count || 0 };
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Get email sending statistics
|
|
738
|
+
* @param {number} sinceDays - Number of days to look back
|
|
739
|
+
*/
|
|
740
|
+
async getStats(sinceDays = 7) {
|
|
741
|
+
const sinceTimestamp = Math.floor(Date.now() / 1e3) - sinceDays * 24 * 60 * 60;
|
|
742
|
+
const statusResult = await this.db.prepare(`
|
|
743
|
+
SELECT status, COUNT(*) as count
|
|
744
|
+
FROM ${this.tableName}
|
|
745
|
+
WHERE created_at >= ?
|
|
746
|
+
GROUP BY status
|
|
747
|
+
`).bind(sinceTimestamp).all();
|
|
748
|
+
const templateResult = await this.db.prepare(`
|
|
749
|
+
SELECT template_id, COUNT(*) as count
|
|
750
|
+
FROM ${this.tableName}
|
|
751
|
+
WHERE created_at >= ?
|
|
752
|
+
GROUP BY template_id
|
|
753
|
+
`).bind(sinceTimestamp).all();
|
|
754
|
+
const stats = {
|
|
755
|
+
total: 0,
|
|
756
|
+
sent: 0,
|
|
757
|
+
failed: 0,
|
|
758
|
+
pending: 0,
|
|
759
|
+
byTemplate: {}
|
|
760
|
+
};
|
|
761
|
+
(statusResult.results || []).forEach((row) => {
|
|
762
|
+
const count = row.count || 0;
|
|
763
|
+
stats.total += count;
|
|
764
|
+
if (row.status === "sent") stats.sent = count;
|
|
765
|
+
if (row.status === "failed") stats.failed = count;
|
|
766
|
+
if (row.status === "pending") stats.pending = count;
|
|
767
|
+
});
|
|
768
|
+
(templateResult.results || []).forEach((row) => {
|
|
769
|
+
stats.byTemplate[row.template_id] = row.count;
|
|
770
|
+
});
|
|
771
|
+
return stats;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Get recent failed emails for debugging
|
|
775
|
+
* @param {number} limit - Number of failed emails to retrieve
|
|
776
|
+
*/
|
|
777
|
+
async getRecentFailures(limit = 20) {
|
|
778
|
+
const { results } = await this.db.prepare(`
|
|
779
|
+
SELECT id, recipient_email, template_id, subject, error_message, error_code, created_at
|
|
780
|
+
FROM ${this.tableName}
|
|
781
|
+
WHERE status = 'failed'
|
|
782
|
+
ORDER BY created_at DESC
|
|
783
|
+
LIMIT ?
|
|
784
|
+
`).bind(limit).all();
|
|
785
|
+
return results || [];
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
function createEmailLoggerCallback(db, tableName = "system_email_logs") {
|
|
789
|
+
const logger = new EmailLogger(db, { tableName });
|
|
790
|
+
return logger.createCallback();
|
|
791
|
+
}
|
|
792
|
+
|
|
573
793
|
// src/backend/EmailService.js
|
|
574
794
|
var EmailService = class {
|
|
575
795
|
/**
|
|
@@ -577,6 +797,7 @@ var EmailService = class {
|
|
|
577
797
|
* @param {Object} config - Configuration options
|
|
578
798
|
* @param {string} [config.emailTablePrefix='system_email_'] - Prefix for D1 tables
|
|
579
799
|
* @param {Object} [config.defaults] - Default settings (fromName, fromAddress)
|
|
800
|
+
* @param {boolean|Function} [config.emailLogger] - Email logger: true (default), false (disabled), or custom callback
|
|
580
801
|
* @param {Object} [cacheProvider] - Optional cache interface (DO stub or KV wrapper)
|
|
581
802
|
*/
|
|
582
803
|
constructor(env, config = {}, cacheProvider = null) {
|
|
@@ -595,10 +816,6 @@ var EmailService = class {
|
|
|
595
816
|
// Updater function to save settings to backend
|
|
596
817
|
// Signature: async (profile, tenantId, settings) => void
|
|
597
818
|
settingsUpdater: config.settingsUpdater || null,
|
|
598
|
-
// Email Logger callback for tracking all email sends
|
|
599
|
-
// Signature: async (logEntry) => void
|
|
600
|
-
// logEntry: { event: 'pending'|'sent'|'failed', recipientEmail, templateId?, subject?, provider?, messageId?, error?, metadata? }
|
|
601
|
-
emailLogger: config.emailLogger || null,
|
|
602
819
|
// Branding configuration for email templates
|
|
603
820
|
branding: {
|
|
604
821
|
brandName: config.branding?.brandName || "Your App",
|
|
@@ -608,6 +825,16 @@ var EmailService = class {
|
|
|
608
825
|
},
|
|
609
826
|
...config
|
|
610
827
|
};
|
|
828
|
+
if (config.emailLogger === false) {
|
|
829
|
+
this.emailLogger = null;
|
|
830
|
+
} else if (typeof config.emailLogger === "function") {
|
|
831
|
+
this.emailLogger = config.emailLogger;
|
|
832
|
+
} else if (env.DB) {
|
|
833
|
+
const logger = new EmailLogger(env.DB);
|
|
834
|
+
this.emailLogger = logger.createCallback();
|
|
835
|
+
} else {
|
|
836
|
+
this.emailLogger = null;
|
|
837
|
+
}
|
|
611
838
|
if (!cacheProvider && env.EMAIL_TEMPLATE_CACHE) {
|
|
612
839
|
this.cache = createDOCacheProvider(env.EMAIL_TEMPLATE_CACHE);
|
|
613
840
|
} else {
|
|
@@ -861,9 +1088,9 @@ var EmailService = class {
|
|
|
861
1088
|
const htmlContent = html || htmlBody;
|
|
862
1089
|
const textContent = text || textBody;
|
|
863
1090
|
const templateId = metadata?.templateId || "direct";
|
|
864
|
-
if (this.
|
|
1091
|
+
if (this.emailLogger) {
|
|
865
1092
|
try {
|
|
866
|
-
await this.
|
|
1093
|
+
await this.emailLogger({
|
|
867
1094
|
event: "pending",
|
|
868
1095
|
recipientEmail: to,
|
|
869
1096
|
recipientUserId: userId,
|
|
@@ -900,9 +1127,9 @@ var EmailService = class {
|
|
|
900
1127
|
break;
|
|
901
1128
|
default:
|
|
902
1129
|
console.error(`[EmailService] Unknown provider: ${useProvider}`);
|
|
903
|
-
if (this.
|
|
1130
|
+
if (this.emailLogger) {
|
|
904
1131
|
try {
|
|
905
|
-
await this.
|
|
1132
|
+
await this.emailLogger({
|
|
906
1133
|
event: "failed",
|
|
907
1134
|
recipientEmail: to,
|
|
908
1135
|
recipientUserId: userId,
|
|
@@ -920,9 +1147,9 @@ var EmailService = class {
|
|
|
920
1147
|
}
|
|
921
1148
|
if (result) {
|
|
922
1149
|
const messageId = providerMessageId || crypto.randomUUID();
|
|
923
|
-
if (this.
|
|
1150
|
+
if (this.emailLogger) {
|
|
924
1151
|
try {
|
|
925
|
-
await this.
|
|
1152
|
+
await this.emailLogger({
|
|
926
1153
|
event: "sent",
|
|
927
1154
|
recipientEmail: to,
|
|
928
1155
|
recipientUserId: userId,
|
|
@@ -940,9 +1167,9 @@ var EmailService = class {
|
|
|
940
1167
|
return { success: true, messageId };
|
|
941
1168
|
} else {
|
|
942
1169
|
console.error("[EmailService] Failed to send email to:", to);
|
|
943
|
-
if (this.
|
|
1170
|
+
if (this.emailLogger) {
|
|
944
1171
|
try {
|
|
945
|
-
await this.
|
|
1172
|
+
await this.emailLogger({
|
|
946
1173
|
event: "failed",
|
|
947
1174
|
recipientEmail: to,
|
|
948
1175
|
recipientUserId: userId,
|
|
@@ -960,9 +1187,9 @@ var EmailService = class {
|
|
|
960
1187
|
}
|
|
961
1188
|
} catch (error) {
|
|
962
1189
|
console.error("[EmailService] Error sending email:", error);
|
|
963
|
-
if (this.
|
|
1190
|
+
if (this.emailLogger) {
|
|
964
1191
|
try {
|
|
965
|
-
await this.
|
|
1192
|
+
await this.emailLogger({
|
|
966
1193
|
event: "failed",
|
|
967
1194
|
recipientEmail: to,
|
|
968
1195
|
recipientUserId: userId,
|
|
@@ -1166,226 +1393,6 @@ var EmailService = class {
|
|
|
1166
1393
|
}
|
|
1167
1394
|
};
|
|
1168
1395
|
|
|
1169
|
-
// src/backend/EmailLogger.js
|
|
1170
|
-
var EmailLogger = class {
|
|
1171
|
-
/**
|
|
1172
|
-
* @param {Object} db - D1 database binding
|
|
1173
|
-
* @param {Object} options - Configuration options
|
|
1174
|
-
* @param {string} [options.tableName='system_email_logs'] - Table name for logs
|
|
1175
|
-
*/
|
|
1176
|
-
constructor(db, options = {}) {
|
|
1177
|
-
this.db = db;
|
|
1178
|
-
this.tableName = options.tableName || "system_email_logs";
|
|
1179
|
-
}
|
|
1180
|
-
/**
|
|
1181
|
-
* Creates a logger callback function for use with EmailService.
|
|
1182
|
-
* Usage: new EmailService(env, { emailLogger: emailLogger.createCallback() })
|
|
1183
|
-
*/
|
|
1184
|
-
createCallback() {
|
|
1185
|
-
return async (entry) => {
|
|
1186
|
-
await this.log(entry);
|
|
1187
|
-
};
|
|
1188
|
-
}
|
|
1189
|
-
/**
|
|
1190
|
-
* Log an email event (pending, sent, or failed)
|
|
1191
|
-
* @param {Object} entry - Log entry
|
|
1192
|
-
*/
|
|
1193
|
-
async log(entry) {
|
|
1194
|
-
const {
|
|
1195
|
-
event,
|
|
1196
|
-
recipientEmail,
|
|
1197
|
-
recipientUserId,
|
|
1198
|
-
templateId,
|
|
1199
|
-
subject,
|
|
1200
|
-
provider,
|
|
1201
|
-
messageId,
|
|
1202
|
-
batchId,
|
|
1203
|
-
error,
|
|
1204
|
-
errorCode,
|
|
1205
|
-
metadata
|
|
1206
|
-
} = entry;
|
|
1207
|
-
try {
|
|
1208
|
-
if (event === "pending") {
|
|
1209
|
-
const id = crypto.randomUUID().replace(/-/g, "");
|
|
1210
|
-
await this.db.prepare(`
|
|
1211
|
-
INSERT INTO ${this.tableName}
|
|
1212
|
-
(id, batch_id, recipient_email, recipient_user_id, template_id, subject, status, metadata, created_at)
|
|
1213
|
-
VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, strftime('%s', 'now'))
|
|
1214
|
-
`).bind(
|
|
1215
|
-
id,
|
|
1216
|
-
batchId || null,
|
|
1217
|
-
recipientEmail,
|
|
1218
|
-
recipientUserId || null,
|
|
1219
|
-
templateId || "direct",
|
|
1220
|
-
subject || null,
|
|
1221
|
-
metadata ? JSON.stringify(metadata) : null
|
|
1222
|
-
).run();
|
|
1223
|
-
} else if (event === "sent") {
|
|
1224
|
-
await this.db.prepare(`
|
|
1225
|
-
UPDATE ${this.tableName}
|
|
1226
|
-
SET status = 'sent',
|
|
1227
|
-
provider = ?,
|
|
1228
|
-
provider_message_id = ?,
|
|
1229
|
-
sent_at = strftime('%s', 'now')
|
|
1230
|
-
WHERE recipient_email = ?
|
|
1231
|
-
AND template_id = ?
|
|
1232
|
-
AND status = 'pending'
|
|
1233
|
-
ORDER BY created_at DESC
|
|
1234
|
-
LIMIT 1
|
|
1235
|
-
`).bind(
|
|
1236
|
-
provider || null,
|
|
1237
|
-
messageId || null,
|
|
1238
|
-
recipientEmail,
|
|
1239
|
-
templateId || "direct"
|
|
1240
|
-
).run();
|
|
1241
|
-
} else if (event === "failed") {
|
|
1242
|
-
await this.db.prepare(`
|
|
1243
|
-
UPDATE ${this.tableName}
|
|
1244
|
-
SET status = 'failed',
|
|
1245
|
-
provider = ?,
|
|
1246
|
-
error_message = ?,
|
|
1247
|
-
error_code = ?
|
|
1248
|
-
WHERE recipient_email = ?
|
|
1249
|
-
AND template_id = ?
|
|
1250
|
-
AND status = 'pending'
|
|
1251
|
-
ORDER BY created_at DESC
|
|
1252
|
-
LIMIT 1
|
|
1253
|
-
`).bind(
|
|
1254
|
-
provider || null,
|
|
1255
|
-
error || null,
|
|
1256
|
-
errorCode || null,
|
|
1257
|
-
recipientEmail,
|
|
1258
|
-
templateId || "direct"
|
|
1259
|
-
).run();
|
|
1260
|
-
}
|
|
1261
|
-
} catch (e) {
|
|
1262
|
-
console.error("[EmailLogger] Failed to log:", e);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
/**
|
|
1266
|
-
* Query email logs with filtering
|
|
1267
|
-
* @param {Object} options - Query options
|
|
1268
|
-
*/
|
|
1269
|
-
async query(options = {}) {
|
|
1270
|
-
const {
|
|
1271
|
-
recipientEmail,
|
|
1272
|
-
recipientUserId,
|
|
1273
|
-
templateId,
|
|
1274
|
-
status,
|
|
1275
|
-
batchId,
|
|
1276
|
-
limit = 50,
|
|
1277
|
-
offset = 0
|
|
1278
|
-
} = options;
|
|
1279
|
-
const conditions = [];
|
|
1280
|
-
const bindings = [];
|
|
1281
|
-
if (recipientEmail) {
|
|
1282
|
-
conditions.push("recipient_email = ?");
|
|
1283
|
-
bindings.push(recipientEmail);
|
|
1284
|
-
}
|
|
1285
|
-
if (recipientUserId) {
|
|
1286
|
-
conditions.push("recipient_user_id = ?");
|
|
1287
|
-
bindings.push(recipientUserId);
|
|
1288
|
-
}
|
|
1289
|
-
if (templateId) {
|
|
1290
|
-
conditions.push("template_id = ?");
|
|
1291
|
-
bindings.push(templateId);
|
|
1292
|
-
}
|
|
1293
|
-
if (status) {
|
|
1294
|
-
conditions.push("status = ?");
|
|
1295
|
-
bindings.push(status);
|
|
1296
|
-
}
|
|
1297
|
-
if (batchId) {
|
|
1298
|
-
conditions.push("batch_id = ?");
|
|
1299
|
-
bindings.push(batchId);
|
|
1300
|
-
}
|
|
1301
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1302
|
-
const countResult = await this.db.prepare(
|
|
1303
|
-
`SELECT COUNT(*) as count FROM ${this.tableName} ${whereClause}`
|
|
1304
|
-
).bind(...bindings).first();
|
|
1305
|
-
const { results } = await this.db.prepare(`
|
|
1306
|
-
SELECT id, batch_id, recipient_email, recipient_user_id, template_id, subject,
|
|
1307
|
-
status, provider, provider_message_id, error_message, error_code, metadata,
|
|
1308
|
-
created_at, sent_at
|
|
1309
|
-
FROM ${this.tableName}
|
|
1310
|
-
${whereClause}
|
|
1311
|
-
ORDER BY created_at DESC
|
|
1312
|
-
LIMIT ? OFFSET ?
|
|
1313
|
-
`).bind(...bindings, limit, offset).all();
|
|
1314
|
-
const logs = (results || []).map((row) => ({
|
|
1315
|
-
id: row.id,
|
|
1316
|
-
batchId: row.batch_id,
|
|
1317
|
-
recipientEmail: row.recipient_email,
|
|
1318
|
-
recipientUserId: row.recipient_user_id,
|
|
1319
|
-
templateId: row.template_id,
|
|
1320
|
-
subject: row.subject,
|
|
1321
|
-
status: row.status,
|
|
1322
|
-
provider: row.provider,
|
|
1323
|
-
providerMessageId: row.provider_message_id,
|
|
1324
|
-
errorMessage: row.error_message,
|
|
1325
|
-
errorCode: row.error_code,
|
|
1326
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
1327
|
-
createdAt: row.created_at,
|
|
1328
|
-
sentAt: row.sent_at
|
|
1329
|
-
}));
|
|
1330
|
-
return { logs, total: countResult?.count || 0 };
|
|
1331
|
-
}
|
|
1332
|
-
/**
|
|
1333
|
-
* Get email sending statistics
|
|
1334
|
-
* @param {number} sinceDays - Number of days to look back
|
|
1335
|
-
*/
|
|
1336
|
-
async getStats(sinceDays = 7) {
|
|
1337
|
-
const sinceTimestamp = Math.floor(Date.now() / 1e3) - sinceDays * 24 * 60 * 60;
|
|
1338
|
-
const statusResult = await this.db.prepare(`
|
|
1339
|
-
SELECT status, COUNT(*) as count
|
|
1340
|
-
FROM ${this.tableName}
|
|
1341
|
-
WHERE created_at >= ?
|
|
1342
|
-
GROUP BY status
|
|
1343
|
-
`).bind(sinceTimestamp).all();
|
|
1344
|
-
const templateResult = await this.db.prepare(`
|
|
1345
|
-
SELECT template_id, COUNT(*) as count
|
|
1346
|
-
FROM ${this.tableName}
|
|
1347
|
-
WHERE created_at >= ?
|
|
1348
|
-
GROUP BY template_id
|
|
1349
|
-
`).bind(sinceTimestamp).all();
|
|
1350
|
-
const stats = {
|
|
1351
|
-
total: 0,
|
|
1352
|
-
sent: 0,
|
|
1353
|
-
failed: 0,
|
|
1354
|
-
pending: 0,
|
|
1355
|
-
byTemplate: {}
|
|
1356
|
-
};
|
|
1357
|
-
(statusResult.results || []).forEach((row) => {
|
|
1358
|
-
const count = row.count || 0;
|
|
1359
|
-
stats.total += count;
|
|
1360
|
-
if (row.status === "sent") stats.sent = count;
|
|
1361
|
-
if (row.status === "failed") stats.failed = count;
|
|
1362
|
-
if (row.status === "pending") stats.pending = count;
|
|
1363
|
-
});
|
|
1364
|
-
(templateResult.results || []).forEach((row) => {
|
|
1365
|
-
stats.byTemplate[row.template_id] = row.count;
|
|
1366
|
-
});
|
|
1367
|
-
return stats;
|
|
1368
|
-
}
|
|
1369
|
-
/**
|
|
1370
|
-
* Get recent failed emails for debugging
|
|
1371
|
-
* @param {number} limit - Number of failed emails to retrieve
|
|
1372
|
-
*/
|
|
1373
|
-
async getRecentFailures(limit = 20) {
|
|
1374
|
-
const { results } = await this.db.prepare(`
|
|
1375
|
-
SELECT id, recipient_email, template_id, subject, error_message, error_code, created_at
|
|
1376
|
-
FROM ${this.tableName}
|
|
1377
|
-
WHERE status = 'failed'
|
|
1378
|
-
ORDER BY created_at DESC
|
|
1379
|
-
LIMIT ?
|
|
1380
|
-
`).bind(limit).all();
|
|
1381
|
-
return results || [];
|
|
1382
|
-
}
|
|
1383
|
-
};
|
|
1384
|
-
function createEmailLoggerCallback(db, tableName = "system_email_logs") {
|
|
1385
|
-
const logger = new EmailLogger(db, { tableName });
|
|
1386
|
-
return logger.createCallback();
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
1396
|
// src/backend/routes/index.js
|
|
1390
1397
|
var import_hono3 = require("hono");
|
|
1391
1398
|
|