@wopr-network/platform-core 1.54.0 → 1.56.0

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.
@@ -10,7 +10,7 @@ import { escapeHtml } from "./resend-adapter.js";
10
10
  // ---------------------------------------------------------------------------
11
11
  // Shared layout helpers (duplicated locally so this file is self-contained)
12
12
  // ---------------------------------------------------------------------------
13
- function wrapHtml(title, bodyContent) {
13
+ function wrapHtml(title, bodyContent, brandName = "WOPR") {
14
14
  return `<!DOCTYPE html>
15
15
  <html>
16
16
  <head>
@@ -25,7 +25,7 @@ function wrapHtml(title, bodyContent) {
25
25
  <table role="presentation" style="width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
26
26
  ${bodyContent}
27
27
  </table>
28
- <p style="margin-top: 20px; color: #a0aec0; font-size: 12px;">&copy; ${new Date().getFullYear()} WOPR Network. All rights reserved.</p>
28
+ <p style="margin-top: 20px; color: #a0aec0; font-size: 12px;">&copy; ${new Date().getFullYear()} ${escapeHtml(brandName)}. All rights reserved.</p>
29
29
  </td>
30
30
  </tr>
31
31
  </table>
@@ -60,34 +60,40 @@ function footer(text) {
60
60
  </td>
61
61
  </tr>`;
62
62
  }
63
- function copyright() {
64
- return `\n\n(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
63
+ function copyright(brandName = "WOPR") {
64
+ return `\n\n(c) ${new Date().getFullYear()} ${brandName}. All rights reserved.`;
65
+ }
66
+ /** Extract the brand name from template data, defaulting to "WOPR". */
67
+ function brand(data) {
68
+ return data.brandName || "WOPR";
65
69
  }
66
70
  // ---------------------------------------------------------------------------
67
71
  // Individual template renderers
68
72
  // ---------------------------------------------------------------------------
69
73
  function creditsDepletedTemplate(data) {
74
+ const b = brand(data);
70
75
  const creditsUrl = data.creditsUrl || "";
71
76
  const parts = [
72
- heading("Your WOPR Credits Are Depleted"),
73
- paragraph("<p>Your WOPR credit balance has reached $0. All agent capabilities have been paused.</p>" +
77
+ heading(`Your ${escapeHtml(b)} Credits Are Depleted`),
78
+ paragraph(`<p>Your ${escapeHtml(b)} credit balance has reached $0. All agent capabilities have been paused.</p>` +
74
79
  "<p>Add credits now to resume service immediately.</p>"),
75
80
  ];
76
81
  if (creditsUrl)
77
82
  parts.push(button(creditsUrl, "Add Credits"));
78
83
  parts.push(footer("Your data is preserved. Add credits to reactivate."));
79
84
  return {
80
- subject: "Your WOPR credits are depleted — capabilities paused",
81
- html: wrapHtml("Credits Depleted", parts.join("\n")),
82
- text: `Your WOPR Credits Are Depleted\n\nYour WOPR credit balance has reached $0. All agent capabilities have been paused.\n\nAdd credits now to resume service immediately.\n${creditsUrl ? `\nAdd credits: ${creditsUrl}\n` : ""}${copyright()}`,
85
+ subject: `Your ${b} credits are depleted — capabilities paused`,
86
+ html: wrapHtml("Credits Depleted", parts.join("\n"), b),
87
+ text: `Your ${b} Credits Are Depleted\n\nYour ${b} credit balance has reached $0. All agent capabilities have been paused.\n\nAdd credits now to resume service immediately.\n${creditsUrl ? `\nAdd credits: ${creditsUrl}\n` : ""}${copyright(b)}`,
83
88
  };
84
89
  }
85
90
  function gracePeriodStartTemplate(data) {
91
+ const b = brand(data);
86
92
  const balanceDollars = escapeHtml(data.balanceDollars || "$0.00");
87
93
  const graceDays = Number(data.graceDays) || 7;
88
94
  const creditsUrl = data.creditsUrl || "";
89
95
  const parts = [
90
- heading("Action Needed: Top Up to Keep Your WOPRs Running"),
96
+ heading(`Action Needed: Top Up to Keep Your ${escapeHtml(b)} Agents Running`),
91
97
  paragraph(`<p>Your current balance is <strong>${balanceDollars}</strong> and the monthly deduction could not be processed.</p>` +
92
98
  `<p>You have a <strong>${graceDays}-day grace period</strong> to add credits before your account is suspended.</p>`),
93
99
  ];
@@ -95,15 +101,16 @@ function gracePeriodStartTemplate(data) {
95
101
  parts.push(button(creditsUrl, "Add Credits Now"));
96
102
  parts.push(footer("This is a critical notification about your account status."));
97
103
  return {
98
- subject: "Action needed: top up to keep your WOPRs running",
99
- html: wrapHtml("Grace Period Started", parts.join("\n")),
100
- text: `Action Needed: Top Up to Keep Your WOPRs Running\n\nYour current balance is ${data.balanceDollars} and the monthly deduction could not be processed.\n\nYou have a ${graceDays}-day grace period to add credits before your account is suspended.\n${creditsUrl ? `\nAdd credits: ${creditsUrl}\n` : ""}${copyright()}`,
104
+ subject: `Action needed: top up to keep your ${b} agents running`,
105
+ html: wrapHtml("Grace Period Started", parts.join("\n"), b),
106
+ text: `Action Needed: Top Up to Keep Your ${b} Agents Running\n\nYour current balance is ${data.balanceDollars} and the monthly deduction could not be processed.\n\nYou have a ${graceDays}-day grace period to add credits before your account is suspended.\n${creditsUrl ? `\nAdd credits: ${creditsUrl}\n` : ""}${copyright(b)}`,
101
107
  };
102
108
  }
103
109
  function gracePeriodWarningTemplate(data) {
110
+ const b = brand(data);
104
111
  const creditsUrl = data.creditsUrl || "";
105
112
  const parts = [
106
- heading("Last Chance: Your WOPRs Will Be Suspended Tomorrow"),
113
+ heading("Last Chance: Your Agents Will Be Suspended Tomorrow"),
107
114
  paragraph("<p>Your grace period expires tomorrow. If you do not add credits, your account will be suspended.</p>" +
108
115
  "<p>Add credits now to keep your agents running.</p>"),
109
116
  ];
@@ -111,17 +118,18 @@ function gracePeriodWarningTemplate(data) {
111
118
  parts.push(button(creditsUrl, "Add Credits Now", "#dc2626"));
112
119
  parts.push(footer("This is a critical notification about your account status."));
113
120
  return {
114
- subject: "Last chance: your WOPRs will be suspended tomorrow",
115
- html: wrapHtml("Grace Period Warning", parts.join("\n")),
116
- text: `Last Chance: Your WOPRs Will Be Suspended Tomorrow\n\nYour grace period expires tomorrow. If you do not add credits, your account will be suspended.\n${creditsUrl ? `\nAdd credits: ${creditsUrl}\n` : ""}${copyright()}`,
121
+ subject: "Last chance: your agents will be suspended tomorrow",
122
+ html: wrapHtml("Grace Period Warning", parts.join("\n"), b),
123
+ text: `Last Chance: Your Agents Will Be Suspended Tomorrow\n\nYour grace period expires tomorrow. If you do not add credits, your account will be suspended.\n${creditsUrl ? `\nAdd credits: ${creditsUrl}\n` : ""}${copyright(b)}`,
117
124
  };
118
125
  }
119
126
  function autoSuspendedTemplate(data) {
127
+ const b = brand(data);
120
128
  const reason = escapeHtml(data.reason || "Grace period expired");
121
129
  const creditsUrl = data.creditsUrl || "";
122
130
  const parts = [
123
131
  heading("Your Account Has Been Suspended"),
124
- paragraph(`<p>Your WOPR account has been automatically suspended.</p>` +
132
+ paragraph(`<p>Your ${escapeHtml(b)} account has been automatically suspended.</p>` +
125
133
  `<p><strong>Reason:</strong> ${reason}</p>` +
126
134
  `<p>Add credits to reactivate your account immediately.</p>`),
127
135
  ];
@@ -130,11 +138,12 @@ function autoSuspendedTemplate(data) {
130
138
  parts.push(footer("Your data is preserved for 30 days."));
131
139
  return {
132
140
  subject: "Your account has been suspended",
133
- html: wrapHtml("Account Suspended", parts.join("\n")),
134
- text: `Your Account Has Been Suspended\n\nReason: ${data.reason}\n\nAdd credits to reactivate your account immediately.\n${creditsUrl ? `\nAdd credits: ${creditsUrl}\n` : ""}${copyright()}`,
141
+ html: wrapHtml("Account Suspended", parts.join("\n"), b),
142
+ text: `Your Account Has Been Suspended\n\nReason: ${data.reason}\n\nAdd credits to reactivate your account immediately.\n${creditsUrl ? `\nAdd credits: ${creditsUrl}\n` : ""}${copyright(b)}`,
135
143
  };
136
144
  }
137
145
  function autoTopupSuccessTemplate(data) {
146
+ const b = brand(data);
138
147
  const amountDollars = escapeHtml(data.amountDollars || "$0.00");
139
148
  const newBalanceDollars = escapeHtml(data.newBalanceDollars || "$0.00");
140
149
  const creditsUrl = data.creditsUrl || "";
@@ -148,12 +157,14 @@ function autoTopupSuccessTemplate(data) {
148
157
  parts.push(footer("Auto top-up keeps your agents running without interruption."));
149
158
  return {
150
159
  subject: `Auto top-up: ${data.amountDollars} credits added`,
151
- html: wrapHtml("Auto Top-Up Successful", parts.join("\n")),
152
- text: `Auto Top-Up: ${data.amountDollars} Credits Added\n\nYour auto top-up was successful. ${data.amountDollars} in credits has been added.\n\nYour new balance is ${data.newBalanceDollars}.\n${creditsUrl ? `\nView credits: ${creditsUrl}\n` : ""}${copyright()}`,
160
+ html: wrapHtml("Auto Top-Up Successful", parts.join("\n"), b),
161
+ text: `Auto Top-Up: ${data.amountDollars} Credits Added\n\nYour auto top-up was successful. ${data.amountDollars} in credits has been added.\n\nYour new balance is ${data.newBalanceDollars}.\n${creditsUrl ? `\nView credits: ${creditsUrl}\n` : ""}${copyright(b)}`,
153
162
  };
154
163
  }
155
164
  function autoTopupFailedTemplate(data) {
165
+ const b = brand(data);
156
166
  const creditsUrl = data.creditsUrl || "";
167
+ const supportEmail = data.supportEmail || "support@wopr.bot";
157
168
  const parts = [
158
169
  heading("Auto Top-Up Failed"),
159
170
  paragraph("<p>Your auto top-up failed. We were unable to charge your payment method.</p>" +
@@ -161,11 +172,11 @@ function autoTopupFailedTemplate(data) {
161
172
  ];
162
173
  if (creditsUrl)
163
174
  parts.push(button(creditsUrl, "Add Credits"));
164
- parts.push(footer("If you need help, contact support@wopr.bot."));
175
+ parts.push(footer(`If you need help, contact ${escapeHtml(supportEmail)}.`));
165
176
  return {
166
177
  subject: "Auto top-up failed — update your payment method",
167
- html: wrapHtml("Auto Top-Up Failed", parts.join("\n")),
168
- text: `Auto Top-Up Failed\n\nYour auto top-up failed. We were unable to charge your payment method.\n\nPlease update your payment method or add credits manually to avoid service interruption.\n${creditsUrl ? `\nAdd credits: ${creditsUrl}\n` : ""}${copyright()}`,
178
+ html: wrapHtml("Auto Top-Up Failed", parts.join("\n"), b),
179
+ text: `Auto Top-Up Failed\n\nYour auto top-up failed. We were unable to charge your payment method.\n\nPlease update your payment method or add credits manually to avoid service interruption.\n${creditsUrl ? `\nAdd credits: ${creditsUrl}\n` : ""}${copyright(b)}`,
169
180
  };
170
181
  }
171
182
  function cryptoPaymentConfirmedTemplate(data) {
@@ -176,76 +187,84 @@ function cryptoPaymentConfirmedTemplate(data) {
176
187
  paragraph(`<p>Your crypto payment has been confirmed. <strong>${amountDollars}</strong> in credits has been added to your account.</p>` +
177
188
  `<p>Your new balance is <strong>${newBalanceDollars}</strong>.</p>`),
178
189
  ];
179
- parts.push(footer("Thank you for supporting WOPR!"));
190
+ const b = brand(data);
191
+ parts.push(footer(`Thank you for supporting ${escapeHtml(b)}!`));
180
192
  return {
181
193
  subject: `Crypto payment confirmed: ${data.amountDollars} credits added`,
182
- html: wrapHtml("Crypto Payment Confirmed", parts.join("\n")),
183
- text: `Crypto Payment Confirmed: ${data.amountDollars} Credits Added\n\nYour crypto payment has been confirmed. ${data.amountDollars} in credits has been added.\n\nYour new balance is ${data.newBalanceDollars}.\n${copyright()}`,
194
+ html: wrapHtml("Crypto Payment Confirmed", parts.join("\n"), b),
195
+ text: `Crypto Payment Confirmed: ${data.amountDollars} Credits Added\n\nYour crypto payment has been confirmed. ${data.amountDollars} in credits has been added.\n\nYour new balance is ${data.newBalanceDollars}.\n${copyright(b)}`,
184
196
  };
185
197
  }
186
198
  function adminSuspendedTemplate(data) {
199
+ const b = brand(data);
187
200
  const reason = escapeHtml(data.reason || "Policy violation");
201
+ const supportEmail = data.supportEmail || "support@wopr.bot";
188
202
  const parts = [
189
203
  heading("Your Account Has Been Suspended"),
190
- paragraph(`<p>Your WOPR account has been suspended by an administrator.</p>` +
204
+ paragraph(`<p>Your ${escapeHtml(b)} account has been suspended by an administrator.</p>` +
191
205
  `<p><strong>Reason:</strong> ${reason}</p>` +
192
- `<p>If you believe this is an error, please contact support@wopr.bot.</p>`),
206
+ `<p>If you believe this is an error, please contact ${escapeHtml(supportEmail)}.</p>`),
193
207
  ];
194
- parts.push(footer("Contact support@wopr.bot if you have questions."));
208
+ parts.push(footer(`Contact ${escapeHtml(supportEmail)} if you have questions.`));
195
209
  return {
196
210
  subject: "Your account has been suspended",
197
- html: wrapHtml("Account Suspended", parts.join("\n")),
198
- text: `Your Account Has Been Suspended\n\nReason: ${data.reason}\n\nIf you believe this is an error, please contact support@wopr.bot.\n${copyright()}`,
211
+ html: wrapHtml("Account Suspended", parts.join("\n"), b),
212
+ text: `Your Account Has Been Suspended\n\nReason: ${data.reason}\n\nIf you believe this is an error, please contact ${supportEmail}.\n${copyright(b)}`,
199
213
  };
200
214
  }
201
- function adminReactivatedTemplate(_data) {
215
+ function adminReactivatedTemplate(data) {
216
+ const b = brand(data);
202
217
  const parts = [
203
218
  heading("Your Account Has Been Reactivated"),
204
- paragraph("<p>Your WOPR account has been reactivated. You now have full access to all services.</p>" +
219
+ paragraph(`<p>Your ${escapeHtml(b)} account has been reactivated. You now have full access to all services.</p>` +
205
220
  "<p>Your agents and channels are ready to use.</p>"),
206
221
  ];
207
222
  parts.push(footer("Welcome back!"));
208
223
  return {
209
224
  subject: "Your account has been reactivated",
210
- html: wrapHtml("Account Reactivated", parts.join("\n")),
211
- text: `Your Account Has Been Reactivated\n\nYour WOPR account has been reactivated. You now have full access to all services.\n${copyright()}`,
225
+ html: wrapHtml("Account Reactivated", parts.join("\n"), b),
226
+ text: `Your Account Has Been Reactivated\n\nYour ${b} account has been reactivated. You now have full access to all services.\n${copyright(b)}`,
212
227
  };
213
228
  }
214
229
  function creditsGrantedTemplate(data) {
230
+ const b = brand(data);
215
231
  const amountDollars = escapeHtml(data.amountDollars || "$0.00");
216
232
  const reason = escapeHtml(data.reason || "");
217
233
  const parts = [
218
234
  heading(`You Received ${amountDollars} in Credits`),
219
- paragraph(`<p><strong>${amountDollars}</strong> in credits has been added to your WOPR account.</p>` +
235
+ paragraph(`<p><strong>${amountDollars}</strong> in credits has been added to your ${escapeHtml(b)} account.</p>` +
220
236
  (reason ? `<p><strong>Note:</strong> ${reason}</p>` : "")),
221
237
  ];
222
- parts.push(footer("Thank you for using WOPR!"));
238
+ parts.push(footer(`Thank you for using ${escapeHtml(b)}!`));
223
239
  return {
224
240
  subject: `You received ${data.amountDollars} in credits`,
225
- html: wrapHtml("Credits Granted", parts.join("\n")),
226
- text: `You Received ${data.amountDollars} in Credits\n\n${data.amountDollars} has been added to your account.${reason ? `\n\nNote: ${data.reason}` : ""}\n${copyright()}`,
241
+ html: wrapHtml("Credits Granted", parts.join("\n"), b),
242
+ text: `You Received ${data.amountDollars} in Credits\n\n${data.amountDollars} has been added to your account.${reason ? `\n\nNote: ${data.reason}` : ""}\n${copyright(b)}`,
227
243
  };
228
244
  }
229
245
  function roleChangedTemplate(data) {
246
+ const b = brand(data);
230
247
  const newRole = escapeHtml(data.newRole || "");
248
+ const supportEmail = data.supportEmail || "support@wopr.bot";
231
249
  const parts = [
232
250
  heading("Your Role Has Been Updated"),
233
- paragraph(`<p>Your role on the WOPR platform has been updated to <strong>${newRole}</strong>.</p>` +
251
+ paragraph(`<p>Your role on the ${escapeHtml(b)} platform has been updated to <strong>${newRole}</strong>.</p>` +
234
252
  "<p>Your new permissions are now active.</p>"),
235
253
  ];
236
- parts.push(footer("If you did not expect this change, contact support@wopr.bot."));
254
+ parts.push(footer(`If you did not expect this change, contact ${escapeHtml(supportEmail)}.`));
237
255
  return {
238
256
  subject: "Your role has been updated",
239
- html: wrapHtml("Role Changed", parts.join("\n")),
240
- text: `Your Role Has Been Updated\n\nYour role has been updated to ${data.newRole}.\n${copyright()}`,
257
+ html: wrapHtml("Role Changed", parts.join("\n"), b),
258
+ text: `Your Role Has Been Updated\n\nYour role has been updated to ${data.newRole}.\n${copyright(b)}`,
241
259
  };
242
260
  }
243
261
  function teamInviteTemplate(data) {
262
+ const b = brand(data);
244
263
  const tenantName = escapeHtml(data.tenantName || "a tenant");
245
264
  const inviteUrl = data.inviteUrl || "";
246
265
  const parts = [
247
266
  heading(`You've Been Invited to Join ${tenantName}`),
248
- paragraph(`<p>You've been invited to join <strong>${tenantName}</strong> on the WOPR platform.</p>` +
267
+ paragraph(`<p>You've been invited to join <strong>${tenantName}</strong> on the ${escapeHtml(b)} platform.</p>` +
249
268
  "<p>Click below to accept the invitation.</p>"),
250
269
  ];
251
270
  if (inviteUrl)
@@ -253,25 +272,27 @@ function teamInviteTemplate(data) {
253
272
  parts.push(footer("If you did not expect this invitation, you can ignore this email."));
254
273
  return {
255
274
  subject: `You've been invited to join ${data.tenantName}`,
256
- html: wrapHtml("Team Invite", parts.join("\n")),
257
- text: `You've Been Invited to Join ${data.tenantName}\n\n${inviteUrl ? `Accept: ${inviteUrl}\n` : ""}${copyright()}`,
275
+ html: wrapHtml("Team Invite", parts.join("\n"), b),
276
+ text: `You've Been Invited to Join ${data.tenantName}\n\n${inviteUrl ? `Accept: ${inviteUrl}\n` : ""}${copyright(b)}`,
258
277
  };
259
278
  }
260
279
  function agentCreatedTemplate(data) {
280
+ const b = brand(data);
261
281
  const agentName = escapeHtml(data.agentName || "your agent");
262
282
  const parts = [
263
- heading(`Your WOPR ${agentName} Is Ready`),
283
+ heading(`Your ${escapeHtml(b)} ${agentName} Is Ready`),
264
284
  paragraph(`<p>Your new agent <strong>${agentName}</strong> has been created and is ready to use.</p>` +
265
285
  "<p>Connect it to a channel to start receiving and sending messages.</p>"),
266
286
  ];
267
287
  parts.push(footer("Happy building!"));
268
288
  return {
269
- subject: `Your WOPR ${data.agentName} is ready`,
270
- html: wrapHtml("Agent Created", parts.join("\n")),
271
- text: `Your WOPR ${data.agentName} Is Ready\n\nYour new agent has been created and is ready to use.\n${copyright()}`,
289
+ subject: `Your ${b} ${data.agentName} is ready`,
290
+ html: wrapHtml("Agent Created", parts.join("\n"), b),
291
+ text: `Your ${b} ${data.agentName} Is Ready\n\nYour new agent has been created and is ready to use.\n${copyright(b)}`,
272
292
  };
273
293
  }
274
294
  function channelConnectedTemplate(data) {
295
+ const b = brand(data);
275
296
  const channelName = escapeHtml(data.channelName || "A channel");
276
297
  const agentName = escapeHtml(data.agentName || "your agent");
277
298
  const parts = [
@@ -282,11 +303,12 @@ function channelConnectedTemplate(data) {
282
303
  parts.push(footer("Your agent is live!"));
283
304
  return {
284
305
  subject: `${data.channelName} connected to ${data.agentName}`,
285
- html: wrapHtml("Channel Connected", parts.join("\n")),
286
- text: `${data.channelName} Connected to ${data.agentName}\n\n${data.channelName} has been successfully connected to ${data.agentName}.\n${copyright()}`,
306
+ html: wrapHtml("Channel Connected", parts.join("\n"), b),
307
+ text: `${data.channelName} Connected to ${data.agentName}\n\n${data.channelName} has been successfully connected to ${data.agentName}.\n${copyright(b)}`,
287
308
  };
288
309
  }
289
310
  function channelDisconnectedTemplate(data) {
311
+ const b = brand(data);
290
312
  const channelName = escapeHtml(data.channelName || "A channel");
291
313
  const agentName = escapeHtml(data.agentName || "your agent");
292
314
  const reason = escapeHtml(data.reason || "");
@@ -299,77 +321,85 @@ function channelDisconnectedTemplate(data) {
299
321
  parts.push(footer("Your agent data is preserved."));
300
322
  return {
301
323
  subject: `${data.channelName} disconnected from ${data.agentName}`,
302
- html: wrapHtml("Channel Disconnected", parts.join("\n")),
303
- text: `${data.channelName} Disconnected from ${data.agentName}\n\n${reason ? `Reason: ${data.reason}\n\n` : ""}Reconnect from your dashboard to restore service.\n${copyright()}`,
324
+ html: wrapHtml("Channel Disconnected", parts.join("\n"), b),
325
+ text: `${data.channelName} Disconnected from ${data.agentName}\n\n${reason ? `Reason: ${data.reason}\n\n` : ""}Reconnect from your dashboard to restore service.\n${copyright(b)}`,
304
326
  };
305
327
  }
306
328
  function agentSuspendedTemplate(data) {
329
+ const b = brand(data);
307
330
  const agentName = escapeHtml(data.agentName || "Your agent");
308
331
  const reason = escapeHtml(data.reason || "");
332
+ const supportEmail = data.supportEmail || "support@wopr.bot";
309
333
  const parts = [
310
334
  heading(`${agentName} Has Been Paused`),
311
335
  paragraph(`<p>Your agent <strong>${agentName}</strong> has been paused.</p>` +
312
336
  (reason ? `<p><strong>Reason:</strong> ${reason}</p>` : "")),
313
337
  ];
314
- parts.push(footer("Contact support@wopr.bot if you have questions."));
338
+ parts.push(footer(`Contact ${escapeHtml(supportEmail)} if you have questions.`));
315
339
  return {
316
340
  subject: `${data.agentName} has been paused`,
317
- html: wrapHtml("Agent Paused", parts.join("\n")),
318
- text: `${data.agentName} Has Been Paused\n\n${reason ? `Reason: ${data.reason}\n` : ""}${copyright()}`,
341
+ html: wrapHtml("Agent Paused", parts.join("\n"), b),
342
+ text: `${data.agentName} Has Been Paused\n\n${reason ? `Reason: ${data.reason}\n` : ""}${copyright(b)}`,
319
343
  };
320
344
  }
321
345
  function accountDeletionRequestedTemplate(data) {
346
+ const b = brand(data);
322
347
  const email = escapeHtml(data.email || "");
323
348
  const deleteAfterDate = escapeHtml(data.deleteAfterDate || "");
324
349
  const cancelUrl = data.cancelUrl || "";
350
+ const supportEmail = data.supportEmail || "support@wopr.bot";
325
351
  const parts = [
326
352
  heading("Account Deletion Requested"),
327
353
  paragraph(`<p>Hi <strong>${email}</strong>,</p>` +
328
- `<p>We've received your request to delete your WOPR account and all associated data.</p>` +
354
+ `<p>We've received your request to delete your ${escapeHtml(b)} account and all associated data.</p>` +
329
355
  `<p>Your account will be permanently deleted on <strong>${deleteAfterDate}</strong>. Until then, you can cancel this request and keep your account.</p>` +
330
356
  `<p>After that date, all your data will be permanently and irreversibly removed, including bots, conversation history, credit records, and plugin configurations.</p>`),
331
357
  ];
332
358
  if (cancelUrl)
333
359
  parts.push(button(cancelUrl, "Cancel Deletion", "#22c55e"));
334
- parts.push(footer("If you did not request this, please contact support@wopr.bot immediately."));
360
+ parts.push(footer(`If you did not request this, please contact ${escapeHtml(supportEmail)} immediately.`));
335
361
  return {
336
- subject: "Your WOPR account deletion request",
337
- html: wrapHtml("Account Deletion Requested", parts.join("\n")),
338
- text: `Account Deletion Requested\n\nHi ${data.email},\n\nWe've received your request to delete your WOPR account and all associated data.\n\nYour account will be permanently deleted on ${data.deleteAfterDate}. Until then, you can cancel this request.\n\nAfter that date, all your data will be permanently and irreversibly removed.\n${cancelUrl ? `\nCancel deletion: ${cancelUrl}\n` : ""}If you did not request this, please contact support@wopr.bot immediately.${copyright()}`,
362
+ subject: `Your ${b} account deletion request`,
363
+ html: wrapHtml("Account Deletion Requested", parts.join("\n"), b),
364
+ text: `Account Deletion Requested\n\nHi ${data.email},\n\nWe've received your request to delete your ${b} account and all associated data.\n\nYour account will be permanently deleted on ${data.deleteAfterDate}. Until then, you can cancel this request.\n\nAfter that date, all your data will be permanently and irreversibly removed.\n${cancelUrl ? `\nCancel deletion: ${cancelUrl}\n` : ""}If you did not request this, please contact ${supportEmail} immediately.${copyright(b)}`,
339
365
  };
340
366
  }
341
367
  function accountDeletionCancelledTemplate(data) {
368
+ const b = brand(data);
342
369
  const email = escapeHtml(data.email || "");
370
+ const supportEmail = data.supportEmail || "support@wopr.bot";
343
371
  const parts = [
344
372
  heading("Account Deletion Cancelled"),
345
373
  paragraph(`<p>Hi <strong>${email}</strong>,</p>` +
346
374
  `<p>Your account deletion request has been cancelled. Your account and all data remain intact.</p>` +
347
375
  `<p>No further action is needed.</p>`),
348
- footer("If you didn't cancel this, please contact support@wopr.bot."),
376
+ footer(`If you didn't cancel this, please contact ${escapeHtml(supportEmail)}.`),
349
377
  ];
350
378
  return {
351
- subject: "Your WOPR account deletion has been cancelled",
352
- html: wrapHtml("Account Deletion Cancelled", parts.join("\n")),
353
- text: `Account Deletion Cancelled\n\nHi ${data.email},\n\nYour account deletion request has been cancelled. Your account and all data remain intact.\n\nNo further action is needed.${copyright()}`,
379
+ subject: `Your ${b} account deletion has been cancelled`,
380
+ html: wrapHtml("Account Deletion Cancelled", parts.join("\n"), b),
381
+ text: `Account Deletion Cancelled\n\nHi ${data.email},\n\nYour account deletion request has been cancelled. Your account and all data remain intact.\n\nNo further action is needed.${copyright(b)}`,
354
382
  };
355
383
  }
356
384
  function accountDeletionCompletedTemplate(data) {
385
+ const b = brand(data);
357
386
  const email = escapeHtml(data.email || "");
358
387
  const parts = [
359
388
  heading("Your Account Has Been Deleted"),
360
389
  paragraph(`<p>Hi <strong>${email}</strong>,</p>` +
361
- `<p>Your WOPR account and all associated data have been permanently deleted as requested.</p>` +
390
+ `<p>Your ${escapeHtml(b)} account and all associated data have been permanently deleted as requested.</p>` +
362
391
  `<p>This includes all bots, conversation history, credit records, billing data, and plugin configurations.</p>` +
363
- `<p>If you'd like to use WOPR again in the future, you're welcome to create a new account.</p>`),
364
- footer("Thank you for using WOPR. We're sorry to see you go."),
392
+ `<p>If you'd like to use ${escapeHtml(b)} again in the future, you're welcome to create a new account.</p>`),
393
+ footer(`Thank you for using ${escapeHtml(b)}. We're sorry to see you go.`),
365
394
  ];
366
395
  return {
367
- subject: "Your WOPR account has been deleted",
368
- html: wrapHtml("Account Deleted", parts.join("\n")),
369
- text: `Your Account Has Been Deleted\n\nHi ${data.email},\n\nYour WOPR account and all associated data have been permanently deleted as requested.\n\nThis includes all bots, conversation history, credit records, billing data, and plugin configurations.\n\nIf you'd like to use WOPR again in the future, you're welcome to create a new account.${copyright()}`,
396
+ subject: `Your ${b} account has been deleted`,
397
+ html: wrapHtml("Account Deleted", parts.join("\n"), b),
398
+ text: `Your Account Has Been Deleted\n\nHi ${data.email},\n\nYour ${b} account and all associated data have been permanently deleted as requested.\n\nThis includes all bots, conversation history, credit records, billing data, and plugin configurations.\n\nIf you'd like to use ${b} again in the future, you're welcome to create a new account.${copyright(b)}`,
370
399
  };
371
400
  }
372
401
  function dividendWeeklyDigestTemplate(data) {
402
+ const b = brand(data);
373
403
  const weeklyTotal = escapeHtml(data.weeklyTotalDollars || "$0.00");
374
404
  const lifetimeTotal = escapeHtml(data.lifetimeTotalDollars || "$0.00");
375
405
  const distributionCount = Number(data.distributionCount) || 0;
@@ -382,7 +412,7 @@ function dividendWeeklyDigestTemplate(data) {
382
412
  const unsubscribeUrl = data.unsubscribeUrl || "";
383
413
  const poolAvgDollars = `$${(poolAvgCents / 100).toFixed(2)}`;
384
414
  const parts = [
385
- heading(`WOPR Paid You ${weeklyTotal} This Week`),
415
+ heading(`${escapeHtml(b)} Paid You ${weeklyTotal} This Week`),
386
416
  paragraph(`<p>Here's your weekly dividend summary for <strong>${weekStart} – ${weekEnd}</strong>.</p>` +
387
417
  `<table style="width: 100%; border-collapse: collapse; margin: 16px 0;">` +
388
418
  `<tr><td style="padding: 8px 0; color: #4a5568;">This week's dividends</td><td style="padding: 8px 0; text-align: right; font-weight: 600; color: #1a1a1a;">${weeklyTotal}</td></tr>` +
@@ -402,9 +432,9 @@ function dividendWeeklyDigestTemplate(data) {
402
432
  parts.push(`<tr><td style="padding: 0 40px 20px 40px; text-align: center; color: #a0aec0; font-size: 12px;"><a href="${escapeHtml(unsubscribeUrl)}" style="color: #a0aec0; text-decoration: underline;">Unsubscribe from dividend digests</a></td></tr>`);
403
433
  }
404
434
  return {
405
- subject: `WOPR paid you ${data.weeklyTotalDollars} this week`,
406
- html: wrapHtml("Weekly Dividend Digest", parts.join("\n")),
407
- text: `WOPR Paid You ${data.weeklyTotalDollars} This Week\n\n` +
435
+ subject: `${b} paid you ${data.weeklyTotalDollars} this week`,
436
+ html: wrapHtml("Weekly Dividend Digest", parts.join("\n"), b),
437
+ text: `${b} Paid You ${data.weeklyTotalDollars} This Week\n\n` +
408
438
  `Weekly summary for ${data.weekStartDate} – ${data.weekEndDate}:\n\n` +
409
439
  `This week's dividends: ${data.weeklyTotalDollars}\n` +
410
440
  `Days with distributions: ${distributionCount} of 7\n` +
@@ -414,19 +444,20 @@ function dividendWeeklyDigestTemplate(data) {
414
444
  `Next dividend: ${data.nextDividendDate}\n\n` +
415
445
  `Community dividends are distributed daily from platform revenue.` +
416
446
  (unsubscribeUrl ? `\n\nUnsubscribe: ${unsubscribeUrl}` : "") +
417
- copyright(),
447
+ copyright(b),
418
448
  };
419
449
  }
420
450
  function customTemplate(data) {
421
- const subject = data.subject || "Message from WOPR";
451
+ const b = brand(data);
452
+ const subject = data.subject || `Message from ${b}`;
422
453
  const rawBody = data.bodyText || "";
423
454
  const escapedBody = escapeHtml(rawBody).replace(/\n/g, "<br>\n");
424
- const parts = [heading("Message from WOPR"), paragraph(`<p>${escapedBody}</p>`)];
425
- parts.push(footer("This is an administrative message from WOPR Network."));
455
+ const parts = [heading(`Message from ${escapeHtml(b)}`), paragraph(`<p>${escapedBody}</p>`)];
456
+ parts.push(footer(`This is an administrative message from ${escapeHtml(b)}.`));
426
457
  return {
427
458
  subject,
428
- html: wrapHtml(escapeHtml(subject), parts.join("\n")),
429
- text: `${rawBody}\n${copyright()}`,
459
+ html: wrapHtml(escapeHtml(subject), parts.join("\n"), b),
460
+ text: `${rawBody}\n${copyright(b)}`,
430
461
  };
431
462
  }
432
463
  // ---------------------------------------------------------------------------
@@ -473,6 +504,7 @@ export function renderNotificationTemplate(template, data) {
473
504
  case "agent-suspended":
474
505
  return agentSuspendedTemplate(data);
475
506
  case "affiliate-credit-match": {
507
+ const b = brand(data);
476
508
  const amountDollars = escapeHtml(data.amountDollars || "$0.00");
477
509
  const creditsUrl = data.creditsUrl || "";
478
510
  const parts = [
@@ -481,14 +513,15 @@ export function renderNotificationTemplate(template, data) {
481
513
  ];
482
514
  if (creditsUrl)
483
515
  parts.push(button(creditsUrl, "View Credit Balance"));
484
- parts.push(footer("Thank you for spreading the word about WOPR!"));
516
+ parts.push(footer(`Thank you for spreading the word about ${escapeHtml(b)}!`));
485
517
  return {
486
518
  subject: `You earned ${data.amountDollars} in affiliate credits!`,
487
- html: wrapHtml("Affiliate Credits Earned", parts.join("\n")),
488
- text: `You Earned Affiliate Credits!\n\nA user you referred just made their first purchase, and you've been credited ${data.amountDollars}.\n${creditsUrl ? `\nView your balance: ${creditsUrl}\n` : ""}${copyright()}`,
519
+ html: wrapHtml("Affiliate Credits Earned", parts.join("\n"), b),
520
+ text: `You Earned Affiliate Credits!\n\nA user you referred just made their first purchase, and you've been credited ${data.amountDollars}.\n${creditsUrl ? `\nView your balance: ${creditsUrl}\n` : ""}${copyright(b)}`,
489
521
  };
490
522
  }
491
523
  case "spend-alert": {
524
+ const b = brand(data);
492
525
  const currentSpend = escapeHtml(String(data.currentSpendDollars ?? "$0.00"));
493
526
  const alertAt = escapeHtml(String(data.alertAtDollars ?? "$0.00"));
494
527
  const creditsUrl = data.creditsUrl || "";
@@ -503,11 +536,11 @@ export function renderNotificationTemplate(template, data) {
503
536
  parts.push(footer("This alert fires once per day when your spend exceeds your configured threshold."));
504
537
  return {
505
538
  subject: `Spending alert: you've reached your ${data.alertAtDollars} threshold`,
506
- html: wrapHtml("Spending Alert", parts.join("\n")),
539
+ html: wrapHtml("Spending Alert", parts.join("\n"), b),
507
540
  text: `Spending Alert: Threshold Reached\n\nYour monthly spend has reached ${data.currentSpendDollars}, ` +
508
541
  `crossing your alert threshold of ${data.alertAtDollars}.\n` +
509
542
  (creditsUrl ? `\nReview spending: ${creditsUrl}\n` : "") +
510
- copyright(),
543
+ copyright(b),
511
544
  };
512
545
  }
513
546
  case "custom":
@@ -521,6 +554,7 @@ export function renderNotificationTemplate(template, data) {
521
554
  case "account-deletion-completed":
522
555
  return accountDeletionCompletedTemplate(data);
523
556
  case "fleet-update-available": {
557
+ const b = brand(data);
524
558
  const version = escapeHtml(data.version || "");
525
559
  const changelogDate = escapeHtml(data.changelogDate || "");
526
560
  const changelogSummary = escapeHtml(data.changelogSummary || "");
@@ -538,11 +572,12 @@ export function renderNotificationTemplate(template, data) {
538
572
  faParts.push(footer("Review the changelog and update when ready."));
539
573
  return {
540
574
  subject: `Fleet update available: ${data.version}`,
541
- html: wrapHtml("Fleet Update Available", faParts.join("\n")),
542
- text: `Fleet Update Available: ${data.version}\n\nA new version ${data.version} is available for your fleet.\n${changelogDate ? `Released: ${data.changelogDate}\n` : ""}${changelogSummary ? `Changelog: ${data.changelogSummary}\n` : ""}${fleetUrl ? `\nFleet dashboard: ${fleetUrl}\n` : ""}${copyright()}`,
575
+ html: wrapHtml("Fleet Update Available", faParts.join("\n"), b),
576
+ text: `Fleet Update Available: ${data.version}\n\nA new version ${data.version} is available for your fleet.\n${changelogDate ? `Released: ${data.changelogDate}\n` : ""}${changelogSummary ? `Changelog: ${data.changelogSummary}\n` : ""}${fleetUrl ? `\nFleet dashboard: ${fleetUrl}\n` : ""}${copyright(b)}`,
543
577
  };
544
578
  }
545
579
  case "fleet-update-complete": {
580
+ const b = brand(data);
546
581
  const fcVersion = escapeHtml(data.version || "");
547
582
  const succeeded = Number(data.succeeded) || 0;
548
583
  const failed = Number(data.failed) || 0;
@@ -568,22 +603,25 @@ export function renderNotificationTemplate(template, data) {
568
603
  : "Review failed instances and retry if needed."));
569
604
  return {
570
605
  subject: `Fleet updated to ${data.version} \u2014 ${statusText}`,
571
- html: wrapHtml("Fleet Update Complete", fcParts.join("\n")),
572
- text: `Fleet Updated to ${data.version}\n\nSucceeded: ${succeeded}\nFailed: ${failed}\nTotal: ${total}\n${failed > 0 ? "\nSome instances failed to update.\n" : ""}${fleetUrl2 ? `\nFleet dashboard: ${fleetUrl2}\n` : ""}${copyright()}`,
606
+ html: wrapHtml("Fleet Update Complete", fcParts.join("\n"), b),
607
+ text: `Fleet Updated to ${data.version}\n\nSucceeded: ${succeeded}\nFailed: ${failed}\nTotal: ${total}\n${failed > 0 ? "\nSome instances failed to update.\n" : ""}${fleetUrl2 ? `\nFleet dashboard: ${fleetUrl2}\n` : ""}${copyright(b)}`,
573
608
  };
574
609
  }
575
- case "low-balance":
610
+ case "low-balance": {
611
+ const b = brand(data);
576
612
  return {
577
- subject: "Your WOPR credits are running low",
613
+ subject: `Your ${b} credits are running low`,
578
614
  html: wrapHtml("Low Balance", [
579
- heading("Your WOPR Credits Are Running Low"),
615
+ heading(`Your ${escapeHtml(b)} Credits Are Running Low`),
580
616
  paragraph(`<p>Your balance is <strong>${escapeHtml(data.balanceDollars || "$0.00")}</strong>. Top up to keep your agents running.</p>`),
581
617
  ...(data.creditsUrl ? [button(data.creditsUrl, "Buy Credits")] : []),
582
618
  footer("This is an automated billing notification."),
583
- ].join("\n")),
584
- text: `Your WOPR Credits Are Running Low\n\nBalance: ${data.balanceDollars}\n${data.creditsUrl ? `\nBuy credits: ${data.creditsUrl}\n` : ""}${copyright()}`,
619
+ ].join("\n"), b),
620
+ text: `Your ${b} Credits Are Running Low\n\nBalance: ${data.balanceDollars}\n${data.creditsUrl ? `\nBuy credits: ${data.creditsUrl}\n` : ""}${copyright(b)}`,
585
621
  };
586
- case "credit-purchase-receipt":
622
+ }
623
+ case "credit-purchase-receipt": {
624
+ const b = brand(data);
587
625
  return {
588
626
  subject: "Credits added to your account",
589
627
  html: wrapHtml("Credits Added", [
@@ -592,31 +630,36 @@ export function renderNotificationTemplate(template, data) {
592
630
  (data.newBalanceDollars
593
631
  ? `<p>New balance: <strong>${escapeHtml(data.newBalanceDollars)}</strong></p>`
594
632
  : "")),
595
- footer("Thank you for supporting WOPR!"),
596
- ].join("\n")),
597
- text: `Credits Added\n\n${data.amountDollars} added.\n${copyright()}`,
633
+ footer(`Thank you for supporting ${escapeHtml(b)}!`),
634
+ ].join("\n"), b),
635
+ text: `Credits Added\n\n${data.amountDollars} added.\n${copyright(b)}`,
598
636
  };
599
- case "welcome":
637
+ }
638
+ case "welcome": {
639
+ const b = brand(data);
600
640
  return {
601
- subject: "Welcome to WOPR",
641
+ subject: `Welcome to ${b}`,
602
642
  html: wrapHtml("Welcome", [
603
- heading("Welcome to WOPR!"),
643
+ heading(`Welcome to ${escapeHtml(b)}!`),
604
644
  paragraph("<p>Your account is now active. Start building!</p>"),
605
645
  footer("Happy building!"),
606
- ].join("\n")),
607
- text: `Welcome to WOPR!\n\nYour account is now active.\n${copyright()}`,
646
+ ].join("\n"), b),
647
+ text: `Welcome to ${b}!\n\nYour account is now active.\n${copyright(b)}`,
608
648
  };
609
- case "password-reset":
649
+ }
650
+ case "password-reset": {
651
+ const b = brand(data);
610
652
  return {
611
- subject: "Reset your WOPR password",
653
+ subject: `Reset your ${b} password`,
612
654
  html: wrapHtml("Reset Password", [
613
655
  heading("Reset Your Password"),
614
656
  paragraph("<p>Click below to reset your password.</p>"),
615
657
  ...(data.resetUrl ? [button(data.resetUrl, "Reset Password")] : []),
616
658
  footer("If you did not request this, ignore this email."),
617
- ].join("\n")),
618
- text: `Reset Your Password\n\n${data.resetUrl ? `${data.resetUrl}\n` : ""}${copyright()}`,
659
+ ].join("\n"), b),
660
+ text: `Reset Your Password\n\n${data.resetUrl ? `${data.resetUrl}\n` : ""}${copyright(b)}`,
619
661
  };
662
+ }
620
663
  default: {
621
664
  // Exhaustiveness check
622
665
  const _exhaustive = template;