@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.
@@ -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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"}
@@ -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
  *
@@ -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
- } : {}, options.replyTo ? {
685
- replyTo: options.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;