@aglyn/shared-util-email 1.0.0-beta.275 → 1.0.0-beta.282
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.
- package/package.json +3 -3
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/domain-emails.d.ts +2 -0
- package/src/lib/domain-emails.js +111 -5
- package/src/lib/domain-emails.js.map +1 -1
- package/src/lib/email-linkify.d.ts +1 -1
- package/src/lib/email-linkify.js +9 -17
- package/src/lib/email-linkify.js.map +1 -1
- package/src/lib/mail-provider-resend.js +7 -1
- package/src/lib/mail-provider-resend.js.map +1 -1
- package/src/lib/mail-provider-webhook.js +3 -1
- package/src/lib/mail-provider-webhook.js.map +1 -1
- package/src/lib/mail-provider.d.ts +3 -1
- package/src/lib/mail-provider.js.map +1 -1
- package/src/lib/pos-hardware-emails.d.ts +23 -0
- package/src/lib/pos-hardware-emails.js +310 -0
- package/src/lib/pos-hardware-emails.js.map +1 -0
- package/src/lib/send-email.d.ts +31 -0
- package/src/lib/send-email.js +83 -3
- package/src/lib/send-email.js.map +1 -1
- package/src/lib/system-email-catalog.js +173 -13
- package/src/lib/system-email-catalog.js.map +1 -1
- package/src/lib/tenant-emails.generated.js +69 -0
- package/src/lib/tenant-emails.generated.js.map +1 -1
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /*==========================================
|
|
17
|
+
* A CARD READER BOUGHT FROM THE PLATFORM, AS SYSTEM EMAILS (AGL-4038).
|
|
18
|
+
*
|
|
19
|
+
* The commerce plugin's POS hardware shop sends these to the person who
|
|
20
|
+
* placed the order: the order received, the parcel shipped with its
|
|
21
|
+
* tracking, and the parcel delivered with the way to set the reader up. The
|
|
22
|
+
* platform is the seller, so they are SYSTEM emails in the platform's brand
|
|
23
|
+
* (or the workspace's white label), not a store's own emails to its buyers.
|
|
24
|
+
*
|
|
25
|
+
* They live here, beside the catalog, for the reason `domain-emails.ts`
|
|
26
|
+
* gives: system emails have no plugin seam, and the staff System emails page
|
|
27
|
+
* reads this registry before any plugin loads.
|
|
28
|
+
*
|
|
29
|
+
* Amounts and dates arrive formatted; a template never does arithmetic.
|
|
30
|
+
*=========================================*/ export const POS_HARDWARE_EMAIL_KEYS = {
|
|
31
|
+
received: 'pos-hardware-order-received',
|
|
32
|
+
shipped: 'pos-hardware-order-shipped',
|
|
33
|
+
delivered: 'pos-hardware-order-delivered'
|
|
34
|
+
};
|
|
35
|
+
const SOURCE = 'libs/plugins/commerce/src/lib/server/pos-hardware-notify.ts';
|
|
36
|
+
/**
|
|
37
|
+
* The sample site's Point of sale page in the console, for the designer's
|
|
38
|
+
* preview of the two links. It is a page a merchant opens, not a storage path,
|
|
39
|
+
* so it is written as one value rather than spelled out beside each link.
|
|
40
|
+
*/ const SAMPLE_POS_PAGE = [
|
|
41
|
+
'/test-org',
|
|
42
|
+
'hosts',
|
|
43
|
+
'test-bakery',
|
|
44
|
+
'products',
|
|
45
|
+
'pos'
|
|
46
|
+
].join('/');
|
|
47
|
+
function tokens(origin) {
|
|
48
|
+
return {
|
|
49
|
+
org: {
|
|
50
|
+
name: 'org.name',
|
|
51
|
+
description: 'The workspace that placed the order',
|
|
52
|
+
sample: 'Test Org'
|
|
53
|
+
},
|
|
54
|
+
site: {
|
|
55
|
+
name: 'site.name',
|
|
56
|
+
description: 'The site the order was placed from',
|
|
57
|
+
sample: 'Test Bakery'
|
|
58
|
+
},
|
|
59
|
+
number: {
|
|
60
|
+
name: 'order.number',
|
|
61
|
+
description: 'The hardware order number',
|
|
62
|
+
sample: 'HW-3F9A21'
|
|
63
|
+
},
|
|
64
|
+
items: {
|
|
65
|
+
name: 'order.items',
|
|
66
|
+
description: 'What was ordered, one line per item',
|
|
67
|
+
sample: 'Stripe Reader S710 × 1\nCharging dock for the S700 and S710 × 1'
|
|
68
|
+
},
|
|
69
|
+
total: {
|
|
70
|
+
name: 'order.total',
|
|
71
|
+
description: 'What was charged, shipping and tax included',
|
|
72
|
+
sample: '$123.45'
|
|
73
|
+
},
|
|
74
|
+
shipTo: {
|
|
75
|
+
name: 'order.shipTo',
|
|
76
|
+
description: 'The name and address it ships to',
|
|
77
|
+
sample: 'Alex Test\n12 Main St\nSpringfield, IL 62701'
|
|
78
|
+
},
|
|
79
|
+
shipping: {
|
|
80
|
+
name: 'order.shippingMethod',
|
|
81
|
+
description: 'The shipping speed chosen',
|
|
82
|
+
sample: 'Standard shipping'
|
|
83
|
+
},
|
|
84
|
+
carrier: {
|
|
85
|
+
name: 'order.carrier',
|
|
86
|
+
description: 'The carrier delivering the parcel',
|
|
87
|
+
sample: 'UPS'
|
|
88
|
+
},
|
|
89
|
+
trackingNumber: {
|
|
90
|
+
name: 'order.trackingNumber',
|
|
91
|
+
description: 'The parcel’s tracking number',
|
|
92
|
+
sample: '1Z999AA10123456784'
|
|
93
|
+
},
|
|
94
|
+
trackingUrl: {
|
|
95
|
+
name: 'order.trackingUrl',
|
|
96
|
+
description: 'The carrier’s tracking page for the parcel',
|
|
97
|
+
sample: 'https://www.ups.com/track?tracknum=1Z999AA10123456784'
|
|
98
|
+
},
|
|
99
|
+
ordersUrl: {
|
|
100
|
+
name: 'order.ordersUrl',
|
|
101
|
+
description: 'The Hardware card, where the order and its status are',
|
|
102
|
+
sample: `${origin}${SAMPLE_POS_PAGE}#pos-hardware`
|
|
103
|
+
},
|
|
104
|
+
setupUrl: {
|
|
105
|
+
name: 'order.setupUrl',
|
|
106
|
+
description: 'The POS devices card, where a reader is added to a register',
|
|
107
|
+
sample: `${origin}${SAMPLE_POS_PAGE}#pos-devices`
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function posHardwareSystemEmailTemplates(origin) {
|
|
112
|
+
const t = tokens(origin);
|
|
113
|
+
const footer = 'You’re receiving this because you ordered card reader hardware for ' + '{{site.name}} from {{brand.productName}}.';
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
key: POS_HARDWARE_EMAIL_KEYS.received,
|
|
117
|
+
name: 'Card reader order received',
|
|
118
|
+
description: 'Sent to the person who ordered card readers or accessories from the ' + 'POS hardware shop, once the payment goes through.',
|
|
119
|
+
deliveredBy: 'resend',
|
|
120
|
+
defaultSubject: 'We have your card reader order {{order.number}}',
|
|
121
|
+
mergeTokens: [
|
|
122
|
+
t.org,
|
|
123
|
+
t.site,
|
|
124
|
+
t.number,
|
|
125
|
+
t.items,
|
|
126
|
+
t.total,
|
|
127
|
+
t.shipTo,
|
|
128
|
+
t.shipping,
|
|
129
|
+
t.ordersUrl
|
|
130
|
+
],
|
|
131
|
+
defaultBody: [
|
|
132
|
+
{
|
|
133
|
+
block: 'text',
|
|
134
|
+
text: '{{site.name}} · Order {{order.number}}',
|
|
135
|
+
variant: 'eyebrow'
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
block: 'text',
|
|
139
|
+
text: 'Your hardware order is in',
|
|
140
|
+
variant: 'heading'
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
block: 'text',
|
|
144
|
+
text: 'Thanks for your order. We place it with Stripe, which makes the readers and ships them straight to you. We will email you again with tracking as soon as it ships.',
|
|
145
|
+
variant: 'body'
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
block: 'text',
|
|
149
|
+
text: '{{order.items}}\nShipping: {{order.shippingMethod}}\nTotal charged: {{order.total}}',
|
|
150
|
+
variant: 'details'
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
block: 'text',
|
|
154
|
+
text: 'Shipping to',
|
|
155
|
+
variant: 'subheading'
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
block: 'text',
|
|
159
|
+
text: '{{order.shipTo}}',
|
|
160
|
+
variant: 'details'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
block: 'text',
|
|
164
|
+
text: 'What happens next',
|
|
165
|
+
variant: 'subheading'
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
block: 'text',
|
|
169
|
+
text: 'While the order is being prepared you can follow it on the Hardware card in your point of sale settings. If something is wrong with the address, contact support before it ships: {{brand.supportUrl}}',
|
|
170
|
+
variant: 'body'
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
block: 'button',
|
|
174
|
+
label: 'See your order',
|
|
175
|
+
href: '{{order.ordersUrl}}'
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
block: 'text',
|
|
179
|
+
text: 'Order {{order.number}} for {{org.name}}.',
|
|
180
|
+
variant: 'caption'
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
footerReason: footer,
|
|
184
|
+
source: SOURCE
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
key: POS_HARDWARE_EMAIL_KEYS.shipped,
|
|
188
|
+
name: 'Card reader order shipped',
|
|
189
|
+
description: 'Sent when a card reader order from the POS hardware shop ships, with ' + 'the carrier and a tracking link.',
|
|
190
|
+
deliveredBy: 'resend',
|
|
191
|
+
defaultSubject: 'Your card reader order {{order.number}} has shipped',
|
|
192
|
+
mergeTokens: [
|
|
193
|
+
t.org,
|
|
194
|
+
t.site,
|
|
195
|
+
t.number,
|
|
196
|
+
t.items,
|
|
197
|
+
t.shipTo,
|
|
198
|
+
t.carrier,
|
|
199
|
+
t.trackingNumber,
|
|
200
|
+
t.trackingUrl,
|
|
201
|
+
t.ordersUrl
|
|
202
|
+
],
|
|
203
|
+
defaultBody: [
|
|
204
|
+
{
|
|
205
|
+
block: 'text',
|
|
206
|
+
text: '{{site.name}} · Order {{order.number}}',
|
|
207
|
+
variant: 'eyebrow'
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
block: 'text',
|
|
211
|
+
text: 'Your hardware is on its way',
|
|
212
|
+
variant: 'heading'
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
block: 'text',
|
|
216
|
+
text: 'Your order left Stripe’s warehouse and is with {{order.carrier}}. Someone may need to sign for it, so keep an eye on the tracking page for the delivery day.',
|
|
217
|
+
variant: 'body'
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
block: 'text',
|
|
221
|
+
text: '{{order.items}}\nCarrier: {{order.carrier}}\nTracking number: {{order.trackingNumber}}',
|
|
222
|
+
variant: 'details'
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
block: 'button',
|
|
226
|
+
label: 'Track your parcel',
|
|
227
|
+
href: '{{order.trackingUrl}}'
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
block: 'text',
|
|
231
|
+
text: 'Before it arrives',
|
|
232
|
+
variant: 'subheading'
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
block: 'text',
|
|
236
|
+
text: 'Pick the spot where the reader will live: it needs power and Wi-Fi, or a cellular signal for the S710. Once it is delivered, you add it to a register on the POS devices card in a minute.',
|
|
237
|
+
variant: 'body'
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
block: 'text',
|
|
241
|
+
text: 'Shipping to: {{order.shipTo}}',
|
|
242
|
+
variant: 'caption'
|
|
243
|
+
}
|
|
244
|
+
],
|
|
245
|
+
footerReason: footer,
|
|
246
|
+
source: SOURCE
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
key: POS_HARDWARE_EMAIL_KEYS.delivered,
|
|
250
|
+
name: 'Card reader order delivered',
|
|
251
|
+
description: 'Sent when a card reader order from the POS hardware shop is delivered, ' + 'with the way to set the reader up at a register.',
|
|
252
|
+
deliveredBy: 'resend',
|
|
253
|
+
defaultSubject: 'Your card reader order {{order.number}} was delivered',
|
|
254
|
+
mergeTokens: [
|
|
255
|
+
t.org,
|
|
256
|
+
t.site,
|
|
257
|
+
t.number,
|
|
258
|
+
t.items,
|
|
259
|
+
t.setupUrl,
|
|
260
|
+
t.ordersUrl
|
|
261
|
+
],
|
|
262
|
+
defaultBody: [
|
|
263
|
+
{
|
|
264
|
+
block: 'text',
|
|
265
|
+
text: '{{site.name}} · Order {{order.number}}',
|
|
266
|
+
variant: 'eyebrow'
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
block: 'text',
|
|
270
|
+
text: 'Your hardware has arrived',
|
|
271
|
+
variant: 'heading'
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
block: 'text',
|
|
275
|
+
text: 'Your order was delivered. Here is how to start taking payments with it.',
|
|
276
|
+
variant: 'body'
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
block: 'text',
|
|
280
|
+
text: '{{order.items}}',
|
|
281
|
+
variant: 'details'
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
block: 'text',
|
|
285
|
+
text: 'Set up your reader',
|
|
286
|
+
variant: 'subheading'
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
block: 'text',
|
|
290
|
+
text: '1. Charge it and switch it on.\n2. Connect it to Wi-Fi from the reader’s Settings.\n3. On the reader, generate a pairing code.\n4. On the POS devices card, choose Add card reader and enter the code.',
|
|
291
|
+
variant: 'details'
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
block: 'button',
|
|
295
|
+
label: 'Set up your reader',
|
|
296
|
+
href: '{{order.setupUrl}}'
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
block: 'text',
|
|
300
|
+
text: 'The M2 pairs by Bluetooth from the {{brand.productName}} POS app instead. If anything arrived damaged, contact support: {{brand.supportUrl}}',
|
|
301
|
+
variant: 'caption'
|
|
302
|
+
}
|
|
303
|
+
],
|
|
304
|
+
footerReason: footer,
|
|
305
|
+
source: SOURCE
|
|
306
|
+
}
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
//# sourceMappingURL=pos-hardware-emails.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/shared/util/email/src/lib/pos-hardware-emails.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * A CARD READER BOUGHT FROM THE PLATFORM, AS SYSTEM EMAILS (AGL-4038).\n *\n * The commerce plugin's POS hardware shop sends these to the person who\n * placed the order: the order received, the parcel shipped with its\n * tracking, and the parcel delivered with the way to set the reader up. The\n * platform is the seller, so they are SYSTEM emails in the platform's brand\n * (or the workspace's white label), not a store's own emails to its buyers.\n *\n * They live here, beside the catalog, for the reason `domain-emails.ts`\n * gives: system emails have no plugin seam, and the staff System emails page\n * reads this registry before any plugin loads.\n *\n * Amounts and dates arrive formatted; a template never does arithmetic.\n *=========================================*/\n\nimport type {\n SystemEmailMergeToken,\n SystemEmailTemplateDefinition,\n} from './system-email-catalog'\n\nexport const POS_HARDWARE_EMAIL_KEYS = {\n received: 'pos-hardware-order-received',\n shipped: 'pos-hardware-order-shipped',\n delivered: 'pos-hardware-order-delivered',\n} as const\n\nconst SOURCE = 'libs/plugins/commerce/src/lib/server/pos-hardware-notify.ts'\n\n/**\n * The sample site's Point of sale page in the console, for the designer's\n * preview of the two links. It is a page a merchant opens, not a storage path,\n * so it is written as one value rather than spelled out beside each link.\n */\nconst SAMPLE_POS_PAGE = ['/test-org', 'hosts', 'test-bakery', 'products', 'pos'].join('/')\n\nfunction tokens(origin: string): Record<string, SystemEmailMergeToken> {\n return {\n org: { name: 'org.name', description: 'The workspace that placed the order', sample: 'Test Org' },\n site: { name: 'site.name', description: 'The site the order was placed from', sample: 'Test Bakery' },\n number: { name: 'order.number', description: 'The hardware order number', sample: 'HW-3F9A21' },\n items: {\n name: 'order.items',\n description: 'What was ordered, one line per item',\n sample: 'Stripe Reader S710 × 1\\nCharging dock for the S700 and S710 × 1',\n },\n total: { name: 'order.total', description: 'What was charged, shipping and tax included', sample: '$123.45' },\n shipTo: {\n name: 'order.shipTo',\n description: 'The name and address it ships to',\n sample: 'Alex Test\\n12 Main St\\nSpringfield, IL 62701',\n },\n shipping: { name: 'order.shippingMethod', description: 'The shipping speed chosen', sample: 'Standard shipping' },\n carrier: { name: 'order.carrier', description: 'The carrier delivering the parcel', sample: 'UPS' },\n trackingNumber: { name: 'order.trackingNumber', description: 'The parcel’s tracking number', sample: '1Z999AA10123456784' },\n trackingUrl: {\n name: 'order.trackingUrl',\n description: 'The carrier’s tracking page for the parcel',\n sample: 'https://www.ups.com/track?tracknum=1Z999AA10123456784',\n },\n ordersUrl: {\n name: 'order.ordersUrl',\n description: 'The Hardware card, where the order and its status are',\n sample: `${origin}${SAMPLE_POS_PAGE}#pos-hardware`,\n },\n setupUrl: {\n name: 'order.setupUrl',\n description: 'The POS devices card, where a reader is added to a register',\n sample: `${origin}${SAMPLE_POS_PAGE}#pos-devices`,\n },\n }\n}\n\nexport function posHardwareSystemEmailTemplates(\n origin: string,\n): SystemEmailTemplateDefinition[] {\n const t = tokens(origin)\n const footer =\n 'You’re receiving this because you ordered card reader hardware for ' +\n '{{site.name}} from {{brand.productName}}.'\n return [\n {\n key: POS_HARDWARE_EMAIL_KEYS.received,\n name: 'Card reader order received',\n description:\n 'Sent to the person who ordered card readers or accessories from the ' +\n 'POS hardware shop, once the payment goes through.',\n deliveredBy: 'resend',\n defaultSubject: 'We have your card reader order {{order.number}}',\n mergeTokens: [t.org, t.site, t.number, t.items, t.total, t.shipTo, t.shipping, t.ordersUrl],\n defaultBody: [\n { block: 'text', text: '{{site.name}} · Order {{order.number}}', variant: 'eyebrow' },\n { block: 'text', text: 'Your hardware order is in', variant: 'heading' },\n { block: 'text', text: 'Thanks for your order. We place it with Stripe, which makes the readers and ships them straight to you. We will email you again with tracking as soon as it ships.', variant: 'body' },\n { block: 'text', text: '{{order.items}}\\nShipping: {{order.shippingMethod}}\\nTotal charged: {{order.total}}', variant: 'details' },\n { block: 'text', text: 'Shipping to', variant: 'subheading' },\n { block: 'text', text: '{{order.shipTo}}', variant: 'details' },\n { block: 'text', text: 'What happens next', variant: 'subheading' },\n { block: 'text', text: 'While the order is being prepared you can follow it on the Hardware card in your point of sale settings. If something is wrong with the address, contact support before it ships: {{brand.supportUrl}}', variant: 'body' },\n { block: 'button', label: 'See your order', href: '{{order.ordersUrl}}' },\n { block: 'text', text: 'Order {{order.number}} for {{org.name}}.', variant: 'caption' },\n ],\n footerReason: footer,\n source: SOURCE,\n },\n {\n key: POS_HARDWARE_EMAIL_KEYS.shipped,\n name: 'Card reader order shipped',\n description:\n 'Sent when a card reader order from the POS hardware shop ships, with ' +\n 'the carrier and a tracking link.',\n deliveredBy: 'resend',\n defaultSubject: 'Your card reader order {{order.number}} has shipped',\n mergeTokens: [\n t.org, t.site, t.number, t.items, t.shipTo, t.carrier, t.trackingNumber, t.trackingUrl, t.ordersUrl,\n ],\n defaultBody: [\n { block: 'text', text: '{{site.name}} · Order {{order.number}}', variant: 'eyebrow' },\n { block: 'text', text: 'Your hardware is on its way', variant: 'heading' },\n { block: 'text', text: 'Your order left Stripe’s warehouse and is with {{order.carrier}}. Someone may need to sign for it, so keep an eye on the tracking page for the delivery day.', variant: 'body' },\n { block: 'text', text: '{{order.items}}\\nCarrier: {{order.carrier}}\\nTracking number: {{order.trackingNumber}}', variant: 'details' },\n { block: 'button', label: 'Track your parcel', href: '{{order.trackingUrl}}' },\n { block: 'text', text: 'Before it arrives', variant: 'subheading' },\n { block: 'text', text: 'Pick the spot where the reader will live: it needs power and Wi-Fi, or a cellular signal for the S710. Once it is delivered, you add it to a register on the POS devices card in a minute.', variant: 'body' },\n { block: 'text', text: 'Shipping to: {{order.shipTo}}', variant: 'caption' },\n ],\n footerReason: footer,\n source: SOURCE,\n },\n {\n key: POS_HARDWARE_EMAIL_KEYS.delivered,\n name: 'Card reader order delivered',\n description:\n 'Sent when a card reader order from the POS hardware shop is delivered, ' +\n 'with the way to set the reader up at a register.',\n deliveredBy: 'resend',\n defaultSubject: 'Your card reader order {{order.number}} was delivered',\n mergeTokens: [t.org, t.site, t.number, t.items, t.setupUrl, t.ordersUrl],\n defaultBody: [\n { block: 'text', text: '{{site.name}} · Order {{order.number}}', variant: 'eyebrow' },\n { block: 'text', text: 'Your hardware has arrived', variant: 'heading' },\n { block: 'text', text: 'Your order was delivered. Here is how to start taking payments with it.', variant: 'body' },\n { block: 'text', text: '{{order.items}}', variant: 'details' },\n { block: 'text', text: 'Set up your reader', variant: 'subheading' },\n { block: 'text', text: '1. Charge it and switch it on.\\n2. Connect it to Wi-Fi from the reader’s Settings.\\n3. On the reader, generate a pairing code.\\n4. On the POS devices card, choose Add card reader and enter the code.', variant: 'details' },\n { block: 'button', label: 'Set up your reader', href: '{{order.setupUrl}}' },\n { block: 'text', text: 'The M2 pairs by Bluetooth from the {{brand.productName}} POS app instead. If anything arrived damaged, contact support: {{brand.supportUrl}}', variant: 'caption' },\n ],\n footerReason: footer,\n source: SOURCE,\n },\n ]\n}\n"],"names":["POS_HARDWARE_EMAIL_KEYS","received","shipped","delivered","SOURCE","SAMPLE_POS_PAGE","join","tokens","origin","org","name","description","sample","site","number","items","total","shipTo","shipping","carrier","trackingNumber","trackingUrl","ordersUrl","setupUrl","posHardwareSystemEmailTemplates","t","footer","key","deliveredBy","defaultSubject","mergeTokens","defaultBody","block","text","variant","label","href","footerReason","source"],"mappings":"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"}
|
package/src/lib/send-email.d.ts
CHANGED
|
@@ -36,6 +36,30 @@ export interface EmailTag {
|
|
|
36
36
|
name: string;
|
|
37
37
|
value: string;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A file carried with a message (AGL-4042): a signed waiver's PDF, say.
|
|
41
|
+
* `content` is the file's bytes in base64. Bounded by
|
|
42
|
+
* {@link EMAIL_ATTACHMENT_LIMITS}, and refused on a marketing send, which is
|
|
43
|
+
* mail to a list and never a document owed to one person.
|
|
44
|
+
*/
|
|
45
|
+
export interface EmailAttachment {
|
|
46
|
+
filename: string;
|
|
47
|
+
contentType: string;
|
|
48
|
+
content: string;
|
|
49
|
+
}
|
|
50
|
+
export declare const EMAIL_ATTACHMENT_LIMITS: {
|
|
51
|
+
readonly count: 5;
|
|
52
|
+
/** The base64 characters across every attachment of one message. */
|
|
53
|
+
readonly totalBase64Chars: number;
|
|
54
|
+
};
|
|
55
|
+
/** The attachments a caller passed, or the reason they cannot ride. */
|
|
56
|
+
export declare function checkEmailAttachments(attachments: readonly EmailAttachment[] | undefined): {
|
|
57
|
+
ok: true;
|
|
58
|
+
attachments: EmailAttachment[];
|
|
59
|
+
} | {
|
|
60
|
+
ok: false;
|
|
61
|
+
detail: string;
|
|
62
|
+
};
|
|
39
63
|
export interface SendEmailOptions {
|
|
40
64
|
/** One or more recipient addresses. */
|
|
41
65
|
to: string | string[];
|
|
@@ -64,6 +88,8 @@ export interface SendEmailOptions {
|
|
|
64
88
|
* `to` recipients, so a marketing send refuses any.
|
|
65
89
|
*/
|
|
66
90
|
bcc?: string | string[];
|
|
91
|
+
/** Files carried with the message (AGL-4042); see {@link EmailAttachment}. */
|
|
92
|
+
attachments?: EmailAttachment[];
|
|
67
93
|
/**
|
|
68
94
|
* White-label display name for the sender (White-Label Phase 1). Replaces
|
|
69
95
|
* only the display name in front of the verified address — the address
|
|
@@ -438,6 +464,11 @@ export interface EmailConfig {
|
|
|
438
464
|
* long before the runtime env exists.
|
|
439
465
|
*/
|
|
440
466
|
export declare function getEmailConfig(): EmailConfig;
|
|
467
|
+
/**
|
|
468
|
+
* Where a reply to the platform's own mail goes: the operator's support
|
|
469
|
+
* mailbox, `NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL`, or nothing when it is unset.
|
|
470
|
+
*/
|
|
471
|
+
export declare function platformReplyTo(): string | undefined;
|
|
441
472
|
/**
|
|
442
473
|
* True when the provider has its settings and `USAGE_EMAIL_FROM` is present.
|
|
443
474
|
*
|
package/src/lib/send-email.js
CHANGED
|
@@ -32,6 +32,55 @@ import { bareSenderAddress } from "./email-delivery-events.js";
|
|
|
32
32
|
import { screenTenantMessage } from "./outbound-screen-gate.js";
|
|
33
33
|
import { providerRetryAtMs } from "./mail-provider.js";
|
|
34
34
|
import { mailProvider, mailProviderProblem } from "./mail-providers.js";
|
|
35
|
+
export const EMAIL_ATTACHMENT_LIMITS = {
|
|
36
|
+
count: 5,
|
|
37
|
+
/** The base64 characters across every attachment of one message. */ totalBase64Chars: 10 * 1024 * 1024
|
|
38
|
+
};
|
|
39
|
+
/** The attachments a caller passed, or the reason they cannot ride. */ export function checkEmailAttachments(attachments) {
|
|
40
|
+
const list = (attachments != null ? attachments : []).filter(Boolean);
|
|
41
|
+
if (list.length > EMAIL_ATTACHMENT_LIMITS.count) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
detail: `A message carries at most ${EMAIL_ATTACHMENT_LIMITS.count} attachments.`
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
let total = 0;
|
|
48
|
+
for (const attachment of list){
|
|
49
|
+
var _attachment_filename, _attachment_contentType, _attachment_content;
|
|
50
|
+
const filename = String((_attachment_filename = attachment.filename) != null ? _attachment_filename : '').trim();
|
|
51
|
+
if (!filename || /[\\/\r\n]/.test(filename)) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
detail: 'An attachment needs a plain file name.'
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (!/^[\w.+-]+\/[\w.+-]+$/.test(String((_attachment_contentType = attachment.contentType) != null ? _attachment_contentType : ''))) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
detail: `The attachment ${filename} has no content type.`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(String((_attachment_content = attachment.content) != null ? _attachment_content : ''))) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
detail: `The attachment ${filename} is not base64.`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
total += attachment.content.length;
|
|
70
|
+
}
|
|
71
|
+
if (total > EMAIL_ATTACHMENT_LIMITS.totalBase64Chars) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
detail: 'The attachments are too large to send.'
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
attachments: list.map((one)=>_extends({}, one, {
|
|
80
|
+
filename: one.filename.trim()
|
|
81
|
+
}))
|
|
82
|
+
};
|
|
83
|
+
}
|
|
35
84
|
/**
|
|
36
85
|
* The `context` tag, attached to every send (AGL-2407).
|
|
37
86
|
*
|
|
@@ -235,6 +284,12 @@ import { mailProvider, mailProviderProblem } from "./mail-providers.js";
|
|
|
235
284
|
from: process.env.USAGE_EMAIL_FROM || undefined
|
|
236
285
|
};
|
|
237
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Where a reply to the platform's own mail goes: the operator's support
|
|
289
|
+
* mailbox, `NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL`, or nothing when it is unset.
|
|
290
|
+
*/ export function platformReplyTo() {
|
|
291
|
+
return (process.env.NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL || '').trim() || undefined;
|
|
292
|
+
}
|
|
238
293
|
/**
|
|
239
294
|
* True when the provider has its settings and `USAGE_EMAIL_FROM` is present.
|
|
240
295
|
*
|
|
@@ -280,7 +335,7 @@ function normalizeRecipients(to) {
|
|
|
280
335
|
* `{ sent: false, reason: 'unconfigured' }` rather than failing, so local and
|
|
281
336
|
* preview environments keep working without a mail provider.
|
|
282
337
|
*/ export async function sendEmail(options) {
|
|
283
|
-
var _ref, _ref1, _ref2, _ref3, _ref4, _options_bcc, _options_text;
|
|
338
|
+
var _ref, _options_replyTo, _ref1, _ref2, _ref3, _ref4, _options_bcc, _options_text;
|
|
284
339
|
var _options_sendingIdentity, _options_sendingIdentity1, _options_marketing, _preflight_refused, _preflight_held, _options_marketing1, _options_marketing2, _options_headers;
|
|
285
340
|
const provider = mailProvider();
|
|
286
341
|
const configuredFrom = process.env.USAGE_EMAIL_FROM || undefined;
|
|
@@ -338,6 +393,12 @@ function normalizeRecipients(to) {
|
|
|
338
393
|
};
|
|
339
394
|
}
|
|
340
395
|
const from = applyFromName(resolvedFrom != null ? resolvedFrom : configuredFrom, messageFromName(options));
|
|
396
|
+
// Platform mail answers to the operator's support mailbox. The sending
|
|
397
|
+
// subdomain has no inbox, so without this a reply to an account, billing
|
|
398
|
+
// or team email went nowhere, and the From had to say "noreply" to admit
|
|
399
|
+
// it. A message on a resolved identity (a site's own address) keeps
|
|
400
|
+
// whatever its caller set; so does any caller that names its own.
|
|
401
|
+
const replyTo = (_options_replyTo = options.replyTo) != null ? _options_replyTo : resolvedFrom ? undefined : platformReplyTo();
|
|
341
402
|
const missing = [
|
|
342
403
|
...provider.missingSettings(),
|
|
343
404
|
...from ? [] : [
|
|
@@ -466,8 +527,25 @@ function normalizeRecipients(to) {
|
|
|
466
527
|
* gate minted the pair.
|
|
467
528
|
*/ let unsubscribeUrl = (_ref3 = (_options_marketing1 = options.marketing) == null ? void 0 : _options_marketing1.unsubscribeUrl) != null ? _ref3 : '';
|
|
468
529
|
let oneClickUrl = (_ref4 = (_options_marketing2 = options.marketing) == null ? void 0 : _options_marketing2.oneClickUrl) != null ? _ref4 : '';
|
|
530
|
+
const attached = checkEmailAttachments(options.attachments);
|
|
531
|
+
if (attached.ok === false) {
|
|
532
|
+
console.error(`${label} refused — ${attached.detail}`);
|
|
533
|
+
return {
|
|
534
|
+
sent: false,
|
|
535
|
+
reason: 'no-recipient',
|
|
536
|
+
detail: attached.detail
|
|
537
|
+
};
|
|
538
|
+
}
|
|
469
539
|
if (options.marketing) {
|
|
470
540
|
var _options_bcc1;
|
|
541
|
+
if (attached.attachments.length) {
|
|
542
|
+
console.error(`${label} refused — a marketing send carries no attachments`);
|
|
543
|
+
return {
|
|
544
|
+
sent: false,
|
|
545
|
+
reason: 'no-recipient',
|
|
546
|
+
detail: 'A marketing send carries no attachments.'
|
|
547
|
+
};
|
|
548
|
+
}
|
|
471
549
|
if (normalizeRecipients((_options_bcc1 = options.bcc) != null ? _options_bcc1 : []).length) {
|
|
472
550
|
// A blind copy of a marketing send is mail the gate never asked about.
|
|
473
551
|
console.error(`${label} refused — a marketing send carries no blind copies`);
|
|
@@ -681,10 +759,12 @@ function normalizeRecipients(to) {
|
|
|
681
759
|
headers
|
|
682
760
|
} : {}, tags.length ? {
|
|
683
761
|
tags
|
|
684
|
-
} : {},
|
|
685
|
-
replyTo
|
|
762
|
+
} : {}, replyTo ? {
|
|
763
|
+
replyTo
|
|
686
764
|
} : {}, bcc.length ? {
|
|
687
765
|
bcc
|
|
766
|
+
} : {}, attached.attachments.length ? {
|
|
767
|
+
attachments: attached.attachments
|
|
688
768
|
} : {}), options.context);
|
|
689
769
|
if (!outcome.accepted) {
|
|
690
770
|
var _refusal_detail;
|