ts-mailkit 2.0.1 → 3.0.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.
Files changed (34) hide show
  1. package/README.md +114 -63
  2. package/dist/core/client.d.ts +35 -40
  3. package/dist/core/client.d.ts.map +1 -1
  4. package/dist/core/errors.d.ts +3 -0
  5. package/dist/core/errors.d.ts.map +1 -1
  6. package/dist/core/types.d.ts +126 -7
  7. package/dist/core/types.d.ts.map +1 -1
  8. package/dist/index.cjs +329 -68
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.ts +14 -13
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +327 -68
  13. package/dist/index.js.map +1 -1
  14. package/dist/renderers/default/default-renderer.d.ts +2 -2
  15. package/dist/renderers/renderer.interface.d.ts +1 -1
  16. package/dist/templates/factory.d.ts +1 -1
  17. package/dist/templates/factory.d.ts.map +1 -1
  18. package/dist/templates/template-builder.d.ts +36 -0
  19. package/dist/templates/template-builder.d.ts.map +1 -0
  20. package/dist/transports/console/console-sender.d.ts +7 -2
  21. package/dist/transports/console/console-sender.d.ts.map +1 -1
  22. package/dist/transports/cpaas/cpaas-sender.d.ts +51 -0
  23. package/dist/transports/cpaas/cpaas-sender.d.ts.map +1 -0
  24. package/dist/transports/cpaas/types.d.ts +133 -0
  25. package/dist/transports/cpaas/types.d.ts.map +1 -0
  26. package/dist/transports/memory/memory-sender.d.ts +26 -6
  27. package/dist/transports/memory/memory-sender.d.ts.map +1 -1
  28. package/dist/transports/sender.interface.d.ts +34 -3
  29. package/dist/transports/sender.interface.d.ts.map +1 -1
  30. package/package.json +39 -37
  31. package/dist/transports/zeptomail/types.d.ts +0 -47
  32. package/dist/transports/zeptomail/types.d.ts.map +0 -1
  33. package/dist/transports/zeptomail/zeptomail-sender.d.ts +0 -22
  34. package/dist/transports/zeptomail/zeptomail-sender.d.ts.map +0 -1
