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