@shogo-ai/email 1.9.11 → 1.9.13

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.
@@ -24,10 +24,15 @@ var EmailError = class _EmailError extends Error {
24
24
  };
25
25
 
26
26
  // src/templates.ts
27
- function interpolate(template, data) {
27
+ function escapeHtml(value) {
28
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
29
+ }
30
+ function interpolate(template, data, options = {}) {
28
31
  return template.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (match, path) => {
29
32
  const value = getNestedValue(data, path);
30
- return value !== void 0 ? String(value) : match;
33
+ if (value === void 0) return match;
34
+ const str = String(value);
35
+ return options.escapeValues ? escapeHtml(str) : str;
31
36
  });
32
37
  }
33
38
  function getNestedValue(obj, path) {
@@ -80,8 +85,8 @@ var EmailTemplateRegistry = class {
80
85
  currentYear: (/* @__PURE__ */ new Date()).getFullYear()
81
86
  };
82
87
  const subject = interpolate(template.subject, allData);
83
- const html = interpolate(template.html, allData);
84
- const text = template.text ? interpolate(template.text, allData) : htmlToText(html);
88
+ const html = interpolate(template.html, allData, { escapeValues: true });
89
+ const text = template.text ? interpolate(template.text, allData) : htmlToText(interpolate(template.html, allData));
85
90
  return { subject, html, text };
86
91
  }
87
92
  /**
@@ -771,6 +776,72 @@ var paymentFailedTemplate = {
771
776
  }
772
777
  };
773
778
 
779
+ // src/templates/billing/usage-overage-charged.ts
780
+ var usageOverageChargedTemplate = {
781
+ name: "usage-overage-charged",
782
+ subject: "Usage charge of {{currency}}{{amount}} for {{workspaceName}}",
783
+ html: wrapInLayout(`
784
+ <h1 class="email-h1">On-demand usage charged</h1>
785
+ <p class="email-text">
786
+ <strong>{{workspaceName}}</strong> has used more than its included monthly
787
+ usage, so we charged an on-demand usage block. We bill these in blocks as
788
+ you go rather than one large amount at the end of the period.
789
+ </p>
790
+ <table width="100%" cellpadding="0" cellspacing="0">
791
+ <tr>
792
+ <td class="email-detail-label">Charged now</td>
793
+ <td class="email-detail-value" align="right">{{currency}}{{amount}}</td>
794
+ </tr>
795
+ <tr>
796
+ <td class="email-detail-label">Overage this period</td>
797
+ <td class="email-detail-value" align="right">{{currency}}{{periodOverageUsd}}</td>
798
+ </tr>
799
+ </table>
800
+ <br>
801
+ <a href="{{invoiceUrl}}" class="email-btn-outline">View invoice</a>
802
+ <hr class="email-divider">
803
+ <p class="email-text">
804
+ Want to cap on-demand spend? You can set a monthly spending limit any time.
805
+ </p>
806
+ <a href="{{manageUrl}}" class="email-btn-outline">Set a spending limit</a>
807
+ <p class="email-muted">
808
+ If you have billing questions, reply to this email or contact support.
809
+ </p>
810
+ `),
811
+ defaults: {
812
+ appName: EMAIL_CONSTANTS.APP_NAME,
813
+ currency: "$"
814
+ }
815
+ };
816
+
817
+ // src/templates/billing/usage-approaching-limit.ts
818
+ var usageApproachingLimitTemplate = {
819
+ name: "usage-approaching-limit",
820
+ subject: "Usage alert: {{workspaceName}} is at {{percentUsed}}% of its {{limitLabel}}",
821
+ html: wrapInLayout(`
822
+ <h1 class="email-h1">Approaching your {{limitLabel}}</h1>
823
+ <p class="email-text">
824
+ <strong>{{workspaceName}}</strong> has used
825
+ <strong>{{currency}}{{usedUsd}}</strong> of its
826
+ <strong>{{currency}}{{limitUsd}}</strong> {{limitLabel}} this period
827
+ (<strong>{{percentUsed}}%</strong>).
828
+ </p>
829
+ <p class="email-text">
830
+ Once the included amount is used up, additional usage is billed on demand.
831
+ You can review usage or set a monthly spending limit below.
832
+ </p>
833
+ <a href="{{manageUrl}}" class="email-btn">Review usage &amp; limits</a>
834
+ <hr class="email-divider">
835
+ <p class="email-muted">
836
+ You're receiving this because you're a billing admin for this workspace.
837
+ </p>
838
+ `),
839
+ defaults: {
840
+ appName: EMAIL_CONSTANTS.APP_NAME,
841
+ currency: "$"
842
+ }
843
+ };
844
+
774
845
  // src/templates/workspace/member-joined.ts
775
846
  var memberJoinedTemplate = {
776
847
  name: "member-joined",
@@ -849,6 +920,8 @@ var allTemplates = [
849
920
  planUpgradedTemplate,
850
921
  paymentReceiptTemplate,
851
922
  paymentFailedTemplate,
923
+ usageOverageChargedTemplate,
924
+ usageApproachingLimitTemplate,
852
925
  memberJoinedTemplate,
853
926
  memberRemovedTemplate,
854
927
  accountDeletedTemplate
@@ -879,4 +952,4 @@ export {
879
952
  allTemplates,
880
953
  createShogoTemplateRegistry
881
954
  };
882
- //# sourceMappingURL=chunk-VPWVZHAC.js.map
955
+ //# sourceMappingURL=chunk-AHEPUREO.js.map
package/dist/index.cjs CHANGED
@@ -65,10 +65,15 @@ var EmailError = class _EmailError extends Error {
65
65
  };
66
66
 
67
67
  // src/templates.ts
68
- function interpolate(template, data) {
68
+ function escapeHtml(value) {
69
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
70
+ }
71
+ function interpolate(template, data, options = {}) {
69
72
  return template.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (match, path) => {
70
73
  const value = getNestedValue(data, path);
71
- return value !== void 0 ? String(value) : match;
74
+ if (value === void 0) return match;
75
+ const str = String(value);
76
+ return options.escapeValues ? escapeHtml(str) : str;
72
77
  });
73
78
  }
74
79
  function getNestedValue(obj, path) {
@@ -121,8 +126,8 @@ var EmailTemplateRegistry = class {
121
126
  currentYear: (/* @__PURE__ */ new Date()).getFullYear()
