@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.
@@ -54,7 +54,7 @@ export type TemplateName = NotificationTemplateName;
54
54
  // Shared layout helpers (duplicated locally so this file is self-contained)
55
55
  // ---------------------------------------------------------------------------
56
56
 
57
- function wrapHtml(title: string, bodyContent: string): string {
57
+ function wrapHtml(title: string, bodyContent: string, brandName = "WOPR"): string {
58
58
  return `<!DOCTYPE html>
59
59
  <html>
60
60
  <head>
@@ -69,7 +69,7 @@ function wrapHtml(title: string, bodyContent: string): string {
69
69
  <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);">
70
70
  ${bodyContent}
71
71
  </table>
72
- <p style="margin-top: 20px; color: #a0aec0; font-size: 12px;">&copy; ${new Date().getFullYear()} WOPR Network. All rights reserved.</p>
72
+ <p style="margin-top: 20px; color: #a0aec0; font-size: 12px;">&copy; ${new Date().getFullYear()} ${escapeHtml(brandName)}. All rights reserved.</p>
73
73
  </td>
74
74
  </tr>
75
75
  </table>
@@ -109,8 +109,13 @@ function footer(text: string): string {
109
109
  </tr>`;
110
110
  }
111
111
 
112
- function copyright(): string {
113
- return `\n\n(c) ${new Date().getFullYear()} WOPR Network. All rights reserved.`;
112
+ function copyright(brandName = "WOPR"): string {
113
+ return `\n\n(c) ${new Date().getFullYear()} ${brandName}. All rights reserved.`;
114
+ }
115
+
116
+ /** Extract the brand name from template data, defaulting to "WOPR". */
117
+ function brand(data: Record<string, unknown>): string {
118
+ return (data.brandName as string) || "WOPR";
114
119
  }
115
120
 
116
121
  // ---------------------------------------------------------------------------
@@ -118,11 +123,12 @@ function copyright(): string {
118
123
  // ---------------------------------------------------------------------------
119
124
 
120
125
  function creditsDepletedTemplate(data: Record<string, unknown>): TemplateResult {
126
+ const b = brand(data);
121
127
  const creditsUrl = (data.creditsUrl as string) || "";
122
128
  const parts = [
123
- heading("Your WOPR Credits Are Depleted"),
129
+ heading(`Your ${escapeHtml(b)} Credits Are Depleted`),
124
130
  paragraph(
125
- "<p>Your WOPR credit balance has reached $0. All agent capabilities have been paused.</p>" +
131
+ `<p>Your ${escapeHtml(b)} credit balance has reached $0. All agent capabilities have been paused.</p>` +
126
132
  "<p>Add credits now to resume service immediately.</p>",
127
133
  ),
128
134
  ];
@@ -130,18 +136,19 @@ function creditsDepletedTemplate(data: Record<string, unknown>): TemplateResult
130
136
  parts.push(footer("Your data is preserved. Add credits to reactivate."));
131
137
 
132
138
  return {
133
- subject: "Your WOPR credits are depleted — capabilities paused",
134
- html: wrapHtml("Credits Depleted", parts.join("\n")),
135
- 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()}`,
139
+ subject: `Your ${b} credits are depleted — capabilities paused`,
140
+ html: wrapHtml("Credits Depleted", parts.join("\n"), b),
141
+ 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)}`,
136
142
  };
137
143
  }
138
144
 
139
145
  function gracePeriodStartTemplate(data: Record<string, unknown>): TemplateResult {
146
+ const b = brand(data);
140
147
  const balanceDollars = escapeHtml((data.balanceDollars as string) || "$0.00");
141
148
  const graceDays = Number(data.graceDays) || 7;
142
149
  const creditsUrl = (data.creditsUrl as string) || "";
143
150
  const parts = [
144
- heading("Action Needed: Top Up to Keep Your WOPRs Running"),
151
+ heading(`Action Needed: Top Up to Keep Your ${escapeHtml(b)} Agents Running`),
145
152
  paragraph(
146
153
  `<p>Your current balance is <strong>${balanceDollars}</strong> and the monthly deduction could not be processed.</p>` +
147
154
  `<p>You have a <strong>${graceDays}-day grace period</strong> to add credits before your account is suspended.</p>`,
@@ -151,16 +158,17 @@ function gracePeriodStartTemplate(data: Record<string, unknown>): TemplateResult
151
158
  parts.push(footer("This is a critical notification about your account status."));
152
159
 
153
160
  return {
154
- subject: "Action needed: top up to keep your WOPRs running",
155
- html: wrapHtml("Grace Period Started", parts.join("\n")),
156
- 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()}`,
161
+ subject: `Action needed: top up to keep your ${b} agents running`,
162
+ html: wrapHtml("Grace Period Started", parts.join("\n"), b),
163
+ 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)}`,
157
164
  };
158
165
  }
159
166
 
160
167
  function gracePeriodWarningTemplate(data: Record<string, unknown>): TemplateResult {
168
+ const b = brand(data);
161
169
  const creditsUrl = (data.creditsUrl as string) || "";
162
170
  const parts = [
163
- heading("Last Chance: Your WOPRs Will Be Suspended Tomorrow"),
171
+ heading("Last Chance: Your Agents Will Be Suspended Tomorrow"),
164
172
  paragraph(
165
173
  "<p>Your grace period expires tomorrow. If you do not add credits, your account will be suspended.</p>" +
166
174
  "<p>Add credits now to keep your agents running.</p>",
@@ -170,19 +178,20 @@ function gracePeriodWarningTemplate(data: Record<string, unknown>): TemplateResu
170
178
  parts.push(footer("This is a critical notification about your account status."));
171
179
 
172
180
  return {
173
- subject: "Last chance: your WOPRs will be suspended tomorrow",
174
- html: wrapHtml("Grace Period Warning", parts.join("\n")),
175
- 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()}`,
181
+ subject: "Last chance: your agents will be suspended tomorrow",
182
+ html: wrapHtml("Grace Period Warning", parts.join("\n"), b),
183
+ 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)}`,
176
184
  };
177
185
  }
178
186
 
179
187
  function autoSuspendedTemplate(data: Record<string, unknown>): TemplateResult {
188
+ const b = brand(data);
180
189
  const reason = escapeHtml((data.reason as string) || "Grace period expired");
181
190
  const creditsUrl = (data.creditsUrl as string) || "";
182
191
  const parts = [
183
192
  heading("Your Account Has Been Suspended"),
184
193
  paragraph(
185
- `<p>Your WOPR account has been automatically suspended.</p>` +
194
+ `<p>Your ${escapeHtml(b)} account has been automatically suspended.</p>` +
186
195
  `<p><strong>Reason:</strong> ${reason}</p>` +
187
196
  `<p>Add credits to reactivate your account immediately.</p>`,
188
197
  ),
@@ -192,12 +201,13 @@ function autoSuspendedTemplate(data: Record<string, unknown>): TemplateResult {
192
201
 
193
202
  return {
194
203
  subject: "Your account has been suspended",
195
- html: wrapHtml("Account Suspended", parts.join("\n")),
196
- 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()}`,
204
+ html: wrapHtml("Account Suspended", parts.join("\n"), b),
205
+ 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)}`,
197
206
  };
198
207
  }
199
208
 
200
209
  function autoTopupSuccessTemplate(data: Record<string, unknown>): TemplateResult {
210
+ const b = brand(data);
201
211
  const amountDollars = escapeHtml((data.amountDollars as string) || "$0.00");
202
212
  const newBalanceDollars = escapeHtml((data.newBalanceDollars as string) || "$0.00");
203
213
  const creditsUrl = (data.creditsUrl as string) || "";
@@ -213,13 +223,15 @@ function autoTopupSuccessTemplate(data: Record<string, unknown>): TemplateResult
213
223
 
214
224
  return {
215
225
  subject: `Auto top-up: ${data.amountDollars} credits added`,
216
- html: wrapHtml("Auto Top-Up Successful", parts.join("\n")),
217
- 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()}`,
226
+ html: wrapHtml("Auto Top-Up Successful", parts.join("\n"), b),
227
+ 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)}`,
218
228
  };
219
229
  }
220
230
 
221
231
  function autoTopupFailedTemplate(data: Record<string, unknown>): TemplateResult {
232
+ const b = brand(data);
222
233
  const creditsUrl = (data.creditsUrl as string) || "";
234
+ const supportEmail = (data.supportEmail as string) || "support@wopr.bot";
223
235
  const parts = [
224
236
  heading("Auto Top-Up Failed"),
225
237
  paragraph(
@@ -228,12 +240,12 @@ function autoTopupFailedTemplate(data: Record<string, unknown>): TemplateResult
228
240
  ),
229
241
  ];
230
242
  if (creditsUrl) parts.push(button(creditsUrl, "Add Credits"));
231
- parts.push(footer("If you need help, contact support@wopr.bot."));
243
+ parts.push(footer(`If you need help, contact ${escapeHtml(supportEmail)}.`));
232
244
 
233
245
  return {
234
246
  subject: "Auto top-up failed — update your payment method",
235
- html: wrapHtml("Auto Top-Up Failed", parts.join("\n")),
236
- 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()}`,
247
+ html: wrapHtml("Auto Top-Up Failed", parts.join("\n"), b),
248
+ 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)}`,
237
249
  };
238
250
  }
239
251
 
@@ -247,39 +259,43 @@ function cryptoPaymentConfirmedTemplate(data: Record<string, unknown>): Template
247
259
  `<p>Your new balance is <strong>${newBalanceDollars}</strong>.</p>`,
248
260
  ),
249
261
  ];
250
- parts.push(footer("Thank you for supporting WOPR!"));
262
+ const b = brand(data);
263
+ parts.push(footer(`Thank you for supporting ${escapeHtml(b)}!`));
251
264
 
252
265
  return {
253
266
  subject: `Crypto payment confirmed: ${data.amountDollars} credits added`,
254
- html: wrapHtml("Crypto Payment Confirmed", parts.join("\n")),
255
- 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()}`,
267
+ html: wrapHtml("Crypto Payment Confirmed", parts.join("\n"), b),
268
+ 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)}`,
256
269
  };