package/dist/index.cjs CHANGED
@@ -68,6 +68,8 @@ var TransportError = class extends MailKitError {
68
68
  transportName;
69
69
  statusCode;
70
70
  responseBody;
71
+ /** Parsed raw provider response, when available. */
72
+ rawResponse;
71
73
  cause;
72
74
  constructor(message, options) {
73
75
  super(message);
@@ -75,6 +77,7 @@ var TransportError = class extends MailKitError {
75
77
  this.transportName = options.transportName;
76
78
  this.statusCode = options.statusCode;
77
79
  this.responseBody = options.responseBody;
80
+ this.rawResponse = options.rawResponse;
78
81
  this.cause = options.cause;
79
82
  Object.setPrototypeOf(this, new.target.prototype);
80
83
  }
@@ -221,18 +224,16 @@ var MailKit = class {
221
224
  *
222
225
  * Example:
223
226
  * ```ts
224
- * await mailer.send({
227
+ * await mailer.sendMail({
225
228
  * to: 'user@example.com',
226
229
  * template: myDto,
227
230
  * });
228
231
  * ```
229
232
  */
230
- async send(options) {
233
+ async sendMail(options) {
231
234
  const template = options.template;
232
235
  const recipientName = options.toName ?? template.userName;
233
- let resolvedFromName = options.fromName ?? template.fromName;
234
- if (!resolvedFromName && this.brandNameResolver) resolvedFromName = await this.brandNameResolver();
235
- if (!resolvedFromName) resolvedFromName = this.defaultBrandName;
236
+ const resolvedFromName = await this.resolveFromName(template, options.fromName);
236
237
  const rendered = await this.renderer.render(template);
237
238
  const plaintextBody = template.noHtmlMessage ?? rendered.text;
238
239
  let toRecipients;
@@ -241,11 +242,7 @@ var MailKit = class {
241
242
  name: recipientName
242
243
  };
243
244
  else toRecipients = options.to;
244
- let from = options.from ?? this.defaultFrom;
245
- if (resolvedFromName && from) from = {
246
- address: normalizeRecipient(from).address,
247
- name: resolvedFromName
248
- };
245
+ const from = this.applyFromName(options.from ?? this.defaultFrom, resolvedFromName);
249
246
  return await this.sendRaw({
250
247
  to: toRecipients,
251
248
  from,
@@ -262,6 +259,84 @@ var MailKit = class {
262
259
  });
263
260
  }
264
261
  /**
262
+ * Sends a bulk email using a strongly-typed template DTO.
263
+ *
264
+ * The transport delivers an individual copy to each recipient, so
265
+ * recipients are not exposed to one another. Per-recipient personalization
266
+ * is available via {@link sendBulkKnownTemplate}.
267
+ *
268
+ * Example:
269
+ * ```ts
270
+ * await mailer.sendBulkMail({
271
+ * to: ['a@example.com', 'b@example.com'],
272
+ * template: myDto,
273
+ * });
274
+ * ```
275
+ */
276
+ async sendBulkMail(options) {
277
+ const transport = this.transport;
278
+ if (typeof transport.sendBulk !== "function") throw new ConfigurationError("MailKit: The configured transport does not support bulk sending (sendBulk).");
279
+ const template = options.template;
280
+ const resolvedFromName = await this.resolveFromName(template);
281
+ const rendered = await this.renderer.render(template);
282
+ const plaintextBody = template.noHtmlMessage ?? rendered.text;
283
+ const from = this.applyFromName(options.from ?? this.defaultFrom, resolvedFromName);
284
+ return await transport.sendBulk({
285
+ to: options.to,
286
+ from,
287
+ subject: template.subject,
288
+ html: rendered.html,
289
+ text: plaintextBody,
290
+ cc: options.cc,
291
+ bcc: options.bcc,
292
+ replyTo: options.replyTo,
293
+ attachments: options.attachments,
294
+ headers: options.headers,
295
+ tags: options.tags,
296
+ metadata: options.metadata,
297
+ trackClicks: options.trackClicks,
298
+ trackOpens: options.trackOpens,
299
+ bounceAddress: options.bounceAddress,
300
+ clientReference: options.clientReference
301
+ });
302
+ }
303
+ /**
304
+ * Sends a single email using a provider-side template.
305
+ */
306
+ async sendKnownTemplate(options) {
307
+ const transport = this.transport;
308
+ if (typeof transport.sendKnownTemplate !== "function") throw new ConfigurationError("MailKit: The configured transport does not support template sending (sendKnownTemplate).");
309
+ return await transport.sendKnownTemplate({
310
+ ...options,
311
+ from: options.from ?? this.defaultFrom
312
+ });
313
+ }
314
+ /**
315
+ * Sends a bulk email using a provider-side template with optional
316
+ * per-recipient merge data.
317
+ */
318
+ async sendBulkKnownTemplate(options) {
319
+ const transport = this.transport;
320
+ if (typeof transport.sendBulkKnownTemplate !== "function") throw new ConfigurationError("MailKit: The configured transport does not support bulk template sending (sendBulkKnownTemplate).");
321
+ return await transport.sendBulkKnownTemplate({
322
+ ...options,
323
+ from: options.from ?? this.defaultFrom
324
+ });
325
+ }
326
+ async resolveFromName(template, explicitFromName) {
327
+ let resolved = explicitFromName ?? template.fromName;
328
+ if (!resolved && this.brandNameResolver) resolved = await this.brandNameResolver();
329
+ if (!resolved) resolved = this.defaultBrandName;
330
+ return resolved;
331
+ }
332
+ applyFromName(from, fromName) {
333
+ if (fromName && from) return {
334
+ address: normalizeRecipient(from).address,
335
+ name: fromName
336
+ };
337
+ return from;
338
+ }
339
+ /**
265
340
  * Dispatches a raw, pre-rendered email directly through the transport.
266
341
  * If `options.text` is omitted, plain text is automatically derived from `options.html`.
267
342
  */
@@ -283,38 +358,73 @@ function createMailKit(options) {
283
358
  }
284
359
 
285
360
  //#endregion
286
- //#region src/transports/zeptomail/zeptomail-sender.ts
361
+ //#region src/transports/cpaas/cpaas-sender.ts
287
362
  /**
288
- * ZeptoMail (Zoho Mail transactional email HTTP API) transport adapter.
363
+ * Zoho CPaaS transactional email HTTP transport adapter.
289
364
  *
290
- * Designed for zero dependencies and universal edge compatibility (Node.js, Bun, Deno, Cloudflare Workers)
291
- * by utilizing the standard global `fetch` API.
365
+ * Uses the standard global `fetch` API for zero-dependency, universal edge
366
+ * compatibility (Node.js, Bun, Deno, Cloudflare Workers).
367
+ *
368
+ * Supports:
369
+ * - `send` — single raw email (`POST /v1.1/email`)
370
+ * - `sendBulk` — batch raw email (`POST /v1.1/email/batch`)
371
+ * - `sendKnownTemplate` — single template email (`POST /v1.1/email/template`)
372
+ * - `sendBulkKnownTemplate` — batch template email (`POST /v1.1/email/template/batch`)
292
373
  */
293
- var ZeptoMailHttpSender = class {
294
- name = "zeptomail";
295
- apiUrl;
374
+ var CpaasHttpSender = class {
375
+ name = "cpaas";
376
+ apiBase;
296
377
  authHeader;
297
378
  defaultFrom;
379
+ preferTemplateAlias;
298
380
  fetchImpl;
299
381
  constructor(config) {
300
- if (!config.apiToken || typeof config.apiToken !== "string") throw new ConfigurationError("ZeptoMailHttpSender: Missing or invalid apiToken.");
301
- if (!config.from) throw new ConfigurationError("ZeptoMailHttpSender: Missing required default from address.");
302
- const host = (config.apiHost || "api.zeptomail.com").replace(/^https?:\/\//, "").replace(/\/+$/, "");
303
- this.apiUrl = `https://${host}/v1.1/email`;
382
+ if (!config.apiToken || typeof config.apiToken !== "string") throw new ConfigurationError("CpaasHttpSender: Missing or invalid apiToken.");
383
+ if (!config.from) throw new ConfigurationError("CpaasHttpSender: Missing required default from address.");
384
+ const host = (config.apiHost || "cpaas.zoho.com").replace(/^https?:\/\//, "").replace(/\/+$/, "");
385
+ this.apiBase = `https://${host}/v1.1`;
304
386
  const token = config.apiToken.trim();
305
387
  this.authHeader = token.startsWith("Zoho-") ? token : `Zoho-enczapikey ${token}`;
306
388
  this.defaultFrom = normalizeRecipient(config.from);
307
- if (!this.defaultFrom.address) throw new ConfigurationError("ZeptoMailHttpSender: Configured from address must not be empty.");
389
+ if (!this.defaultFrom.address) throw new ConfigurationError("CpaasHttpSender: Configured from address must not be empty.");
390
+ this.preferTemplateAlias = config.preferTemplateAlias ?? false;
308
391
  const fetchImpl = config.fetch ?? globalThis.fetch;
309
- if (typeof fetchImpl !== "function") throw new ConfigurationError("ZeptoMailHttpSender: No global fetch implementation found. Pass a custom fetch to ZeptoMailHttpConfig if running on Node < 18.");
392
+ if (typeof fetchImpl !== "function") throw new ConfigurationError("CpaasHttpSender: No global fetch implementation found. Pass a custom fetch to CpaasHttpConfig if running on Node < 18.");
310
393
  this.fetchImpl = fetchImpl.bind(globalThis);
311
394
  }
312
395
  /**
313
- * Dispatches an email using Zoho ZeptoMail API v1.1.
396
+ * Dispatches a single raw email using Zoho CPaaS API.
314
397
  */
315
398
  async send(options) {
399
+ const payload = this.buildSendPayload(options);
400
+ return await this.post(`${this.apiBase}/email`, payload);
401
+ }
402
+ /**
403
+ * Dispatches a batch of a single raw email content to many recipients.
404
+ * The provider delivers an individual copy to each recipient.
405
+ */
406
+ async sendBulk(options) {
407
+ const payload = this.buildSendPayload(options);
408
+ return await this.post(`${this.apiBase}/email/batch`, payload);
409
+ }
410
+ /**
411
+ * Dispatches a single email using a provider-side template.
412
+ */
413
+ async sendKnownTemplate(options) {
414
+ const payload = this.buildTemplatePayload(options);
415
+ return await this.post(`${this.apiBase}/email/template`, payload);
416
+ }
417
+ /**
418
+ * Dispatches a batch of emails using a provider-side template, with
419
+ * optional per-recipient merge data.
420
+ */
421
+ async sendBulkKnownTemplate(options) {
422
+ const payload = this.buildBatchTemplatePayload(options);
423
+ return await this.post(`${this.apiBase}/email/template/batch`, payload);
424
+ }
425
+ buildSendPayload(options) {
316
426
  const toRecipients = normalizeRecipients(options.to);
317
- if (toRecipients.length === 0) throw new TransportError("ZeptoMailHttpSender: At least one recipient (to) must be specified.", { transportName: this.name });
427
+ if (toRecipients.length === 0) throw new TransportError("CpaasHttpSender: At least one recipient (to) must be specified.", { transportName: this.name });
318
428
  const resolvedFrom = options.from ? normalizeRecipient(options.from) : this.defaultFrom;
319
429
  const payload = {
320
430
  from: {
@@ -329,6 +439,52 @@ var ZeptoMailHttpSender = class {
329
439
  htmlbody: options.html,
330
440
  textbody: options.text
331
441
  };
442
+ this.applyCommon(payload, options);
443
+ return payload;
444
+ }
445
+ buildTemplatePayload(options) {
446
+ const toRecipients = normalizeRecipients(options.to);
447
+ if (toRecipients.length === 0) throw new TransportError("CpaasHttpSender: At least one recipient (to) must be specified.", { transportName: this.name });
448
+ const resolvedFrom = options.from ? normalizeRecipient(options.from) : this.defaultFrom;
449
+ const payload = {
450
+ ...this.templateRef(options.templateId),
451
+ from: {
452
+ address: resolvedFrom.address,
453
+ name: resolvedFrom.name
454
+ },
455
+ to: toRecipients.map((r) => ({ email_address: {
456
+ address: r.address,
457
+ name: r.name || ""
458
+ } }))
459
+ };
460
+ if (options.mergeInfo) payload.merge_info = options.mergeInfo;
461
+ this.applyCommon(payload, options);
462
+ return payload;
463
+ }
464
+ buildBatchTemplatePayload(options) {
465
+ if (!options.to || options.to.length === 0) throw new TransportError("CpaasHttpSender: At least one recipient (to) must be specified.", { transportName: this.name });
466
+ const resolvedFrom = options.from ? normalizeRecipient(options.from) : this.defaultFrom;
467
+ const payload = {
468
+ ...this.templateRef(options.templateId),
469
+ from: {
470
+ address: resolvedFrom.address,
471
+ name: resolvedFrom.name
472
+ },
473
+ to: options.to.map((r) => ({
474
+ email_address: {
475
+ address: r.address,
476
+ name: r.name || ""
477
+ },
478
+ ...r.mergeInfo ? { merge_info: r.mergeInfo } : {}
479
+ }))
480
+ };
481
+ this.applyCommon(payload, options);
482
+ return payload;
483
+ }
484
+ templateRef(templateId) {
485
+ return this.preferTemplateAlias ? { template_alias: templateId } : { template_key: templateId };
486
+ }
487
+ applyCommon(payload, options) {
332
488
  if (options.cc) {
333
489
  const ccList = normalizeRecipients(options.cc);
334
490
  if (ccList.length > 0) payload.cc = ccList.map((r) => ({ email_address: {
@@ -350,26 +506,33 @@ var ZeptoMailHttpSender = class {
350
506
  name: r.name
351
507
  }));
352
508
  }
353
- if (options.attachments && options.attachments.length > 0) payload.attachments = options.attachments.map((att) => {
354
- let contentBase64;
355
- if (typeof att.content === "string") contentBase64 = att.content;
356
- else if (typeof Buffer !== "undefined") contentBase64 = Buffer.from(att.content).toString("base64");
357
- else {
358
- let binary = "";
359
- const bytes = new Uint8Array(att.content);
360
- for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
361
- contentBase64 = btoa(binary);
362
- }
363
- return {
364
- name: att.filename,
365
- content: contentBase64,
366
- mime_type: att.mimeType || att.contentType || "application/octet-stream",
367
- cid: att.cid
368
- };
369
- });
509
+ if (options.attachments && options.attachments.length > 0) payload.attachments = options.attachments.map((att) => this.toAttachmentPayload(att));
510
+ if (options.trackClicks !== void 0) payload.track_clicks = options.trackClicks;
511
+ if (options.trackOpens !== void 0) payload.track_opens = options.trackOpens;
512
+ if (options.bounceAddress !== void 0) payload.bounce_address = options.bounceAddress;
513
+ if (options.clientReference !== void 0) payload.client_reference = options.clientReference;
514
+ }
515
+ toAttachmentPayload(att) {
516
+ let contentBase64;
517
+ if (typeof att.content === "string") contentBase64 = att.content;
518
+ else if (typeof Buffer !== "undefined") contentBase64 = Buffer.from(att.content).toString("base64");
519
+ else {
520
+ let binary = "";
521
+ const bytes = new Uint8Array(att.content);
522
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
523
+ contentBase64 = btoa(binary);
524
+ }
525
+ return {
526
+ name: att.filename,
527
+ content: contentBase64,
528
+ mime_type: att.mimeType || att.contentType || "application/octet-stream",
529
+ cid: att.cid
530
+ };
531
+ }
532
+ async post(url, payload) {
370
533
  let response;
371
534
  try {
372
- response = await this.fetchImpl(this.apiUrl, {
535
+ response = await this.fetchImpl(url, {
373
536
  method: "POST",
374
537
  headers: {
375
538
  Accept: "application/json",
@@ -379,7 +542,7 @@ var ZeptoMailHttpSender = class {
379
542
  body: JSON.stringify(payload)
380
543
  });
381
544
  } catch (err) {
382
- throw new TransportError(`ZeptoMailHttpSender: Network request failed: ${err instanceof Error ? err.message : String(err)}`, {
545
+ throw new TransportError(`CpaasHttpSender: Network request failed: ${err instanceof Error ? err.message : String(err)}`, {
383
546
  transportName: this.name,
384
547
  cause: err
385
548
  });
@@ -392,17 +555,18 @@ var ZeptoMailHttpSender = class {
392
555
  responseData = { raw: responseText };
393
556
  }
394
557
  if (!response.ok) {
395
- const errorMessage = responseData?.error?.message || responseData?.message || `HTTP ${response.status}: ${responseText || response.statusText}`;
396
- throw new TransportError(`ZeptoMailHttpSender: Failed to send email - ${errorMessage}`, {
558
+ const errorMessage = responseData?.data?.message || responseData?.error?.message || responseData?.message || `HTTP ${response.status}: ${responseText || response.statusText}`;
559
+ throw new TransportError(`CpaasHttpSender: Failed to send email - ${errorMessage}`, {
397
560
  transportName: this.name,
398
561
  statusCode: response.status,
399
- responseBody: responseText
562
+ responseBody: responseText,
563
+ rawResponse: responseData
400
564
  });
401
565
  }
402
566
  return {
403
567
  success: true,
404
568
  messageId: responseData?.data?.[0]?.message_id,
405
- response: responseData
569
+ rawResponse: responseData
406
570
  };
407
571
  }
408
572
  };
@@ -416,36 +580,76 @@ var ZeptoMailHttpSender = class {
416
580
  var MemoryEmailSender = class {
417
581
  name = "memory";
418
582
  sentMails = [];
583
+ sentBulkMails = [];
584
+ sentTemplateMails = [];
585
+ sentBulkTemplateMails = [];
419
586
  nextId = 1;
420
- async send(options) {
421
- this.sentMails.push({ ...options });
422
- const messageId = `mem-${this.nextId++}-${Date.now()}`;
587
+ result(prefix) {
588
+ const id = this.nextId++;
589
+ const messageId = prefix ? `mem-${prefix}-${id}-${Date.now()}` : `mem-${id}-${Date.now()}`;
423
590
  return {
424
591
  success: true,
425
592
  messageId,
426
- response: { id: messageId }
593
+ rawResponse: { id: messageId }
427
594
  };
428
595
  }
596
+ async send(options) {
597
+ this.sentMails.push({ ...options });
598
+ return this.result();
599
+ }
600
+ async sendBulk(options) {
601
+ this.sentBulkMails.push({ ...options });
602
+ return this.result("bulk");
603
+ }
604
+ async sendKnownTemplate(options) {
605
+ this.sentTemplateMails.push({ ...options });
606
+ return this.result("tpl");
607
+ }
608
+ async sendBulkKnownTemplate(options) {
609
+ this.sentBulkTemplateMails.push({ ...options });
610
+ return this.result("bulk-tpl");
611
+ }
429
612
  /**
430
- * Returns a copy of all emails sent through this transport instance.
613
+ * Returns a copy of all single emails sent through this transport instance.
431
614
  */
432
615
  getSentMails() {
433
616
  return [...this.sentMails];
434
617
  }
435
618
  /**
436
- * Returns the most recent email sent, or undefined if no emails have been sent.
619
+ * Returns the most recent single email sent, or undefined if none have been sent.
437
620
  */
438
621
  getLastMail() {
439
622
  return this.sentMails.length > 0 ? { ...this.sentMails[this.sentMails.length - 1] } : void 0;
440
623
  }
441
624
  /**
442
- * Clears all recorded sent emails.
625
+ * Returns a copy of all bulk emails sent through this transport instance.
626
+ */
627
+ getSentBulkMails() {
628
+ return [...this.sentBulkMails];
629
+ }
630
+ /**
631
+ * Returns a copy of all single template emails sent through this transport instance.
632
+ */
633
+ getSentTemplateMails() {
634
+ return [...this.sentTemplateMails];
635
+ }
636
+ /**
637
+ * Returns a copy of all bulk template emails sent through this transport instance.
638
+ */
639
+ getSentBulkTemplateMails() {
640
+ return [...this.sentBulkTemplateMails];
641
+ }
642
+ /**
643
+ * Clears all recorded emails.
443
644
  */
444
645
  clear() {
445
646
  this.sentMails = [];
647
+ this.sentBulkMails = [];
648
+ this.sentTemplateMails = [];
649
+ this.sentBulkTemplateMails = [];
446
650
  }
447
651
  /**
448
- * Returns the total count of sent emails.
652
+ * Returns the total count of single emails sent.
449
653
  */
450
654
  count() {
451
655
  return this.sentMails.length;
@@ -468,33 +672,70 @@ var ConsoleEmailSender = class {
468
672
  this.includeHtml = options.includeHtml ?? false;
469
673
  }
470
674
  async send(options) {
471
- const to = normalizeRecipients(options.to).map((r) => r.name ? `${r.name} <${r.address}>` : r.address).join(", ");
472
- const from = options.from ? JSON.stringify(options.from) : "(default)";
675
+ return this.logDispatch({
676
+ kind: "EMAIL",
677
+ to: options.to,
678
+ from: options.from,
679
+ subject: options.subject,
680
+ text: options.text,
681
+ html: options.html
682
+ });
683
+ }
684
+ async sendBulk(options) {
685
+ return this.logDispatch({
686
+ kind: "BULK EMAIL",
687
+ to: options.to,
688
+ from: options.from,
689
+ subject: options.subject,
690
+ text: options.text,
691
+ html: options.html
692
+ });
693
+ }
694
+ async sendKnownTemplate(options) {
695
+ return this.logDispatch({
696
+ kind: "TEMPLATE EMAIL",
697
+ to: options.to,
698
+ from: options.from,
699
+ templateId: options.templateId
700
+ });
701
+ }
702
+ async sendBulkKnownTemplate(options) {
703
+ return this.logDispatch({
704
+ kind: "BULK TEMPLATE EMAIL",
705
+ to: options.to,
706
+ from: options.from,
707
+ templateId: options.templateId
708
+ });
709
+ }
710
+ logDispatch(entry) {
711
+ const to = normalizeRecipients(entry.to).map((r) => r.name ? `${r.name} <${r.address}>` : r.address).join(", ");
712
+ const from = entry.from ? JSON.stringify(entry.from) : "(default)";
473
713
  const messageId = `console-${this.nextId++}-${Date.now()}`;
474
714
  const separator = "─".repeat(60);
475
715
  const lines = [
476
716
  `\n┌${separator}┐`,
477
- `│ [ts-mailkit:console] DISPATCHED EMAIL (${messageId})`,
717
+ `│ [ts-mailkit:console] DISPATCHED ${entry.kind} (${messageId})`,
478
718
  `├${separator}┤`,
479
719
  `│ From: ${from}`,
480
- `│ To: ${to}`,
481
- `│ Subject: ${options.subject}`,
482
- `├${separator}┤`
720
+ `│ To: ${to}`
483
721
  ];
484
- if (options.text) {
722
+ if (entry.subject !== void 0) lines.push(`│ Subject: ${entry.subject}`);
723
+ if (entry.templateId !== void 0) lines.push(`│ Template: ${entry.templateId}`);
724
+ lines.push(`├${separator}┤`);
725
+ if (entry.text) {
485
726
  lines.push("│ Text Body:");
486
- for (const line of options.text.split("\n")) lines.push(`│ ${line}`);
727
+ for (const line of entry.text.split("\n")) lines.push(`│ ${line}`);
487
728
  }
488
- if (this.includeHtml && options.html) {
729
+ if (this.includeHtml && entry.html) {
489
730
  lines.push("│ HTML Body:");
490
- for (const line of options.html.split("\n")) lines.push(`│ ${line}`);
731
+ for (const line of entry.html.split("\n")) lines.push(`│ ${line}`);
491
732
  }
492
733
  lines.push(`└${separator}┘\n`);
493
734
  this.log(lines.join("\n"));
494
735
  return {
495
736
  success: true,
496
737
  messageId,
497
- response: { logged: true }
738
+ rawResponse: { logged: true }
498
739
  };
499
740
  }
500
741
  };
@@ -680,9 +921,28 @@ var EmailTemplateFactory = class EmailTemplateFactory {
680
921
  }
681
922
  };
682
923
 
924
+ //#endregion
925
+ //#region src/templates/template-builder.ts
926
+ /**
927
+ * Builds an upload-ready HTML template from the canonical {@link EmailTemplateDto}.
928
+ *
929
+ * The output can be uploaded to a messaging platform (Zoho CPaaS, SendGrid, etc.)
930
+ * as a provider-side template.
931
+ */
932
+ function buildHTMLTemplate(dto, options = {}) {
933
+ return buildHtml(dto, options);
934
+ }
935
+ /**
936
+ * Builds an upload-ready plain-text template from the canonical {@link EmailTemplateDto}.
937
+ */
938
+ function buildPlainTextTemplate(dto, _options) {
939
+ return buildPlainText(dto);
940
+ }
941
+
683
942
  //#endregion
684
943
  exports.ConfigurationError = ConfigurationError;
685
944
  exports.ConsoleEmailSender = ConsoleEmailSender;
945
+ exports.CpaasHttpSender = CpaasHttpSender;
686
946
  exports.DefaultEmailRenderer = DefaultEmailRenderer;
687
947
  exports.EmailTemplateFactory = EmailTemplateFactory;
688
948
  exports.MailKit = MailKit;
@@ -690,9 +950,10 @@ exports.MailKitError = MailKitError;
690
950
  exports.MemoryEmailSender = MemoryEmailSender;
691
951
  exports.TemplateError = TemplateError;
692
952
  exports.TransportError = TransportError;
693
- exports.ZeptoMailHttpSender = ZeptoMailHttpSender;
953
+ exports.buildHTMLTemplate = buildHTMLTemplate;
694
954
  exports.buildHtml = buildHtml;
695
955
  exports.buildPlainText = buildPlainText;
956
+ exports.buildPlainTextTemplate = buildPlainTextTemplate;
696
957
  exports.createMailKit = createMailKit;
697
958
  exports.decodeHtmlEntities = decodeHtmlEntities;
698
959
  exports.htmlToPlainText = htmlToPlainText;