122
127
  };
123
128
  const subject = interpolate(template.subject, allData);
124
- const html = interpolate(template.html, allData);
125
- const text = template.text ? interpolate(template.text, allData) : htmlToText(html);
129
+ const html = interpolate(template.html, allData, { escapeValues: true });
130
+ const text = template.text ? interpolate(template.text, allData) : htmlToText(interpolate(template.html, allData));
126
131
  return { subject, html, text };
127
132
  }
128
133
  /**
@@ -812,6 +817,72 @@ var paymentFailedTemplate = {
812
817
  }
813
818
  };
814
819
 
820
+ // src/templates/billing/usage-overage-charged.ts
821
+ var usageOverageChargedTemplate = {
822
+ name: "usage-overage-charged",
823
+ subject: "Usage charge of {{currency}}{{amount}} for {{workspaceName}}",
824
+ html: wrapInLayout(`
825
+ <h1 class="email-h1">On-demand usage charged</h1>
826
+ <p class="email-text">
827
+ <strong>{{workspaceName}}</strong> has used more than its included monthly
828
+ usage, so we charged an on-demand usage block. We bill these in blocks as
829
+ you go rather than one large amount at the end of the period.
830
+ </p>
831
+ <table width="100%" cellpadding="0" cellspacing="0">
832
+ <tr>
833
+ <td class="email-detail-label">Charged now</td>
834
+ <td class="email-detail-value" align="right">{{currency}}{{amount}}</td>
835
+ </tr>
836
+ <tr>
837
+ <td class="email-detail-label">Overage this period</td>
838
+ <td class="email-detail-value" align="right">{{currency}}{{periodOverageUsd}}</td>
839
+ </tr>
840
+ </table>
841
+ <br>
842
+ <a href="{{invoiceUrl}}" class="email-btn-outline">View invoice</a>
843
+ <hr class="email-divider">
844
+ <p class="email-text">
845
+ Want to cap on-demand spend? You can set a monthly spending limit any time.
846
+ </p>
847
+ <a href="{{manageUrl}}" class="email-btn-outline">Set a spending limit</a>
848
+ <p class="email-muted">
849
+ If you have billing questions, reply to this email or contact support.
850
+ </p>
851
+ `),
852
+ defaults: {
853
+ appName: EMAIL_CONSTANTS.APP_NAME,
854
+ currency: "$"
855
+ }
856
+ };
857
+
858
+ // src/templates/billing/usage-approaching-limit.ts
859
+ var usageApproachingLimitTemplate = {
860
+ name: "usage-approaching-limit",
861
+ subject: "Usage alert: {{workspaceName}} is at {{percentUsed}}% of its {{limitLabel}}",
862
+ html: wrapInLayout(`
863
+ <h1 class="email-h1">Approaching your {{limitLabel}}</h1>
864
+ <p class="email-text">
865
+ <strong>{{workspaceName}}</strong> has used
866
+ <strong>{{currency}}{{usedUsd}}</strong> of its
867
+ <strong>{{currency}}{{limitUsd}}</strong> {{limitLabel}} this period
868
+ (<strong>{{percentUsed}}%</strong>).
869
+ </p>
870
+ <p class="email-text">
871
+ Once the included amount is used up, additional usage is billed on demand.
872
+ You can review usage or set a monthly spending limit below.
873
+ </p>
874
+ <a href="{{manageUrl}}" class="email-btn">Review usage &amp; limits</a>
875
+ <hr class="email-divider">
876
+ <p class="email-muted">
877
+ You're receiving this because you're a billing admin for this workspace.
878
+ </p>
879
+ `),
880
+ defaults: {
881
+ appName: EMAIL_CONSTANTS.APP_NAME,
882
+ currency: "$"
883
+ }
884
+ };
885
+
815
886
  // src/templates/workspace/member-joined.ts
816
887
  var memberJoinedTemplate = {
817
888
  name: "member-joined",
@@ -890,6 +961,8 @@ var allTemplates = [
890
961
  planUpgradedTemplate,
891
962
  paymentReceiptTemplate,
892
963
  paymentFailedTemplate,
964
+ usageOverageChargedTemplate,
965
+ usageApproachingLimitTemplate,
893
966
  memberJoinedTemplate,
894
967
  memberRemovedTemplate,
895
968
  accountDeletedTemplate
package/dist/index.d.cts CHANGED
@@ -16,9 +16,13 @@ export { a as EmailAddress, b as EmailConfig, c as EmailError, d as EmailErrorCo
16
16
  *
17
17
  * @param template - Template string with {{variable}} placeholders
18
18
  * @param data - Data object with values to interpolate
19
+ * @param options - When `escapeValues` is true, interpolated values are
20
+ * HTML-encoded (use for HTML bodies; leave off for plain-text subject/text)
19
21
  * @returns Interpolated string
20
22
  */