257
270
  }
258
271
 
259
272
  function adminSuspendedTemplate(data: Record<string, unknown>): TemplateResult {
273
+ const b = brand(data);
260
274
  const reason = escapeHtml((data.reason as string) || "Policy violation");
275
+ const supportEmail = (data.supportEmail as string) || "support@wopr.bot";
261
276
  const parts = [
262
277
  heading("Your Account Has Been Suspended"),
263
278
  paragraph(
264
- `<p>Your WOPR account has been suspended by an administrator.</p>` +
279
+ `<p>Your ${escapeHtml(b)} account has been suspended by an administrator.</p>` +
265
280
  `<p><strong>Reason:</strong> ${reason}</p>` +
266
- `<p>If you believe this is an error, please contact support@wopr.bot.</p>`,
281
+ `<p>If you believe this is an error, please contact ${escapeHtml(supportEmail)}.</p>`,
267
282
  ),
268
283
  ];
269
- parts.push(footer("Contact support@wopr.bot if you have questions."));
284
+ parts.push(footer(`Contact ${escapeHtml(supportEmail)} if you have questions.`));
270
285
 
271
286
  return {
272
287
  subject: "Your account has been suspended",
273
- html: wrapHtml("Account Suspended", parts.join("\n")),
274
- 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()}`,
288
+ html: wrapHtml("Account Suspended", parts.join("\n"), b),
289
+ text: `Your Account Has Been Suspended\n\nReason: ${data.reason}\n\nIf you believe this is an error, please contact ${supportEmail}.\n${copyright(b)}`,
275
290
  };
276
291
  }
277
292
 
278
- function adminReactivatedTemplate(_data: Record<string, unknown>): TemplateResult {
293
+ function adminReactivatedTemplate(data: Record<string, unknown>): TemplateResult {
294
+ const b = brand(data);
279
295
  const parts = [
280
296
  heading("Your Account Has Been Reactivated"),
281
297
  paragraph(
282
- "<p>Your WOPR account has been reactivated. You now have full access to all services.</p>" +
298
+ `<p>Your ${escapeHtml(b)} account has been reactivated. You now have full access to all services.</p>` +
283
299
  "<p>Your agents and channels are ready to use.</p>",
284
300
  ),
285
301
  ];
@@ -287,55 +303,59 @@ function adminReactivatedTemplate(_data: Record<string, unknown>): TemplateResul
287
303
 
288
304
  return {
289
305
  subject: "Your account has been reactivated",
290
- html: wrapHtml("Account Reactivated", parts.join("\n")),
291
- text: `Your Account Has Been Reactivated\n\nYour WOPR account has been reactivated. You now have full access to all services.\n${copyright()}`,
306
+ html: wrapHtml("Account Reactivated", parts.join("\n"), b),
307
+ text: `Your Account Has Been Reactivated\n\nYour ${b} account has been reactivated. You now have full access to all services.\n${copyright(b)}`,
292
308
  };
293
309
  }
294
310
 
295
311
  function creditsGrantedTemplate(data: Record<string, unknown>): TemplateResult {
312
+ const b = brand(data);
296
313
  const amountDollars = escapeHtml((data.amountDollars as string) || "$0.00");
297
314
  const reason = escapeHtml((data.reason as string) || "");
298
315
  const parts = [
299
316
  heading(`You Received ${amountDollars} in Credits`),
300
317
  paragraph(
301
- `<p><strong>${amountDollars}</strong> in credits has been added to your WOPR account.</p>` +
318
+ `<p><strong>${amountDollars}</strong> in credits has been added to your ${escapeHtml(b)} account.</p>` +
302
319
  (reason ? `<p><strong>Note:</strong> ${reason}</p>` : ""),
303
320
  ),
304
321
  ];
305
- parts.push(footer("Thank you for using WOPR!"));
322
+ parts.push(footer(`Thank you for using ${escapeHtml(b)}!`));
306
323
 
307
324
  return {
308
325
  subject: `You received ${data.amountDollars} in credits`,
309
- html: wrapHtml("Credits Granted", parts.join("\n")),
310
- text: `You Received ${data.amountDollars} in Credits\n\n${data.amountDollars} has been added to your account.${reason ? `\n\nNote: ${data.reason}` : ""}\n${copyright()}`,
326
+ html: wrapHtml("Credits Granted", parts.join("\n"), b),
327
+ 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)}`,
311
328
  };
