@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/index.d.cts CHANGED
@@ -5,3 +5,68 @@ export { createEmailRoutes, createTemplateRoutes, createTrackingRoutes } from '.
5
5
  export { encodeTrackingLinks, extractVariables, getWebsiteUrl, markdownToPlainText, resetWebsiteUrlCache, wrapInEmailTemplate } from './common/index.cjs';
6
6
  import 'react';
7
7
  import 'hono';
8
+
9
+ /**
10
+ * Create a simple logger callback for EmailService that logs to D1.
11
+ * This is a convenience function for quick setup.
12
+ *
13
+ * Usage:
14
+ * const emailService = new EmailService(env, {
15
+ * emailLogger: createEmailLoggerCallback(env.DB)
16
+ * });
17
+ */
18
+ declare function createEmailLoggerCallback(db: any, tableName?: string): (entry: any) => Promise<void>;
19
+ /**
20
+ * Email Logger Utility
21
+ *
22
+ * Provides built-in email logging to D1 database.
23
+ * Can be used directly or passed to EmailService as the emailLogger callback.
24
+ */
25
+ declare class EmailLogger {
26
+ /**
27
+ * @param {Object} db - D1 database binding
28
+ * @param {Object} options - Configuration options
29
+ * @param {string} [options.tableName='system_email_logs'] - Table name for logs
30
+ */
31
+ constructor(db: any, options?: {
32
+ tableName?: string;
33
+ });
34
+ db: any;
35
+ tableName: string;
36
+ /**
37
+ * Creates a logger callback function for use with EmailService.
38
+ * Usage: new EmailService(env, { emailLogger: emailLogger.createCallback() })
39
+ */
40
+ createCallback(): (entry: any) => Promise<void>;
41
+ /**
42
+ * Log an email event (pending, sent, or failed)
43
+ * @param {Object} entry - Log entry
44
+ */
45
+ log(entry: any): Promise<void>;
46
+ /**
47
+ * Query email logs with filtering
48
+ * @param {Object} options - Query options
49
+ */
50
+ query(options?: any): Promise<{
51
+ logs: any;
52
+ total: any;
53
+ }>;
54
+ /**
55
+ * Get email sending statistics
56
+ * @param {number} sinceDays - Number of days to look back
57
+ */
58
+ getStats(sinceDays?: number): Promise<{
59
+ total: number;
60
+ sent: number;
61
+ failed: number;
62
+ pending: number;
63
+ byTemplate: {};
64
+ }>;
65
+ /**
66
+ * Get recent failed emails for debugging
67
+ * @param {number} limit - Number of failed emails to retrieve
68
+ */
69
+ getRecentFailures(limit?: number): Promise<any>;
70
+ }
71
+
72
+ export { EmailLogger, createEmailLoggerCallback };
package/dist/index.d.ts CHANGED
@@ -5,3 +5,68 @@ export { createEmailRoutes, createTemplateRoutes, createTrackingRoutes } from '.
5
5
  export { encodeTrackingLinks, extractVariables, getWebsiteUrl, markdownToPlainText, resetWebsiteUrlCache, wrapInEmailTemplate } from './common/index.js';
6
6
  import 'react';
7
7
  import 'hono';