21
- declare function interpolate(template: string, data: Record<string, unknown>): string;
23
+ declare function interpolate(template: string, data: Record<string, unknown>, options?: {
24
+ escapeValues?: boolean;
25
+ }): string;
22
26
  /**
23
27
  * Strip HTML tags to create plain text version.
24
28
  *
package/dist/index.d.ts CHANGED
@@ -16,9 +16,13 @@ export { a as EmailAddress, b as EmailConfig, c as EmailError, d as EmailErrorCo
16
16
  *
17
17
  * @param template - Template string with {{variable}} placeholders
18
18
  * @param data - Data object with values to interpolate
19
+ * @param options - When `escapeValues` is true, interpolated values are
20
+ * HTML-encoded (use for HTML bodies; leave off for plain-text subject/text)
19
21
  * @returns Interpolated string
20
22
  */
21
- declare function interpolate(template: string, data: Record<string, unknown>): string;
23
+ declare function interpolate(template: string, data: Record<string, unknown>, options?: {
24
+ escapeValues?: boolean;
25
+ }): string;
22
26
  /**
23
27
  * Strip HTML tags to create plain text version.
24
28
  *
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  passwordResetTemplate,
16
16
  welcomeTemplate,
17
17
  wrapInLayout
18
- } from "./chunk-VPWVZHAC.js";
18
+ } from "./chunk-AHEPUREO.js";
19
19
  export {
20
20
  DARK_STYLE_OVERRIDES,
21
21
  EMAIL_CONSTANTS,
package/dist/server.cjs CHANGED
@@ -379,10 +379,15 @@ function createProviderFromEnv() {
379
379
  }
380
380
 
381
381
  // src/templates.ts
382
- function interpolate(template, data) {
382
+ function escapeHtml(value) {
383
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
384
+ }
385
+ function interpolate(template, data, options = {}) {
383
386
  return template.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (match, path) => {
384
387
  const value = getNestedValue(data, path);
385
- return value !== void 0 ? String(value) : match;
388
+ if (value === void 0) return match;
389
+ const str = String(value);
390
+ return options.escapeValues ? escapeHtml(str) : str;
386
391
  });
387
392
  }
388
393
  function getNestedValue(obj, path) {
@@ -435,8 +440,8 @@ var EmailTemplateRegistry = class {
435
440
  currentYear: (/* @__PURE__ */ new Date()).getFullYear()
436
441
  };
437
442
  const subject = interpolate(template.subject, allData);
438
- const html = interpolate(template.html, allData);
439
- const text = template.text ? interpolate(template.text, allData) : htmlToText(html);
443
+ const html = interpolate(template.html, allData, { escapeValues: true });
444
+ const text = template.text ? interpolate(template.text, allData) : htmlToText(interpolate(template.html, allData));
440
445
  return { subject, html, text };