312
329
  }
313
330
 
314
331
  function roleChangedTemplate(data: Record<string, unknown>): TemplateResult {
332
+ const b = brand(data);
315
333
  const newRole = escapeHtml((data.newRole as string) || "");
334
+ const supportEmail = (data.supportEmail as string) || "support@wopr.bot";
316
335
  const parts = [
317
336
  heading("Your Role Has Been Updated"),
318
337
  paragraph(
319
- `<p>Your role on the WOPR platform has been updated to <strong>${newRole}</strong>.</p>` +
338
+ `<p>Your role on the ${escapeHtml(b)} platform has been updated to <strong>${newRole}</strong>.</p>` +
320
339
  "<p>Your new permissions are now active.</p>",
321
340
  ),
322
341
  ];
323
- parts.push(footer("If you did not expect this change, contact support@wopr.bot."));
342
+ parts.push(footer(`If you did not expect this change, contact ${escapeHtml(supportEmail)}.`));
324
343
 
325
344
  return {
326
345
  subject: "Your role has been updated",
327
- html: wrapHtml("Role Changed", parts.join("\n")),
328
- text: `Your Role Has Been Updated\n\nYour role has been updated to ${data.newRole}.\n${copyright()}`,
346
+ html: wrapHtml("Role Changed", parts.join("\n"), b),
347
+ text: `Your Role Has Been Updated\n\nYour role has been updated to ${data.newRole}.\n${copyright(b)}`,
329
348
  };
330
349
  }
