@contentgrowth/content-emailing 0.6.1 → 0.7.1
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 +111 -4
- package/dist/backend/EmailService.cjs.map +1 -1
- package/dist/backend/EmailService.d.cts +7 -1
- package/dist/backend/EmailService.d.ts +7 -1
- package/dist/backend/EmailService.js +111 -4
- package/dist/backend/EmailService.js.map +1 -1
- package/dist/backend/routes/index.cjs +111 -4
- package/dist/backend/routes/index.cjs.map +1 -1
- package/dist/backend/routes/index.js +111 -4
- package/dist/backend/routes/index.js.map +1 -1
- package/dist/frontend/index.cjs +144 -1
- package/dist/frontend/index.cjs.map +1 -1
- package/dist/frontend/index.d.cts +66 -1
- package/dist/frontend/index.d.ts +66 -1
- package/dist/frontend/index.js +143 -1
- package/dist/frontend/index.js.map +1 -1
- package/dist/index.cjs +336 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +334 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schema.sql +32 -0
package/dist/index.cjs
CHANGED
|
@@ -29,11 +29,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
29
29
|
// src/index.js
|
|
30
30
|
var index_exports = {};
|
|
31
31
|
__export(index_exports, {
|
|
32
|
+
EmailLogger: () => EmailLogger,
|
|
32
33
|
EmailService: () => EmailService,
|
|
33
34
|
EmailingCacheDO: () => EmailingCacheDO,
|
|
34
35
|
TemplateEditor: () => TemplateEditor,
|
|
35
36
|
TemplateManager: () => TemplateManager,
|
|
36
37
|
createDOCacheProvider: () => createDOCacheProvider,
|
|
38
|
+
createEmailLoggerCallback: () => createEmailLoggerCallback,
|
|
37
39
|
createEmailRoutes: () => createEmailRoutes,
|
|
38
40
|
createTemplateRoutes: () => createTemplateRoutes,
|
|
39
41
|
createTrackingRoutes: () => createTrackingRoutes,
|
|
@@ -593,6 +595,10 @@ var EmailService = class {
|
|
|
593
595
|
// Updater function to save settings to backend
|
|
594
596
|
// Signature: async (profile, tenantId, settings) => void
|
|
595
597
|
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,
|
|
596
602
|
// Branding configuration for email templates
|
|
597
603
|
branding: {
|
|
598
604
|
brandName: config.branding?.brandName || "Your App",
|
|
@@ -768,11 +774,28 @@ var EmailService = class {
|
|
|
768
774
|
}
|
|
769
775
|
}
|
|
770
776
|
// --- Rendering ---
|
|
777
|
+
/**
|
|
778
|
+
* Pre-process template data to auto-format URLs
|
|
779
|
+
* Scans for strings starting with http:// or https:// and wraps them in Markdown links
|
|
780
|
+
*/
|
|
781
|
+
_preprocessData(data) {
|
|
782
|
+
if (!data || typeof data !== "object") return data;
|
|
783
|
+
const processed = { ...data };
|
|
784
|
+
for (const [key, value] of Object.entries(processed)) {
|
|
785
|
+
if (typeof value === "string" && (value.startsWith("http://") || value.startsWith("https://"))) {
|
|
786
|
+
if (!value.trim().startsWith("[") && !value.includes("](")) {
|
|
787
|
+
processed[key] = `[${value}](${value})`;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return processed;
|
|
792
|
+
}
|
|
771
793
|
async renderTemplate(templateId, data) {
|
|
772
794
|
const template = await this.getTemplate(templateId);
|
|
773
795
|
if (!template) throw new Error(`Template not found: ${templateId}`);
|
|
774
|
-
const
|
|
775
|
-
|
|
796
|
+
const processedData = this._preprocessData(data);
|
|
797
|
+
const subject = import_mustache.default.render(template.subject_template, processedData);
|
|
798
|
+
let markdown = import_mustache.default.render(template.body_markdown, processedData);
|
|
776
799
|
markdown = markdown.replace(/\\n/g, "\n");
|
|
777
800
|
import_marked.marked.use({
|
|
778
801
|
mangle: false,
|
|
@@ -834,13 +857,30 @@ var EmailService = class {
|
|
|
834
857
|
* @param {Object} [params.metadata] - Additional metadata
|
|
835
858
|
* @returns {Promise<Object>} Delivery result
|
|
836
859
|
*/
|
|
837
|
-
async sendEmail({ to, subject, html, htmlBody, text, textBody, provider, profile = "system", tenantId = null, metadata = {} }) {
|
|
860
|
+
async sendEmail({ to, subject, html, htmlBody, text, textBody, provider, profile = "system", tenantId = null, metadata = {}, batchId = null, userId = null }) {
|
|
838
861
|
const htmlContent = html || htmlBody;
|
|
839
862
|
const textContent = text || textBody;
|
|
863
|
+
const templateId = metadata?.templateId || "direct";
|
|
864
|
+
if (this.config.emailLogger) {
|
|
865
|
+
try {
|
|
866
|
+
await this.config.emailLogger({
|
|
867
|
+
event: "pending",
|
|
868
|
+
recipientEmail: to,
|
|
869
|
+
recipientUserId: userId,
|
|
870
|
+
templateId,
|
|
871
|
+
subject,
|
|
872
|
+
batchId,
|
|
873
|
+
metadata
|
|
874
|
+
});
|
|
875
|
+
} catch (e) {
|
|
876
|
+
console.warn("[EmailService] emailLogger pending failed:", e);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
840
879
|
try {
|
|
841
880
|
const settings = await this.loadSettings(profile, tenantId);
|
|
842
881
|
const useProvider = provider || settings.provider || "mailchannels";
|
|
843
882
|
let result;
|
|
883
|
+
let providerMessageId = null;
|
|
844
884
|
switch (useProvider) {
|
|
845
885
|
case "mailchannels":
|
|
846
886
|
result = await this.sendViaMailChannels(to, subject, htmlContent, textContent, settings, metadata);
|
|
@@ -850,22 +890,91 @@ var EmailService = class {
|
|
|
850
890
|
break;
|
|
851
891
|
case "resend":
|
|
852
892
|
result = await this.sendViaResend(to, subject, htmlContent, textContent, settings, metadata);
|
|
893
|
+
if (result && typeof result === "object" && result.id) {
|
|
894
|
+
providerMessageId = result.id;
|
|
895
|
+
result = true;
|
|
896
|
+
}
|
|
853
897
|
break;
|
|
854
898
|
case "sendpulse":
|
|
855
899
|
result = await this.sendViaSendPulse(to, subject, htmlContent, textContent, settings, metadata);
|
|
856
900
|
break;
|
|
857
901
|
default:
|
|
858
902
|
console.error(`[EmailService] Unknown provider: ${useProvider}`);
|
|
903
|
+
if (this.config.emailLogger) {
|
|
904
|
+
try {
|
|
905
|
+
await this.config.emailLogger({
|
|
906
|
+
event: "failed",
|
|
907
|
+
recipientEmail: to,
|
|
908
|
+
recipientUserId: userId,
|
|
909
|
+
templateId,
|
|
910
|
+
subject,
|
|
911
|
+
provider: useProvider,
|
|
912
|
+
batchId,
|
|
913
|
+
error: `Unknown email provider: ${useProvider}`,
|
|
914
|
+
metadata
|
|
915
|
+
});
|
|
916
|
+
} catch (e) {
|
|
917
|
+
}
|
|
918
|
+
}
|
|
859
919
|
return { success: false, error: `Unknown email provider: ${useProvider}` };
|
|
860
920
|
}
|
|
861
921
|
if (result) {
|
|
862
|
-
|
|
922
|
+
const messageId = providerMessageId || crypto.randomUUID();
|
|
923
|
+
if (this.config.emailLogger) {
|
|
924
|
+
try {
|
|
925
|
+
await this.config.emailLogger({
|
|
926
|
+
event: "sent",
|
|
927
|
+
recipientEmail: to,
|
|
928
|
+
recipientUserId: userId,
|
|
929
|
+
templateId,
|
|
930
|
+
subject,
|
|
931
|
+
provider: useProvider,
|
|
932
|
+
messageId,
|
|
933
|
+
batchId,
|
|
934
|
+
metadata
|
|
935
|
+
});
|
|
936
|
+
} catch (e) {
|
|
937
|
+
console.warn("[EmailService] emailLogger sent failed:", e);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return { success: true, messageId };
|
|
863
941
|
} else {
|
|
864
942
|
console.error("[EmailService] Failed to send email to:", to);
|
|
943
|
+
if (this.config.emailLogger) {
|
|
944
|
+
try {
|
|
945
|
+
await this.config.emailLogger({
|
|
946
|
+
event: "failed",
|
|
947
|
+
recipientEmail: to,
|
|
948
|
+
recipientUserId: userId,
|
|
949
|
+
templateId,
|
|
950
|
+
subject,
|
|
951
|
+
provider: useProvider,
|
|
952
|
+
batchId,
|
|
953
|
+
error: "Failed to send email",
|
|
954
|
+
metadata
|
|
955
|
+
});
|
|
956
|
+
} catch (e) {
|
|
957
|
+
}
|
|
958
|
+
}
|
|
865
959
|
return { success: false, error: "Failed to send email" };
|
|
866
960
|
}
|
|
867
961
|
} catch (error) {
|
|
868
962
|
console.error("[EmailService] Error sending email:", error);
|
|
963
|
+
if (this.config.emailLogger) {
|
|
964
|
+
try {
|
|
965
|
+
await this.config.emailLogger({
|
|
966
|
+
event: "failed",
|
|
967
|
+
recipientEmail: to,
|
|
968
|
+
recipientUserId: userId,
|
|
969
|
+
templateId,
|
|
970
|
+
subject,
|
|
971
|
+
batchId,
|
|
972
|
+
error: error.message,
|
|
973
|
+
metadata
|
|
974
|
+
});
|
|
975
|
+
} catch (e) {
|
|
976
|
+
}
|
|
977
|
+
}
|
|
869
978
|
return { success: false, error: error.message };
|
|
870
979
|
}
|
|
871
980
|
}
|
|
@@ -1057,6 +1166,226 @@ var EmailService = class {
|
|
|
1057
1166
|
}
|
|
1058
1167
|
};
|
|
1059
1168
|
|
|
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
|
+
|
|
1060
1389
|
// src/backend/routes/index.js
|
|
1061
1390
|
var import_hono3 = require("hono");
|
|
1062
1391
|
|
|
@@ -1725,7 +2054,7 @@ var TemplateManager = ({
|
|
|
1725
2054
|
},
|
|
1726
2055
|
/* @__PURE__ */ import_react3.default.createElement("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" }, /* @__PURE__ */ import_react3.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 4v16m8-8H4" })),
|
|
1727
2056
|
"Create Template"
|
|
1728
|
-
)), error && /* @__PURE__ */ import_react3.default.createElement("div", { className: "mb-4 p-3 rounded-lg bg-red-50 text-red-800 border border-red-200" }, error, /* @__PURE__ */ import_react3.default.createElement("button", { onClick: () => setError(null), className: "ml-2 underline" }, "Dismiss")), /* @__PURE__ */ import_react3.default.createElement("div", { className: "
|
|
2057
|
+
)), error && /* @__PURE__ */ import_react3.default.createElement("div", { className: "mb-4 p-3 rounded-lg bg-red-50 text-red-800 border border-red-200" }, error, /* @__PURE__ */ import_react3.default.createElement("button", { onClick: () => setError(null), className: "ml-2 underline" }, "Dismiss")), /* @__PURE__ */ import_react3.default.createElement("div", { className: "flex gap-4 mb-6" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-2xl font-bold text-gray-900" }, templates.length), /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-sm text-gray-500" }, "Total")), /* @__PURE__ */ import_react3.default.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-2xl font-bold text-green-600" }, templates.filter((t) => t.is_active).length), /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-sm text-gray-500" }, "Active")), /* @__PURE__ */ import_react3.default.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-2xl font-bold text-yellow-600" }, templates.filter((t) => !t.is_active).length), /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-sm text-gray-500" }, "Inactive")), /* @__PURE__ */ import_react3.default.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-2xl font-bold text-blue-600" }, new Set(templates.map((t) => t.template_type)).size), /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-sm text-gray-500" }, "Types"))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "flex gap-2 mb-6 border-b border-gray-200 pb-3 flex-wrap" }, uniqueTypes.map((type) => /* @__PURE__ */ import_react3.default.createElement(
|
|
1729
2058
|
"button",
|
|
1730
2059
|
{
|
|
1731
2060
|
key: type,
|
|
@@ -1850,11 +2179,13 @@ var TemplateManager = ({
|
|
|
1850
2179
|
};
|
|
1851
2180
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1852
2181
|
0 && (module.exports = {
|
|
2182
|
+
EmailLogger,
|
|
1853
2183
|
EmailService,
|
|
1854
2184
|
EmailingCacheDO,
|
|
1855
2185
|
TemplateEditor,
|
|
1856
2186
|
TemplateManager,
|
|
1857
2187
|
createDOCacheProvider,
|
|
2188
|
+
createEmailLoggerCallback,
|
|
1858
2189
|
createEmailRoutes,
|
|
1859
2190
|
createTemplateRoutes,
|
|
1860
2191
|
createTrackingRoutes,
|