441
446
  }
442
447
  /**
@@ -887,6 +892,72 @@ var paymentFailedTemplate = {
887
892
  }
888
893
  };
889
894
 
895
+ // src/templates/billing/usage-overage-charged.ts
896
+ var usageOverageChargedTemplate = {
897
+ name: "usage-overage-charged",
898
+ subject: "Usage charge of {{currency}}{{amount}} for {{workspaceName}}",
899
+ html: wrapInLayout(`
900
+ <h1 class="email-h1">On-demand usage charged</h1>
901
+ <p class="email-text">
902
+ <strong>{{workspaceName}}</strong> has used more than its included monthly
903
+ usage, so we charged an on-demand usage block. We bill these in blocks as
904
+ you go rather than one large amount at the end of the period.
905
+ </p>
906
+ <table width="100%" cellpadding="0" cellspacing="0">
907
+ <tr>
908
+ <td class="email-detail-label">Charged now</td>
909
+ <td class="email-detail-value" align="right">{{currency}}{{amount}}</td>
910
+ </tr>
911
+ <tr>
912
+ <td class="email-detail-label">Overage this period</td>
913
+ <td class="email-detail-value" align="right">{{currency}}{{periodOverageUsd}}</td>
914
+ </tr>
915
+ </table>
916
+ <br>
917
+ <a href="{{invoiceUrl}}" class="email-btn-outline">View invoice</a>
918
+ <hr class="email-divider">
919
+ <p class="email-text">
920
+ Want to cap on-demand spend? You can set a monthly spending limit any time.
921
+ </p>
922
+ <a href="{{manageUrl}}" class="email-btn-outline">Set a spending limit</a>
923
+ <p class="email-muted">
924
+ If you have billing questions, reply to this email or contact support.
925
+ </p>
926
+ `),
927
+ defaults: {
928
+ appName: EMAIL_CONSTANTS.APP_NAME,
929
+ currency: "$"
930
+ }
931
+ };
932
+
933
+ // src/templates/billing/usage-approaching-limit.ts
934
+ var usageApproachingLimitTemplate = {
935
+ name: "usage-approaching-limit",
936
+ subject: "Usage alert: {{workspaceName}} is at {{percentUsed}}% of its {{limitLabel}}",
937
+ html: wrapInLayout(`
938
+ <h1 class="email-h1">Approaching your {{limitLabel}}</h1>
939
+ <p class="email-text">
940
+ <strong>{{workspaceName}}</strong> has used
941
+ <strong>{{currency}}{{usedUsd}}</strong> of its
942
+ <strong>{{currency}}{{limitUsd}}</strong> {{limitLabel}} this period
943
+ (<strong>{{percentUsed}}%</strong>).
944
+ </p>
945
+ <p class="email-text">
946
+ Once the included amount is used up, additional usage is billed on demand.
947
+ You can review usage or set a monthly spending limit below.
948
+ </p>
949
+ <a href="{{manageUrl}}" class="email-btn">Review usage &amp; limits</a>
950
+ <hr class="email-divider">
951
+ <p class="email-muted">
952
+ You're receiving this because you're a billing admin for this workspace.
953
+ </p>
954
+ `),
955
+ defaults: {
956
+ appName: EMAIL_CONSTANTS.APP_NAME,
957
+ currency: "$"
958
+ }
959
+ };
960
+
890
961
  // src/templates/workspace/member-joined.ts
891
962
  var memberJoinedTemplate = {
892
963
  name: "member-joined",
@@ -965,6 +1036,8 @@ var allTemplates = [
965
1036
  planUpgradedTemplate,
966
1037
  paymentReceiptTemplate,
967
1038
  paymentFailedTemplate,
1039
+ usageOverageChargedTemplate,
1040
+ usageApproachingLimitTemplate,
968
1041
  memberJoinedTemplate,
969
1042
  memberRemovedTemplate,
970
1043
  accountDeletedTemplate
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  EmailTemplateRegistry,
4
4
  createShogoTemplateRegistry,
5
5
  formatEmailAddress
6
- } from "./chunk-VPWVZHAC.js";
6
+ } from "./chunk-AHEPUREO.js";
7
7
 
8
8
  // src/providers/smtp.ts
9
9
  var SmtpProvider = class {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shogo-ai/email",
3
- "version": "1.9.11",
3
+ "version": "1.9.13",
4
4
  "description": "Shogo Email — multi-provider transactional email (SES, SMTP, OCI) with built-in transactional templates.",
5
5
  "license": "MIT",
6
6
  "type": "module",