331
350
 
332
351
  function teamInviteTemplate(data: Record<string, unknown>): TemplateResult {
352
+ const b = brand(data);
333
353
  const tenantName = escapeHtml((data.tenantName as string) || "a tenant");
334
354
  const inviteUrl = (data.inviteUrl as string) || "";
335
355
  const parts = [
336
356
  heading(`You've Been Invited to Join ${tenantName}`),
337
357
  paragraph(
338
- `<p>You've been invited to join <strong>${tenantName}</strong> on the WOPR platform.</p>` +
358
+ `<p>You've been invited to join <strong>${tenantName}</strong> on the ${escapeHtml(b)} platform.</p>` +
339
359
  "<p>Click below to accept the invitation.</p>",
340
360
  ),
341
361
  ];
@@ -344,15 +364,16 @@ function teamInviteTemplate(data: Record<string, unknown>): TemplateResult {
344
364
 
345
365
  return {
346
366
  subject: `You've been invited to join ${data.tenantName}`,
347
- html: wrapHtml("Team Invite", parts.join("\n")),
348
- text: `You've Been Invited to Join ${data.tenantName}\n\n${inviteUrl ? `Accept: ${inviteUrl}\n` : ""}${copyright()}`,
367
+ html: wrapHtml("Team Invite", parts.join("\n"), b),
368
+ text: `You've Been Invited to Join ${data.tenantName}\n\n${inviteUrl ? `Accept: ${inviteUrl}\n` : ""}${copyright(b)}`,
349
369
  };
350
370
  }
351
371
 
352
372
  function agentCreatedTemplate(data: Record<string, unknown>): TemplateResult {
373
+ const b = brand(data);
353
374
  const agentName = escapeHtml((data.agentName as string) || "your agent");
354
375
  const parts = [
355
- heading(`Your WOPR ${agentName} Is Ready`),
376
+ heading(`Your ${escapeHtml(b)} ${agentName} Is Ready`),
356
377
  paragraph(
357
378
  `<p>Your new agent <strong>${agentName}</strong> has been created and is ready to use.</p>` +
358
379
  "<p>Connect it to a channel to start receiving and sending messages.</p>",
@@ -361,13 +382,14 @@ function agentCreatedTemplate(data: Record<string, unknown>): TemplateResult {
361
382
  parts.push(footer("Happy building!"));
362
383
 
363
384
  return {
364
- subject: `Your WOPR ${data.agentName} is ready`,
365
- html: wrapHtml("Agent Created", parts.join("\n")),
366
- text: `Your WOPR ${data.agentName} Is Ready\n\nYour new agent has been created and is ready to use.\n${copyright()}`,
385
+ subject: `Your ${b} ${data.agentName} is ready`,
386
+ html: wrapHtml("Agent Created", parts.join("\n"), b),
387
+ text: `Your ${b} ${data.agentName} Is Ready\n\nYour new agent has been created and is ready to use.\n${copyright(b)}`,
367
388
  };
368
389
  }
369
390
 
370
391
  function channelConnectedTemplate(data: Record<string, unknown>): TemplateResult {
392
+ const b = brand(data);
371
393
  const channelName = escapeHtml((data.channelName as string) || "A channel");
372
394
  const agentName = escapeHtml((data.agentName as string) || "your agent");
373
395
  const parts = [
@@ -381,12 +403,13 @@ function channelConnectedTemplate(data: Record<string, unknown>): TemplateResult
381
403
 
382
404
  return {
383
405
  subject: `${data.channelName} connected to ${data.agentName}`,
384
- html: wrapHtml("Channel Connected", parts.join("\n")),
385
- text: `${data.channelName} Connected to ${data.agentName}\n\n${data.channelName} has been successfully connected to ${data.agentName}.\n${copyright()}`,
406
+ html: wrapHtml("Channel Connected", parts.join("\n"), b),
407
+ text: `${data.channelName} Connected to ${data.agentName}\n\n${data.channelName} has been successfully connected to ${data.agentName}.\n${copyright(b)}`,
386
408
  };
387
409
  }
388
410
 
389
411
  function channelDisconnectedTemplate(data: Record<string, unknown>): TemplateResult {
412
+ const b = brand(data);
390
413
  const channelName = escapeHtml((data.channelName as string) || "A channel");
391
414
  const agentName = escapeHtml((data.agentName as string) || "your agent");
392
415
  const reason = escapeHtml((data.reason as string) || "");
@@ -402,14 +425,16 @@ function channelDisconnectedTemplate(data: Record<string, unknown>): TemplateRes
402
425
 
403
426
  return {
404
427
  subject: `${data.channelName} disconnected from ${data.agentName}`,
405
- html: wrapHtml("Channel Disconnected", parts.join("\n")),
406
- text: `${data.channelName} Disconnected from ${data.agentName}\n\n${reason ? `Reason: ${data.reason}\n\n` : ""}Reconnect from your dashboard to restore service.\n${copyright()}`,
428
+ html: wrapHtml("Channel Disconnected", parts.join("\n"), b),
429
+ 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)}`,
407
430
  };
408
431
  }
409
432
 
410
433
  function agentSuspendedTemplate(data: Record<string, unknown>): TemplateResult {
434
+ const b = brand(data);
411
435
  const agentName = escapeHtml((data.agentName as string) || "Your agent");
412
436
  const reason = escapeHtml((data.reason as string) || "");
437
+ const supportEmail = (data.supportEmail as string) || "support@wopr.bot";
413
438
  const parts = [
414
439
  heading(`${agentName} Has Been Paused`),
415
440
  paragraph(
@@ -417,41 +442,45 @@ function agentSuspendedTemplate(data: Record<string, unknown>): TemplateResult {
417
442
  (reason ? `<p><strong>Reason:</strong> ${reason}</p>` : ""),
418
443
  ),
419
444
  ];
420
- parts.push(footer("Contact support@wopr.bot if you have questions."));
445
+ parts.push(footer(`Contact ${escapeHtml(supportEmail)} if you have questions.`));
421
446
 
422
447
  return {
423
448
  subject: `${data.agentName} has been paused`,
424
- html: wrapHtml("Agent Paused", parts.join("\n")),
425
- text: `${data.agentName} Has Been Paused\n\n${reason ? `Reason: ${data.reason}\n` : ""}${copyright()}`,
449
+ html: wrapHtml("Agent Paused", parts.join("\n"), b),
450
+ text: `${data.agentName} Has Been Paused\n\n${reason ? `Reason: ${data.reason}\n` : ""}${copyright(b)}`,
426
451
  };
427
452
  }
428
453
 
429
454
  function accountDeletionRequestedTemplate(data: Record<string, unknown>): TemplateResult {
455
+ const b = brand(data);
430
456
  const email = escapeHtml((data.email as string) || "");
431
457
  const deleteAfterDate = escapeHtml((data.deleteAfterDate as string) || "");
432
458
  const cancelUrl = (data.cancelUrl as string) || "";
459
+ const supportEmail = (data.supportEmail as string) || "support@wopr.bot";
433
460
 
434
461
  const parts = [
435
462
  heading("Account Deletion Requested"),
436
463
  paragraph(
437
464
  `<p>Hi <strong>${email}</strong>,</p>` +
438
- `<p>We've received your request to delete your WOPR account and all associated data.</p>` +
465
+ `<p>We've received your request to delete your ${escapeHtml(b)} account and all associated data.</p>` +
439
466
  `<p>Your account will be permanently deleted on <strong>${deleteAfterDate}</strong>. Until then, you can cancel this request and keep your account.</p>` +
440
467
  `<p>After that date, all your data will be permanently and irreversibly removed, including bots, conversation history, credit records, and plugin configurations.</p>`,
441
468
  ),
442
469
  ];
443
470
  if (cancelUrl) parts.push(button(cancelUrl, "Cancel Deletion", "#22c55e"));
444
- parts.push(footer("If you did not request this, please contact support@wopr.bot immediately."));
471
+ parts.push(footer(`If you did not request this, please contact ${escapeHtml(supportEmail)} immediately.`));
445
472
 
446
473
  return {
447
- subject: "Your WOPR account deletion request",
448
- html: wrapHtml("Account Deletion Requested", parts.join("\n")),
449
- text: `Account Deletion Requested\n\nHi ${data.email as string},\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 as string}. 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()}`,
474
+ subject: `Your ${b} account deletion request`,
475
+ html: wrapHtml("Account Deletion Requested", parts.join("\n"), b),
476
+ text: `Account Deletion Requested\n\nHi ${data.email as string},\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 as string}. 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)}`,
450
477
  };
451
478
  }
452
479
 
453
480
  function accountDeletionCancelledTemplate(data: Record<string, unknown>): TemplateResult {
481
+ const b = brand(data);
454
482
  const email = escapeHtml((data.email as string) || "");
483
+ const supportEmail = (data.supportEmail as string) || "support@wopr.bot";
455
484
 
456
485
  const parts = [
457
486
  heading("Account Deletion Cancelled"),
@@ -460,38 +489,40 @@ function accountDeletionCancelledTemplate(data: Record<string, unknown>): Templa
460
489
  `<p>Your account deletion request has been cancelled. Your account and all data remain intact.</p>` +
461
490
  `<p>No further action is needed.</p>`,
462
491
  ),
463
- footer("If you didn't cancel this, please contact support@wopr.bot."),
492
+ footer(`If you didn't cancel this, please contact ${escapeHtml(supportEmail)}.`),
464
493
  ];
465
494
 
466
495
  return {
467
- subject: "Your WOPR account deletion has been cancelled",
468
- html: wrapHtml("Account Deletion Cancelled", parts.join("\n")),
469
- text: `Account Deletion Cancelled\n\nHi ${data.email as string},\n\nYour account deletion request has been cancelled. Your account and all data remain intact.\n\nNo further action is needed.${copyright()}`,
496
+ subject: `Your ${b} account deletion has been cancelled`,
497
+ html: wrapHtml("Account Deletion Cancelled", parts.join("\n"), b),
498
+ text: `Account Deletion Cancelled\n\nHi ${data.email as string},\n\nYour account deletion request has been cancelled. Your account and all data remain intact.\n\nNo further action is needed.${copyright(b)}`,
470
499
  };
471
500
  }
472
501
 
473
502
  function accountDeletionCompletedTemplate(data: Record<string, unknown>): TemplateResult {
503
+ const b = brand(data);
474
504
  const email = escapeHtml((data.email as string) || "");
475
505
 
476
506
  const parts = [
477
507
  heading("Your Account Has Been Deleted"),
478
508
  paragraph(
479
509
  `<p>Hi <strong>${email}</strong>,</p>` +
480
- `<p>Your WOPR account and all associated data have been permanently deleted as requested.</p>` +
510
+ `<p>Your ${escapeHtml(b)} account and all associated data have been permanently deleted as requested.</p>` +
481
511
  `<p>This includes all bots, conversation history, credit records, billing data, and plugin configurations.</p>` +
482
- `<p>If you'd like to use WOPR again in the future, you're welcome to create a new account.</p>`,
512
+ `<p>If you'd like to use ${escapeHtml(b)} again in the future, you're welcome to create a new account.</p>`,
483
513
  ),
484
- footer("Thank you for using WOPR. We're sorry to see you go."),
514
+ footer(`Thank you for using ${escapeHtml(b)}. We're sorry to see you go.`),
485
515
  ];
486
516
 
487
517
  return {
488
- subject: "Your WOPR account has been deleted",
489
- html: wrapHtml("Account Deleted", parts.join("\n")),
490
- text: `Your Account Has Been Deleted\n\nHi ${data.email as string},\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()}`,
518
+ subject: `Your ${b} account has been deleted`,
519
+ html: wrapHtml("Account Deleted", parts.join("\n"), b),
520
+ text: `Your Account Has Been Deleted\n\nHi ${data.email as string},\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)}`,
491
521
  };
492
522
  }
493
523
 
494
524
  function dividendWeeklyDigestTemplate(data: Record<string, unknown>): TemplateResult {
525
+ const b = brand(data);
495
526
  const weeklyTotal = escapeHtml((data.weeklyTotalDollars as string) || "$0.00");
496
527
  const lifetimeTotal = escapeHtml((data.lifetimeTotalDollars as string) || "$0.00");
497
528
  const distributionCount = Number(data.distributionCount) || 0;
@@ -505,7 +536,7 @@ function dividendWeeklyDigestTemplate(data: Record<string, unknown>): TemplateRe
505
536
  const poolAvgDollars = `$${(poolAvgCents / 100).toFixed(2)}`;
506
537
 
507
538
  const parts = [
508
- heading(`WOPR Paid You ${weeklyTotal} This Week`),
539
+ heading(`${escapeHtml(b)} Paid You ${weeklyTotal} This Week`),
509
540
  paragraph(
510
541
  `<p>Here's your weekly dividend summary for <strong>${weekStart} – ${weekEnd}</strong>.</p>` +
511
542
  `<table style="width: 100%; border-collapse: collapse; margin: 16px 0;">` +
@@ -533,10 +564,10 @@ function dividendWeeklyDigestTemplate(data: Record<string, unknown>): TemplateRe
533
564
  }
534
565
 
535
566
  return {
536
- subject: `WOPR paid you ${data.weeklyTotalDollars as string} this week`,
537
- html: wrapHtml("Weekly Dividend Digest", parts.join("\n")),
567
+ subject: `${b} paid you ${data.weeklyTotalDollars as string} this week`,
568
+ html: wrapHtml("Weekly Dividend Digest", parts.join("\n"), b),
538
569
  text:
539
- `WOPR Paid You ${data.weeklyTotalDollars as string} This Week\n\n` +
570
+ `${b} Paid You ${data.weeklyTotalDollars as string} This Week\n\n` +
540
571
  `Weekly summary for ${data.weekStartDate as string} – ${data.weekEndDate as string}:\n\n` +
541
572
  `This week's dividends: ${data.weeklyTotalDollars as string}\n` +
542
573
  `Days with distributions: ${distributionCount} of 7\n` +
@@ -546,22 +577,23 @@ function dividendWeeklyDigestTemplate(data: Record<string, unknown>): TemplateRe
546
577
  `Next dividend: ${data.nextDividendDate as string}\n\n` +
547
578
  `Community dividends are distributed daily from platform revenue.` +
548
579
  (unsubscribeUrl ? `\n\nUnsubscribe: ${unsubscribeUrl}` : "") +
549
- copyright(),
580
+ copyright(b),
550
581
  };
551
582
  }
552
583
 
553
584
  function customTemplate(data: Record<string, unknown>): TemplateResult {
554
- const subject = (data.subject as string) || "Message from WOPR";
585
+ const b = brand(data);
586
+ const subject = (data.subject as string) || `Message from ${b}`;
555
587
  const rawBody = (data.bodyText as string) || "";
556
588
  const escapedBody = escapeHtml(rawBody).replace(/\n/g, "<br>\n");
557
589
 
558
- const parts = [heading("Message from WOPR"), paragraph(`<p>${escapedBody}</p>`)];
559
- parts.push(footer("This is an administrative message from WOPR Network."));
590
+ const parts = [heading(`Message from ${escapeHtml(b)}`), paragraph(`<p>${escapedBody}</p>`)];
591
+ parts.push(footer(`This is an administrative message from ${escapeHtml(b)}.`));
560
592
 
561
593
  return {
562
594
  subject,
563
- html: wrapHtml(escapeHtml(subject), parts.join("\n")),
564
- text: `${rawBody}\n${copyright()}`,
595
+ html: wrapHtml(escapeHtml(subject), parts.join("\n"), b),
596
+ text: `${rawBody}\n${copyright(b)}`,
565
597
  };
566
598
  }
567
599
 
@@ -610,6 +642,7 @@ export function renderNotificationTemplate(template: TemplateName, data: Record<
610
642
  case "agent-suspended":
611
643
  return agentSuspendedTemplate(data);
612
644
  case "affiliate-credit-match": {
645
+ const b = brand(data);
613
646
  const amountDollars = escapeHtml((data.amountDollars as string) || "$0.00");
614
647
  const creditsUrl = (data.creditsUrl as string) || "";
615
648
  const parts = [
@@ -619,14 +652,15 @@ export function renderNotificationTemplate(template: TemplateName, data: Record<
619
652
  ),
620
653
  ];
621
654
  if (creditsUrl) parts.push(button(creditsUrl, "View Credit Balance"));
622
- parts.push(footer("Thank you for spreading the word about WOPR!"));
655
+ parts.push(footer(`Thank you for spreading the word about ${escapeHtml(b)}!`));
623
656
  return {
624
657
  subject: `You earned ${data.amountDollars as string} in affiliate credits!`,
625
- html: wrapHtml("Affiliate Credits Earned", parts.join("\n")),
626
- text: `You Earned Affiliate Credits!\n\nA user you referred just made their first purchase, and you've been credited ${data.amountDollars as string}.\n${creditsUrl ? `\nView your balance: ${creditsUrl}\n` : ""}${copyright()}`,
658
+ html: wrapHtml("Affiliate Credits Earned", parts.join("\n"), b),
659
+ text: `You Earned Affiliate Credits!\n\nA user you referred just made their first purchase, and you've been credited ${data.amountDollars as string}.\n${creditsUrl ? `\nView your balance: ${creditsUrl}\n` : ""}${copyright(b)}`,
627
660
  };
628
661
  }
629
662
  case "spend-alert": {
663
+ const b = brand(data);
630
664
  const currentSpend = escapeHtml(String(data.currentSpendDollars ?? "$0.00"));
631
665
  const alertAt = escapeHtml(String(data.alertAtDollars ?? "$0.00"));
632
666
  const creditsUrl = (data.creditsUrl as string) || "";
@@ -642,12 +676,12 @@ export function renderNotificationTemplate(template: TemplateName, data: Record<
642
676
  parts.push(footer("This alert fires once per day when your spend exceeds your configured threshold."));
643
677
  return {
644
678
  subject: `Spending alert: you've reached your ${data.alertAtDollars as string} threshold`,
645
- html: wrapHtml("Spending Alert", parts.join("\n")),
679
+ html: wrapHtml("Spending Alert", parts.join("\n"), b),
646
680
  text:
647
681
  `Spending Alert: Threshold Reached\n\nYour monthly spend has reached ${data.currentSpendDollars as string}, ` +
648
682
  `crossing your alert threshold of ${data.alertAtDollars as string}.\n` +
649
683
  (creditsUrl ? `\nReview spending: ${creditsUrl}\n` : "") +
650
- copyright(),
684
+ copyright(b),
651
685
  };
652
686
  }
653
687
  case "custom":
@@ -661,6 +695,7 @@ export function renderNotificationTemplate(template: TemplateName, data: Record<
661
695
  case "account-deletion-completed":
662
696
  return accountDeletionCompletedTemplate(data);
663
697
  case "fleet-update-available": {
698
+ const b = brand(data);
664
699
  const version = escapeHtml((data.version as string) || "");
665
700
  const changelogDate = escapeHtml((data.changelogDate as string) || "");
666
701
  const changelogSummary = escapeHtml((data.changelogSummary as string) || "");
@@ -679,11 +714,12 @@ export function renderNotificationTemplate(template: TemplateName, data: Record<
679
714
  faParts.push(footer("Review the changelog and update when ready."));
680
715
  return {
681
716
  subject: `Fleet update available: ${data.version}`,
682
- html: wrapHtml("Fleet Update Available", faParts.join("\n")),
683
- 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()}`,
717
+ html: wrapHtml("Fleet Update Available", faParts.join("\n"), b),
718
+ 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)}`,
684
719
  };
685
720
  }
686
721
  case "fleet-update-complete": {
722
+ const b = brand(data);
687
723
  const fcVersion = escapeHtml((data.version as string) || "");
688
724
  const succeeded = Number(data.succeeded) || 0;
689
725
  const failed = Number(data.failed) || 0;
@@ -714,27 +750,31 @@ export function renderNotificationTemplate(template: TemplateName, data: Record<
714
750
  );
715
751
  return {
716
752
  subject: `Fleet updated to ${data.version} \u2014 ${statusText}`,
717
- html: wrapHtml("Fleet Update Complete", fcParts.join("\n")),
718
- 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()}`,
753
+ html: wrapHtml("Fleet Update Complete", fcParts.join("\n"), b),
754
+ 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)}`,
719
755
  };
720
756
  }
721
- case "low-balance":
757
+ case "low-balance": {
758
+ const b = brand(data);
722
759
  return {
723
- subject: "Your WOPR credits are running low",
760
+ subject: `Your ${b} credits are running low`,
724
761
  html: wrapHtml(
725
762
  "Low Balance",
726
763
  [
727
- heading("Your WOPR Credits Are Running Low"),
764
+ heading(`Your ${escapeHtml(b)} Credits Are Running Low`),
728
765
  paragraph(
729
766
  `<p>Your balance is <strong>${escapeHtml((data.balanceDollars as string) || "$0.00")}</strong>. Top up to keep your agents running.</p>`,
730
767
  ),
731
768
  ...(data.creditsUrl ? [button(data.creditsUrl as string, "Buy Credits")] : []),
732
769
  footer("This is an automated billing notification."),
733
770
  ].join("\n"),
771
+ b,
734
772
  ),
735
- text: `Your WOPR Credits Are Running Low\n\nBalance: ${data.balanceDollars}\n${data.creditsUrl ? `\nBuy credits: ${data.creditsUrl}\n` : ""}${copyright()}`,
773
+ text: `Your ${b} Credits Are Running Low\n\nBalance: ${data.balanceDollars}\n${data.creditsUrl ? `\nBuy credits: ${data.creditsUrl}\n` : ""}${copyright(b)}`,
736
774
  };
737
- case "credit-purchase-receipt":
775
+ }
776
+ case "credit-purchase-receipt": {
777
+ const b = brand(data);
738
778
  return {
739
779
  subject: "Credits added to your account",
740
780
  html: wrapHtml(
@@ -747,27 +787,33 @@ export function renderNotificationTemplate(template: TemplateName, data: Record<
747
787
  ? `<p>New balance: <strong>${escapeHtml(data.newBalanceDollars as string)}</strong></p>`
748
788
  : ""),
749
789
  ),
750
- footer("Thank you for supporting WOPR!"),
790
+ footer(`Thank you for supporting ${escapeHtml(b)}!`),
751
791
  ].join("\n"),
792
+ b,
752
793
  ),
753
- text: `Credits Added\n\n${data.amountDollars} added.\n${copyright()}`,
794
+ text: `Credits Added\n\n${data.amountDollars} added.\n${copyright(b)}`,
754
795
  };
755
- case "welcome":
796
+ }
797
+ case "welcome": {
798
+ const b = brand(data);
756
799
  return {
757
- subject: "Welcome to WOPR",
800
+ subject: `Welcome to ${b}`,
758
801
  html: wrapHtml(
759
802
  "Welcome",
760
803
  [
761
- heading("Welcome to WOPR!"),
804
+ heading(`Welcome to ${escapeHtml(b)}!`),
762
805
  paragraph("<p>Your account is now active. Start building!</p>"),
763
806
  footer("Happy building!"),
764
807
  ].join("\n"),
808
+ b,
765
809
  ),
766
- text: `Welcome to WOPR!\n\nYour account is now active.\n${copyright()}`,
810
+ text: `Welcome to ${b}!\n\nYour account is now active.\n${copyright(b)}`,
767
811
  };
768
- case "password-reset":
812
+ }
813
+ case "password-reset": {
814
+ const b = brand(data);
769
815
  return {
770
- subject: "Reset your WOPR password",
816
+ subject: `Reset your ${b} password`,
771
817
  html: wrapHtml(
772
818
  "Reset Password",
773
819
  [
@@ -776,9 +822,11 @@ export function renderNotificationTemplate(template: TemplateName, data: Record<
776
822
  ...(data.resetUrl ? [button(data.resetUrl as string, "Reset Password")] : []),
777
823
  footer("If you did not request this, ignore this email."),
778
824
  ].join("\n"),
825
+ b,
779
826
  ),
780
- text: `Reset Your Password\n\n${data.resetUrl ? `${data.resetUrl}\n` : ""}${copyright()}`,
827
+ text: `Reset Your Password\n\n${data.resetUrl ? `${data.resetUrl}\n` : ""}${copyright(b)}`,
781
828
  };
829
+ }
782
830
  default: {
783
831
  // Exhaustiveness check
784
832
  const _exhaustive: never = template;