8
+
9
+ /**
10
+ * Create a simple logger callback for EmailService that logs to D1.
11
+ * This is a convenience function for quick setup.
12
+ *
13
+ * Usage:
14
+ * const emailService = new EmailService(env, {
15
+ * emailLogger: createEmailLoggerCallback(env.DB)
16
+ * });
17
+ */
18
+ declare function createEmailLoggerCallback(db: any, tableName?: string): (entry: any) => Promise<void>;
19
+ /**
20
+ * Email Logger Utility
21
+ *
22
+ * Provides built-in email logging to D1 database.
23
+ * Can be used directly or passed to EmailService as the emailLogger callback.
24
+ */
25
+ declare class EmailLogger {
26
+ /**
27
+ * @param {Object} db - D1 database binding
28
+ * @param {Object} options - Configuration options
29
+ * @param {string} [options.tableName='system_email_logs'] - Table name for logs
30
+ */
31
+ constructor(db: any, options?: {
32
+ tableName?: string;
33
+ });
34
+ db: any;
35
+ tableName: string;
36
+ /**
37
+ * Creates a logger callback function for use with EmailService.
38
+ * Usage: new EmailService(env, { emailLogger: emailLogger.createCallback() })
39
+ */
40
+ createCallback(): (entry: any) => Promise<void>;
41
+ /**
42
+ * Log an email event (pending, sent, or failed)
43
+ * @param {Object} entry - Log entry
44
+ */
45
+ log(entry: any): Promise<void>;
46
+ /**
47
+ * Query email logs with filtering
48
+ * @param {Object} options - Query options
49
+ */
50
+ query(options?: any): Promise<{
51
+ logs: any;
52
+ total: any;
53
+ }>;
54
+ /**
55
+ * Get email sending statistics
56
+ * @param {number} sinceDays - Number of days to look back
57
+ */
58
+ getStats(sinceDays?: number): Promise<{
59
+ total: number;
60
+ sent: number;
61
+ failed: number;
62
+ pending: number;
63
+ byTemplate: {};
64
+ }>;
65
+ /**
66
+ * Get recent failed emails for debugging
67
+ * @param {number} limit - Number of failed emails to retrieve
68
+ */
69
+ getRecentFailures(limit?: number): Promise<any>;
70
+ }
71
+
72
+ export { EmailLogger, createEmailLoggerCallback };
package/dist/index.js CHANGED
@@ -545,6 +545,10 @@ var EmailService = class {
545
545
  // Updater function to save settings to backend
546
546
  // Signature: async (profile, tenantId, settings) => void
547
547
  settingsUpdater: config.settingsUpdater || null,
548
+ // Email Logger callback for tracking all email sends
549
+ // Signature: async (logEntry) => void
550
+ // logEntry: { event: 'pending'|'sent'|'failed', recipientEmail, templateId?, subject?, provider?, messageId?, error?, metadata? }
551
+ emailLogger: config.emailLogger || null,
548
552
  // Branding configuration for email templates
549
553
  branding: {
550
554
  brandName: config.branding?.brandName || "Your App",
@@ -720,11 +724,28 @@ var EmailService = class {
720
724
  }
721
725
  }
722
726
  // --- Rendering ---
727
+ /**
728
+ * Pre-process template data to auto-format URLs
729
+ * Scans for strings starting with http:// or https:// and wraps them in Markdown links
730
+ */
731
+ _preprocessData(data) {
732
+ if (!data || typeof data !== "object") return data;
733
+ const processed = { ...data };
734
+ for (const [key, value] of Object.entries(processed)) {
735
+ if (typeof value === "string" && (value.startsWith("http://") || value.startsWith("https://"))) {
736
+ if (!value.trim().startsWith("[") && !value.includes("](")) {
737
+ processed[key] = `[${value}](${value})`;
738
+ }
739
+ }
740
+ }
741
+ return processed;
742
+ }
723
743
  async renderTemplate(templateId, data) {
724
744
  const template = await this.getTemplate(templateId);
725
745
  if (!template) throw new Error(`Template not found: ${templateId}`);
726
- const subject = Mustache.render(template.subject_template, data);
727
- let markdown = Mustache.render(template.body_markdown, data);
746
+ const processedData = this._preprocessData(data);
747
+ const subject = Mustache.render(template.subject_template, processedData);
748
+ let markdown = Mustache.render(template.body_markdown, processedData);
728
749
  markdown = markdown.replace(/\\n/g, "\n");
729
750
  marked.use({
730
751
  mangle: false,
@@ -786,13 +807,30 @@ var EmailService = class {
786
807
  * @param {Object} [params.metadata] - Additional metadata
787
808
  * @returns {Promise<Object>} Delivery result
788
809
  */
789
- async sendEmail({ to, subject, html, htmlBody, text, textBody, provider, profile = "system", tenantId = null, metadata = {} }) {
810
+ async sendEmail({ to, subject, html, htmlBody, text, textBody, provider, profile = "system", tenantId = null, metadata = {}, batchId = null, userId = null }) {
790
811
  const htmlContent = html || htmlBody;
791
812
  const textContent = text || textBody;
813
+ const templateId = metadata?.templateId || "direct";
814
+ if (this.config.emailLogger) {
815
+ try {
816
+ await this.config.emailLogger({
817
+ event: "pending",
818
+ recipientEmail: to,
819
+ recipientUserId: userId,
820
+ templateId,
821
+ subject,
822
+ batchId,
823
+ metadata
824
+ });
825
+ } catch (e) {
826
+ console.warn("[EmailService] emailLogger pending failed:", e);
827
+ }
828
+ }
792
829
  try {
793
830
  const settings = await this.loadSettings(profile, tenantId);
794
831
  const useProvider = provider || settings.provider || "mailchannels";
795
832
  let result;
833
+ let providerMessageId = null;
796
834
  switch (useProvider) {
797
835
  case "mailchannels":
798
836
  result = await this.sendViaMailChannels(to, subject, htmlContent, textContent, settings, metadata);
@@ -802,22 +840,91 @@ var EmailService = class {
802
840
  break;
803
841
  case "resend":
804
842
  result = await this.sendViaResend(to, subject, htmlContent, textContent, settings, metadata);
843
+ if (result && typeof result === "object" && result.id) {
844
+ providerMessageId = result.id;
845
+ result = true;
846
+ }
805
847
  break;
806
848
  case "sendpulse":
807
849
  result = await this.sendViaSendPulse(to, subject, htmlContent, textContent, settings, metadata);
808
850
  break;
809
851
  default:
810
852
  console.error(`[EmailService] Unknown provider: ${useProvider}`);
853
+ if (this.config.emailLogger) {
854
+ try {
855
+ await this.config.emailLogger({
856
+ event: "failed",
857
+ recipientEmail: to,
858
+ recipientUserId: userId,
859
+ templateId,
860
+ subject,
861
+ provider: useProvider,
862
+ batchId,
863
+ error: `Unknown email provider: ${useProvider}`,
864
+ metadata
865
+ });
866
+ } catch (e) {
867
+ }
868
+ }
811
869
  return { success: false, error: `Unknown email provider: ${useProvider}` };
812
870
  }
813
871
  if (result) {
814
- return { success: true, messageId: crypto.randomUUID() };
872
+ const messageId = providerMessageId || crypto.randomUUID();
873
+ if (this.config.emailLogger) {
874
+ try {
875
+ await this.config.emailLogger({
876
+ event: "sent",
877
+ recipientEmail: to,
878
+ recipientUserId: userId,
879
+ templateId,
880
+ subject,
881
+ provider: useProvider,
882
+ messageId,
883
+ batchId,
884
+ metadata
885
+ });
886
+ } catch (e) {
887
+ console.warn("[EmailService] emailLogger sent failed:", e);
888
+ }
889
+ }
890
+ return { success: true, messageId };
815
891
  } else {
816
892
  console.error("[EmailService] Failed to send email to:", to);
893
+ if (this.config.emailLogger) {
894
+ try {
895
+ await this.config.emailLogger({
896
+ event: "failed",
897
+ recipientEmail: to,
898
+ recipientUserId: userId,
899
+ templateId,
900
+ subject,
901
+ provider: useProvider,
902
+ batchId,
903
+ error: "Failed to send email",
904
+ metadata
905
+ });
906
+ } catch (e) {
907
+ }
908
+ }
817
909
  return { success: false, error: "Failed to send email" };
818
910
  }
819
911
  } catch (error) {
820
912
  console.error("[EmailService] Error sending email:", error);
913
+ if (this.config.emailLogger) {
914
+ try {
915
+ await this.config.emailLogger({
916
+ event: "failed",
917
+ recipientEmail: to,
918
+ recipientUserId: userId,
919
+ templateId,
920
+ subject,
921
+ batchId,
922
+ error: error.message,
923
+ metadata
924
+ });
925
+ } catch (e) {
926
+ }
927
+ }
821
928
  return { success: false, error: error.message };
822
929
  }
823
930
  }
@@ -1009,6 +1116,226 @@ var EmailService = class {
1009
1116
  }
1010
1117
  };
1011
1118
 
1119
+ // src/backend/EmailLogger.js
1120
+ var EmailLogger = class {
1121
+ /**
1122
+ * @param {Object} db - D1 database binding
1123
+ * @param {Object} options - Configuration options
1124
+ * @param {string} [options.tableName='system_email_logs'] - Table name for logs
1125
+ */
1126
+ constructor(db, options = {}) {
1127
+ this.db = db;
1128
+ this.tableName = options.tableName || "system_email_logs";
1129
+ }
1130
+ /**
1131
+ * Creates a logger callback function for use with EmailService.
1132
+ * Usage: new EmailService(env, { emailLogger: emailLogger.createCallback() })
1133
+ */
1134
+ createCallback() {
1135
+ return async (entry) => {
1136
+ await this.log(entry);
1137
+ };
1138
+ }
1139
+ /**
1140
+ * Log an email event (pending, sent, or failed)
1141
+ * @param {Object} entry - Log entry
1142
+ */
1143
+ async log(entry) {
1144
+ const {
1145
+ event,
1146
+ recipientEmail,
1147
+ recipientUserId,
1148
+ templateId,
1149
+ subject,
1150
+ provider,
1151
+ messageId,
1152
+ batchId,
1153
+ error,
1154
+ errorCode,
1155
+ metadata
1156
+ } = entry;
1157
+ try {
1158
+ if (event === "pending") {
1159
+ const id = crypto.randomUUID().replace(/-/g, "");
1160
+ await this.db.prepare(`
1161
+ INSERT INTO ${this.tableName}
1162
+ (id, batch_id, recipient_email, recipient_user_id, template_id, subject, status, metadata, created_at)
1163
+ VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, strftime('%s', 'now'))
1164
+ `).bind(
1165
+ id,
1166
+ batchId || null,
1167
+ recipientEmail,
1168
+ recipientUserId || null,
1169
+ templateId || "direct",
1170
+ subject || null,
1171
+ metadata ? JSON.stringify(metadata) : null
1172
+ ).run();
1173
+ } else if (event === "sent") {
1174
+ await this.db.prepare(`
1175
+ UPDATE ${this.tableName}
1176
+ SET status = 'sent',
1177
+ provider = ?,
1178
+ provider_message_id = ?,
1179
+ sent_at = strftime('%s', 'now')
1180
+ WHERE recipient_email = ?
1181
+ AND template_id = ?
1182
+ AND status = 'pending'
1183
+ ORDER BY created_at DESC
1184
+ LIMIT 1
1185
+ `).bind(
1186
+ provider || null,
1187
+ messageId || null,
1188
+ recipientEmail,
1189
+ templateId || "direct"
1190
+ ).run();
1191
+ } else if (event === "failed") {
1192
+ await this.db.prepare(`
1193
+ UPDATE ${this.tableName}
1194
+ SET status = 'failed',
1195
+ provider = ?,
1196
+ error_message = ?,
1197
+ error_code = ?
1198
+ WHERE recipient_email = ?
1199
+ AND template_id = ?
1200
+ AND status = 'pending'
1201
+ ORDER BY created_at DESC
1202
+ LIMIT 1
1203
+ `).bind(
1204
+ provider || null,
1205
+ error || null,
1206
+ errorCode || null,
1207
+ recipientEmail,
1208
+ templateId || "direct"
1209
+ ).run();
1210
+ }
1211
+ } catch (e) {
1212
+ console.error("[EmailLogger] Failed to log:", e);
1213
+ }
1214
+ }
1215
+ /**
1216
+ * Query email logs with filtering
1217
+ * @param {Object} options - Query options
1218
+ */
1219
+ async query(options = {}) {
1220
+ const {
1221
+ recipientEmail,
1222
+ recipientUserId,
1223
+ templateId,
1224
+ status,
1225
+ batchId,
1226
+ limit = 50,
1227
+ offset = 0
1228
+ } = options;
1229
+ const conditions = [];
1230
+ const bindings = [];
1231
+ if (recipientEmail) {
1232
+ conditions.push("recipient_email = ?");
1233
+ bindings.push(recipientEmail);
1234
+ }
1235
+ if (recipientUserId) {
1236
+ conditions.push("recipient_user_id = ?");
1237
+ bindings.push(recipientUserId);
1238
+ }
1239
+ if (templateId) {
1240
+ conditions.push("template_id = ?");
1241
+ bindings.push(templateId);
1242
+ }
1243
+ if (status) {
1244
+ conditions.push("status = ?");
1245
+ bindings.push(status);
1246
+ }
1247
+ if (batchId) {
1248
+ conditions.push("batch_id = ?");
1249
+ bindings.push(batchId);
1250
+ }
1251
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1252
+ const countResult = await this.db.prepare(
1253
+ `SELECT COUNT(*) as count FROM ${this.tableName} ${whereClause}`
1254
+ ).bind(...bindings).first();
1255
+ const { results } = await this.db.prepare(`
1256
+ SELECT id, batch_id, recipient_email, recipient_user_id, template_id, subject,
1257
+ status, provider, provider_message_id, error_message, error_code, metadata,
1258
+ created_at, sent_at
1259
+ FROM ${this.tableName}
1260
+ ${whereClause}
1261
+ ORDER BY created_at DESC
1262
+ LIMIT ? OFFSET ?
1263
+ `).bind(...bindings, limit, offset).all();
1264
+ const logs = (results || []).map((row) => ({
1265
+ id: row.id,
1266
+ batchId: row.batch_id,
1267
+ recipientEmail: row.recipient_email,
1268
+ recipientUserId: row.recipient_user_id,
1269
+ templateId: row.template_id,
1270
+ subject: row.subject,
1271
+ status: row.status,
1272
+ provider: row.provider,
1273
+ providerMessageId: row.provider_message_id,
1274
+ errorMessage: row.error_message,
1275
+ errorCode: row.error_code,
1276
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1277
+ createdAt: row.created_at,
1278
+ sentAt: row.sent_at
1279
+ }));
1280
+ return { logs, total: countResult?.count || 0 };
1281
+ }
1282
+ /**
1283
+ * Get email sending statistics
1284
+ * @param {number} sinceDays - Number of days to look back
1285
+ */
1286
+ async getStats(sinceDays = 7) {
1287
+ const sinceTimestamp = Math.floor(Date.now() / 1e3) - sinceDays * 24 * 60 * 60;
1288
+ const statusResult = await this.db.prepare(`
1289
+ SELECT status, COUNT(*) as count
1290
+ FROM ${this.tableName}
1291
+ WHERE created_at >= ?
1292
+ GROUP BY status
1293
+ `).bind(sinceTimestamp).all();
1294
+ const templateResult = await this.db.prepare(`
1295
+ SELECT template_id, COUNT(*) as count
1296
+ FROM ${this.tableName}
1297
+ WHERE created_at >= ?
1298
+ GROUP BY template_id
1299
+ `).bind(sinceTimestamp).all();
1300
+ const stats = {
1301
+ total: 0,
1302
+ sent: 0,
1303
+ failed: 0,
1304
+ pending: 0,
1305
+ byTemplate: {}
1306
+ };
1307
+ (statusResult.results || []).forEach((row) => {
1308
+ const count = row.count || 0;
1309
+ stats.total += count;
1310
+ if (row.status === "sent") stats.sent = count;
1311
+ if (row.status === "failed") stats.failed = count;
1312
+ if (row.status === "pending") stats.pending = count;
1313
+ });
1314
+ (templateResult.results || []).forEach((row) => {
1315
+ stats.byTemplate[row.template_id] = row.count;
1316
+ });
1317
+ return stats;
1318
+ }
1319
+ /**
1320
+ * Get recent failed emails for debugging
1321
+ * @param {number} limit - Number of failed emails to retrieve
1322
+ */
1323
+ async getRecentFailures(limit = 20) {
1324
+ const { results } = await this.db.prepare(`
1325
+ SELECT id, recipient_email, template_id, subject, error_message, error_code, created_at
1326
+ FROM ${this.tableName}
1327
+ WHERE status = 'failed'
1328
+ ORDER BY created_at DESC
1329
+ LIMIT ?
1330
+ `).bind(limit).all();
1331
+ return results || [];
1332
+ }
1333
+ };
1334
+ function createEmailLoggerCallback(db, tableName = "system_email_logs") {
1335
+ const logger = new EmailLogger(db, { tableName });
1336
+ return logger.createCallback();
1337
+ }
1338
+
1012
1339
  // src/backend/routes/index.js
1013
1340
  import { Hono as Hono3 } from "hono";
1014
1341
 
@@ -1677,7 +2004,7 @@ var TemplateManager = ({
1677
2004
  },
1678
2005
  /* @__PURE__ */ React3.createElement("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" }, /* @__PURE__ */ React3.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 4v16m8-8H4" })),
1679
2006
  "Create Template"
1680
- )), error && /* @__PURE__ */ React3.createElement("div", { className: "mb-4 p-3 rounded-lg bg-red-50 text-red-800 border border-red-200" }, error, /* @__PURE__ */ React3.createElement("button", { onClick: () => setError(null), className: "ml-2 underline" }, "Dismiss")), /* @__PURE__ */ React3.createElement("div", { className: "grid grid-cols-4 gap-4 mb-6" }, /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-gray-900" }, templates.length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Total")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-green-600" }, templates.filter((t) => t.is_active).length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Active")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-yellow-600" }, templates.filter((t) => !t.is_active).length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Inactive")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-blue-600" }, new Set(templates.map((t) => t.template_type)).size), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Types"))), /* @__PURE__ */ React3.createElement("div", { className: "flex gap-2 mb-6 border-b border-gray-200 pb-3 flex-wrap" }, uniqueTypes.map((type) => /* @__PURE__ */ React3.createElement(
2007
+ )), error && /* @__PURE__ */ React3.createElement("div", { className: "mb-4 p-3 rounded-lg bg-red-50 text-red-800 border border-red-200" }, error, /* @__PURE__ */ React3.createElement("button", { onClick: () => setError(null), className: "ml-2 underline" }, "Dismiss")), /* @__PURE__ */ React3.createElement("div", { className: "flex gap-4 mb-6" }, /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-gray-900" }, templates.length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Total")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-green-600" }, templates.filter((t) => t.is_active).length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Active")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-yellow-600" }, templates.filter((t) => !t.is_active).length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Inactive")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-blue-600" }, new Set(templates.map((t) => t.template_type)).size), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Types"))), /* @__PURE__ */ React3.createElement("div", { className: "flex gap-2 mb-6 border-b border-gray-200 pb-3 flex-wrap" }, uniqueTypes.map((type) => /* @__PURE__ */ React3.createElement(
1681
2008
  "button",
1682
2009
  {
1683
2010
  key: type,
@@ -1801,11 +2128,13 @@ var TemplateManager = ({
1801
2128
  )))));
1802
2129
  };
1803
2130
  export {
2131
+ EmailLogger,
1804
2132
  EmailService,
1805
2133
  EmailingCacheDO,
1806
2134
  TemplateEditor,
1807
2135
  TemplateManager,
1808
2136
  createDOCacheProvider,
2137
+ createEmailLoggerCallback,
1809
2138
  createEmailRoutes,
1810
2139
  createTemplateRoutes,
1811
2140
  createTrackingRoutes,