backend-manager 5.0.92 → 5.0.94
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 +1 -1
- package/src/manager/events/firestore/payments-webhooks/on-write.js +3 -1
- package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +1 -1
- package/src/manager/events/firestore/payments-webhooks/transitions/send-email.js +13 -4
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +3 -3
- package/src/manager/functions/core/actions/api/general/emails/general:download-app-link.js +2 -2
- package/src/manager/libraries/email.js +57 -16
- package/src/manager/routes/general/email/templates/download-app-link.js +2 -2
- package/src/manager/routes/user/signup/post.js +6 -6
- package/src/manager/schemas/admin/email/post.js +3 -3
- package/test/helpers/email.js +45 -5
package/package.json
CHANGED
|
@@ -140,6 +140,8 @@ async function processPaymentEvent({ category, library, resource, resourceType,
|
|
|
140
140
|
const userData = userDoc.exists ? userDoc.data() : {};
|
|
141
141
|
const before = isSubscription ? (userData.subscription || null) : null;
|
|
142
142
|
|
|
143
|
+
assistant.log(`User doc for ${uid}: exists=${userDoc.exists}, email=${userData?.auth?.email || 'null'}, name=${userData?.personal?.name?.first || 'null'}, subscription=${userData?.subscription?.product?.id || 'null'}`);
|
|
144
|
+
|
|
143
145
|
// Auto-fill user name from payment processor if not already set
|
|
144
146
|
if (!userData?.personal?.name?.first) {
|
|
145
147
|
const customerName = extractCustomerName(resource, resourceType);
|
|
@@ -194,7 +196,7 @@ async function processPaymentEvent({ category, library, resource, resourceType,
|
|
|
194
196
|
|
|
195
197
|
if (shouldRunHandlers) {
|
|
196
198
|
transitions.dispatch(transitionName, category, {
|
|
197
|
-
before, after: unified, order, uid, assistant,
|
|
199
|
+
before, after: unified, order, uid, userDoc: userData, assistant,
|
|
198
200
|
});
|
|
199
201
|
} else {
|
|
200
202
|
assistant.log(`Transition handler skipped (testing mode): ${category}/${transitionName}`);
|
package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const { sendOrderEmail, formatDate } = require('../send-email.js');
|
|
6
6
|
|
|
7
|
-
module.exports = async function ({ before, after, order, uid, assistant }) {
|
|
7
|
+
module.exports = async function ({ before, after, order, uid, userDoc, assistant }) {
|
|
8
8
|
assistant.log(`Transition [one-time/purchase-completed]: uid=${uid}, resourceId=${after.payment.resourceId}`);
|
|
9
9
|
|
|
10
10
|
sendOrderEmail({
|
|
11
|
-
template: '
|
|
11
|
+
template: 'main/order/confirmation',
|
|
12
12
|
subject: 'Your order is confirmed!',
|
|
13
13
|
categories: ['order/confirmation'],
|
|
14
|
-
|
|
14
|
+
userDoc,
|
|
15
15
|
assistant,
|
|
16
16
|
data: {
|
|
17
17
|
order: {
|
package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* NOTE: No email template exists for this transition yet. Keeping as stub.
|
|
6
6
|
*/
|
|
7
|
-
module.exports = async function ({ before, after, order, uid, assistant }) {
|
|
7
|
+
module.exports = async function ({ before, after, order, uid, userDoc, assistant }) {
|
|
8
8
|
assistant.log(`Transition [one-time/purchase-failed]: uid=${uid}, orderId=${order?.id}`);
|
|
9
9
|
|
|
10
10
|
// TODO: Send payment failure email once template is created
|
|
@@ -12,18 +12,27 @@ const moment = require('moment');
|
|
|
12
12
|
* @param {string} options.subject - Email subject line
|
|
13
13
|
* @param {string[]} options.categories - SendGrid categories for filtering
|
|
14
14
|
* @param {object} options.data - Template data (passed as-is to the email)
|
|
15
|
-
* @param {
|
|
15
|
+
* @param {object} options.userDoc - User document data (already fetched by on-write.js)
|
|
16
16
|
* @param {object} options.assistant - Assistant instance
|
|
17
17
|
*/
|
|
18
|
-
function sendOrderEmail({ template, subject, categories, data,
|
|
18
|
+
function sendOrderEmail({ template, subject, categories, data, userDoc, assistant }) {
|
|
19
19
|
const email = assistant.Manager.Email(assistant);
|
|
20
20
|
|
|
21
|
+
const userEmail = userDoc?.auth?.email;
|
|
22
|
+
const userName = userDoc?.personal?.name?.first;
|
|
23
|
+
const uid = userDoc?.auth?.uid;
|
|
24
|
+
|
|
25
|
+
if (!userEmail) {
|
|
26
|
+
assistant.error(`sendOrderEmail(): No email found for uid=${uid}, skipping`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
const settings = {
|
|
22
|
-
to:
|
|
31
|
+
to: { email: userEmail, ...(userName && { name: userName }) },
|
|
23
32
|
subject,
|
|
24
33
|
template,
|
|
25
34
|
categories,
|
|
26
|
-
copy:
|
|
35
|
+
copy: true,
|
|
27
36
|
data,
|
|
28
37
|
};
|
|
29
38
|
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const { sendOrderEmail, formatDate } = require('../send-email.js');
|
|
6
6
|
|
|
7
|
-
module.exports = async function ({ before, after, order, uid, assistant }) {
|
|
7
|
+
module.exports = async function ({ before, after, order, uid, userDoc, assistant }) {
|
|
8
8
|
assistant.log(`Transition [subscription/cancellation-requested]: uid=${uid}, product=${after.product.id}, cancelDate=${after.cancellation.date?.timestamp}`);
|
|
9
9
|
|
|
10
10
|
sendOrderEmail({
|
|
11
|
-
template: '
|
|
11
|
+
template: 'main/order/cancellation-requested',
|
|
12
12
|
subject: 'Your subscription cancellation is confirmed',
|
|
13
13
|
categories: ['order/cancellation-requested'],
|
|
14
|
-
|
|
14
|
+
userDoc,
|
|
15
15
|
assistant,
|
|
16
16
|
data: {
|
|
17
17
|
order: {
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
const { sendOrderEmail, formatDate } = require('../send-email.js');
|
|
7
7
|
|
|
8
|
-
module.exports = async function ({ before, after, order, uid, assistant }) {
|
|
8
|
+
module.exports = async function ({ before, after, order, uid, userDoc, assistant }) {
|
|
9
9
|
const isTrial = after.trial?.claimed === true;
|
|
10
10
|
|
|
11
11
|
assistant.log(`Transition [subscription/new-subscription]: uid=${uid}, product=${after.product.id}, frequency=${after.payment.frequency}, trial=${isTrial}`);
|
|
12
12
|
|
|
13
13
|
sendOrderEmail({
|
|
14
|
-
template: '
|
|
14
|
+
template: 'main/order/confirmation',
|
|
15
15
|
subject: isTrial ? 'Your free trial has started!' : 'Your subscription is confirmed!',
|
|
16
16
|
categories: ['order/confirmation'],
|
|
17
|
-
|
|
17
|
+
userDoc,
|
|
18
18
|
assistant,
|
|
19
19
|
data: {
|
|
20
20
|
order: {
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const { sendOrderEmail, formatDate } = require('../send-email.js');
|
|
6
6
|
|
|
7
|
-
module.exports = async function ({ before, after, order, uid, assistant }) {
|
|
7
|
+
module.exports = async function ({ before, after, order, uid, userDoc, assistant }) {
|
|
8
8
|
assistant.log(`Transition [subscription/payment-failed]: uid=${uid}, product=${after.product.id}, previousStatus=${before?.status}`);
|
|
9
9
|
|
|
10
10
|
sendOrderEmail({
|
|
11
|
-
template: '
|
|
11
|
+
template: 'main/order/payment-failed',
|
|
12
12
|
subject: 'Your payment failed',
|
|
13
13
|
categories: ['order/payment-failed'],
|
|
14
|
-
|
|
14
|
+
userDoc,
|
|
15
15
|
assistant,
|
|
16
16
|
data: {
|
|
17
17
|
order: {
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const { sendOrderEmail, formatDate } = require('../send-email.js');
|
|
6
6
|
|
|
7
|
-
module.exports = async function ({ before, after, order, uid, assistant }) {
|
|
7
|
+
module.exports = async function ({ before, after, order, uid, userDoc, assistant }) {
|
|
8
8
|
assistant.log(`Transition [subscription/payment-recovered]: uid=${uid}, product=${after.product.id}`);
|
|
9
9
|
|
|
10
10
|
sendOrderEmail({
|
|
11
|
-
template: '
|
|
11
|
+
template: 'main/order/payment-recovered',
|
|
12
12
|
subject: 'Your payment was successful',
|
|
13
13
|
categories: ['order/payment-recovered'],
|
|
14
|
-
|
|
14
|
+
userDoc,
|
|
15
15
|
assistant,
|
|
16
16
|
data: {
|
|
17
17
|
order: {
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const { sendOrderEmail, formatDate } = require('../send-email.js');
|
|
6
6
|
|
|
7
|
-
module.exports = async function ({ before, after, order, uid, assistant }) {
|
|
7
|
+
module.exports = async function ({ before, after, order, uid, userDoc, assistant }) {
|
|
8
8
|
const direction = after.product.id > before.product.id ? 'upgrade' : 'downgrade';
|
|
9
9
|
assistant.log(`Transition [subscription/plan-changed]: uid=${uid}, ${before.product.id} → ${after.product.id} (${direction})`);
|
|
10
10
|
|
|
11
11
|
sendOrderEmail({
|
|
12
|
-
template: '
|
|
12
|
+
template: 'main/order/plan-changed',
|
|
13
13
|
subject: 'Your subscription plan has been updated',
|
|
14
14
|
categories: ['order/plan-changed'],
|
|
15
|
-
|
|
15
|
+
userDoc,
|
|
16
16
|
assistant,
|
|
17
17
|
data: {
|
|
18
18
|
order: {
|
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const { sendOrderEmail, formatDate } = require('../send-email.js');
|
|
6
6
|
|
|
7
|
-
module.exports = async function ({ before, after, order, uid, assistant }) {
|
|
7
|
+
module.exports = async function ({ before, after, order, uid, userDoc, assistant }) {
|
|
8
8
|
assistant.log(`Transition [subscription/subscription-cancelled]: uid=${uid}, previousProduct=${before?.product?.id}, previousStatus=${before?.status}`);
|
|
9
9
|
|
|
10
10
|
// Check if subscription has a future expiry (e.g., cancelled at period end)
|
|
11
11
|
const hasFutureExpiry = after.expires?.timestamp && new Date(after.expires.timestamp) > new Date();
|
|
12
12
|
|
|
13
13
|
sendOrderEmail({
|
|
14
|
-
template: '
|
|
14
|
+
template: 'main/order/cancelled',
|
|
15
15
|
subject: 'Your subscription has been cancelled',
|
|
16
16
|
categories: ['order/cancelled'],
|
|
17
|
-
|
|
17
|
+
userDoc,
|
|
18
18
|
assistant,
|
|
19
19
|
data: {
|
|
20
20
|
order: {
|
|
@@ -12,8 +12,8 @@ module.exports = function (payload, config) {
|
|
|
12
12
|
},
|
|
13
13
|
categories: ['download'],
|
|
14
14
|
subject: `Free ${config.brand.name} download link for ${payload.name || 'you'}!`,
|
|
15
|
-
template: '
|
|
16
|
-
group:
|
|
15
|
+
template: 'main/misc/app-download-link',
|
|
16
|
+
group: 'marketing',
|
|
17
17
|
copy: false,
|
|
18
18
|
data: {},
|
|
19
19
|
}
|
|
@@ -17,6 +17,34 @@ const moment = require('moment');
|
|
|
17
17
|
// SendGrid limit for scheduled emails (72 hours, but use 71 for buffer)
|
|
18
18
|
const SEND_AT_LIMIT = 71;
|
|
19
19
|
|
|
20
|
+
// Template shortcut map — callers use readable paths instead of SendGrid IDs
|
|
21
|
+
// Paths mirror the email website structure: {category}/{subcategory}/{name}
|
|
22
|
+
const TEMPLATES = {
|
|
23
|
+
// v1 templates
|
|
24
|
+
'main/basic/card': 'd-b7f8da3c98ad49a2ad1e187f3a67b546',
|
|
25
|
+
'main/engagement/feedback': 'd-c1522214c67b47058669acc5a81ed663',
|
|
26
|
+
'main/misc/app-download-link': 'd-1d730ac8cc544b7cbccc8fa4a4b3f9ce',
|
|
27
|
+
|
|
28
|
+
// v2 templates
|
|
29
|
+
'main/order/confirmation': 'd-5371ac2b4e3b490bbce51bfc2922ece8',
|
|
30
|
+
'main/order/payment-failed': 'd-e56af0ac62364bfb9e50af02854e2cd3',
|
|
31
|
+
'main/order/payment-recovered': 'd-d6dbd17a260a4755b34a852ba09c2454',
|
|
32
|
+
'main/order/cancellation-requested': 'd-78074f3e8c844146bf263b86fc8d5ecf',
|
|
33
|
+
'main/order/cancelled': 'd-39041132e6b24e5ebf0e95bce2d94dba',
|
|
34
|
+
'main/order/plan-changed': 'd-399086311bbb48b4b77bc90b20fb9d0a',
|
|
35
|
+
'main/order/trial-ending': 'd-af8ab499cbfb4d56918b4118f44343b0',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// "default" resolves to the basic card template
|
|
39
|
+
TEMPLATES['default'] = TEMPLATES['main/basic/card'];
|
|
40
|
+
|
|
41
|
+
// Group shortcut map — SendGrid ASM group IDs
|
|
42
|
+
const GROUPS = {
|
|
43
|
+
'default': 24077,
|
|
44
|
+
'marketing': 25927,
|
|
45
|
+
'account': 25928,
|
|
46
|
+
};
|
|
47
|
+
|
|
20
48
|
function Email(assistant) {
|
|
21
49
|
const self = this;
|
|
22
50
|
|
|
@@ -68,15 +96,17 @@ Email.prototype.build = async function (settings) {
|
|
|
68
96
|
name: brand.name,
|
|
69
97
|
url: brand.url,
|
|
70
98
|
email: brand.contact?.email,
|
|
71
|
-
images: brand.images || {},
|
|
99
|
+
images: sanitizeImagesForEmail(brand.images || {}),
|
|
72
100
|
};
|
|
73
101
|
|
|
74
102
|
if (!app.email) {
|
|
75
103
|
throw errorWithCode('Missing brand.contact.email in backend-manager-config.json', 400);
|
|
76
104
|
}
|
|
77
105
|
|
|
106
|
+
const copy = settings.copy ?? true;
|
|
107
|
+
|
|
78
108
|
// Add carbon copy recipients
|
|
79
|
-
if (
|
|
109
|
+
if (copy) {
|
|
80
110
|
cc.push({
|
|
81
111
|
email: app.email,
|
|
82
112
|
name: app.name,
|
|
@@ -116,17 +146,8 @@ Email.prototype.build = async function (settings) {
|
|
|
116
146
|
throw errorWithCode('Parameter subject is required', 400);
|
|
117
147
|
}
|
|
118
148
|
|
|
119
|
-
const templateId = settings.template;
|
|
120
|
-
|
|
121
|
-
if (!templateId && !settings.html) {
|
|
122
|
-
throw errorWithCode('Parameter template is required', 400);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const groupId = settings.group;
|
|
126
|
-
|
|
127
|
-
if (!groupId) {
|
|
128
|
-
throw errorWithCode('Parameter group is required', 400);
|
|
129
|
-
}
|
|
149
|
+
const templateId = TEMPLATES[settings.template] || settings.template || TEMPLATES['default'];
|
|
150
|
+
const groupId = GROUPS[settings.group] || settings.group || GROUPS['default'];
|
|
130
151
|
|
|
131
152
|
// Build categories
|
|
132
153
|
const categories = _.uniq([
|
|
@@ -139,7 +160,7 @@ Email.prototype.build = async function (settings) {
|
|
|
139
160
|
const sendAt = normalizeSendAt(settings.sendAt);
|
|
140
161
|
|
|
141
162
|
// Build unsubscribe URL
|
|
142
|
-
const unsubscribeUrl =
|
|
163
|
+
const unsubscribeUrl = `${Manager.project.websiteUrl}/portal/account/email-preferences?email=${encode(to[0].email)}&asmId=${encode(groupId)}&templateId=${encode(templateId)}&appName=${app.name}&appUrl=${app.url}`;
|
|
143
164
|
|
|
144
165
|
// Build signoff
|
|
145
166
|
const signoff = settings?.data?.signoff || {};
|
|
@@ -157,7 +178,7 @@ Email.prototype.build = async function (settings) {
|
|
|
157
178
|
const dynamicTemplateData = {
|
|
158
179
|
email: {
|
|
159
180
|
id: Manager.require('uuid').v4(),
|
|
160
|
-
subject: settings?.data?.email?.subject ||
|
|
181
|
+
subject: settings?.data?.email?.subject || subject,
|
|
161
182
|
preview: settings?.data?.email?.preview || null,
|
|
162
183
|
body: settings?.data?.email?.body || null,
|
|
163
184
|
unsubscribeUrl,
|
|
@@ -165,7 +186,7 @@ Email.prototype.build = async function (settings) {
|
|
|
165
186
|
footer: {
|
|
166
187
|
text: settings?.data?.email?.footer?.text || null,
|
|
167
188
|
},
|
|
168
|
-
carbonCopy:
|
|
189
|
+
carbonCopy: copy,
|
|
169
190
|
},
|
|
170
191
|
personalization: {
|
|
171
192
|
email: to[0].email,
|
|
@@ -229,6 +250,8 @@ Email.prototype.send = async function (settings) {
|
|
|
229
250
|
const admin = self.admin;
|
|
230
251
|
const assistant = self.assistant;
|
|
231
252
|
|
|
253
|
+
assistant.log(`Email.send(): to=${JSON.stringify(settings.to)}, subject=${settings.subject}, template=${settings.template}`);
|
|
254
|
+
|
|
232
255
|
// Build email from settings (throws with code: 400 on validation failure)
|
|
233
256
|
const email = await self.build(settings);
|
|
234
257
|
|
|
@@ -473,6 +496,24 @@ function errorWithCode(message, code) {
|
|
|
473
496
|
return err;
|
|
474
497
|
}
|
|
475
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Convert SVG image URLs to PNG equivalents — email clients don't render SVGs.
|
|
501
|
+
* CDN naming convention: `-x.svg` → `-1024.png`
|
|
502
|
+
*/
|
|
503
|
+
function sanitizeImagesForEmail(images) {
|
|
504
|
+
const result = {};
|
|
505
|
+
|
|
506
|
+
for (const [key, value] of Object.entries(images)) {
|
|
507
|
+
if (typeof value === 'string' && value.endsWith('.svg')) {
|
|
508
|
+
result[key] = value.replace(/-x\.svg$/, '-1024.png');
|
|
509
|
+
} else {
|
|
510
|
+
result[key] = value;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return result;
|
|
515
|
+
}
|
|
516
|
+
|
|
476
517
|
/**
|
|
477
518
|
* URL-encode a value as base64
|
|
478
519
|
*/
|
|
@@ -16,8 +16,8 @@ module.exports = function (payload, config) {
|
|
|
16
16
|
},
|
|
17
17
|
categories: ['download'],
|
|
18
18
|
subject: `Free ${config.brand.name} download link for ${payload.name || 'you'}!`,
|
|
19
|
-
template: '
|
|
20
|
-
group:
|
|
19
|
+
template: 'main/misc/app-download-link',
|
|
20
|
+
group: 'marketing',
|
|
21
21
|
copy: false,
|
|
22
22
|
data: {},
|
|
23
23
|
}
|
|
@@ -280,8 +280,8 @@ function sendWelcomeEmail(assistant, email) {
|
|
|
280
280
|
to: email,
|
|
281
281
|
categories: ['account/welcome'],
|
|
282
282
|
subject: `Welcome to ${Manager.config.brand.name}!`,
|
|
283
|
-
template: '
|
|
284
|
-
group:
|
|
283
|
+
template: 'default',
|
|
284
|
+
group: 'account',
|
|
285
285
|
copy: false,
|
|
286
286
|
data: {
|
|
287
287
|
email: {
|
|
@@ -326,8 +326,8 @@ function sendCheckupEmail(assistant, email) {
|
|
|
326
326
|
to: email,
|
|
327
327
|
categories: ['account/checkup'],
|
|
328
328
|
subject: `How's your experience with ${Manager.config.brand.name}?`,
|
|
329
|
-
template: '
|
|
330
|
-
group:
|
|
329
|
+
template: 'default',
|
|
330
|
+
group: 'account',
|
|
331
331
|
copy: false,
|
|
332
332
|
sendAt: moment().add(7, 'days').unix(),
|
|
333
333
|
data: {
|
|
@@ -373,8 +373,8 @@ function sendFeedbackEmail(assistant, email) {
|
|
|
373
373
|
to: email,
|
|
374
374
|
categories: ['engagement/feedback'],
|
|
375
375
|
subject: `Want to share your feedback about ${Manager.config.brand.name}?`,
|
|
376
|
-
template: '
|
|
377
|
-
group:
|
|
376
|
+
template: 'main/engagement/feedback',
|
|
377
|
+
group: 'account',
|
|
378
378
|
copy: false,
|
|
379
379
|
sendAt: moment().add(14, 'days').unix(),
|
|
380
380
|
})
|
|
@@ -14,12 +14,12 @@ module.exports = () => ({
|
|
|
14
14
|
from: { types: ['object'], default: undefined },
|
|
15
15
|
replyTo: { types: ['string'], default: undefined },
|
|
16
16
|
subject: { types: ['string'], default: undefined },
|
|
17
|
-
template: { types: ['string'], default:
|
|
18
|
-
group: { types: ['number'], default:
|
|
17
|
+
template: { types: ['string'], default: undefined },
|
|
18
|
+
group: { types: ['number'], default: undefined },
|
|
19
19
|
sendAt: { types: ['number', 'string'], default: undefined },
|
|
20
20
|
user: { types: ['object'], default: {} },
|
|
21
21
|
data: { types: ['object'], default: {} },
|
|
22
22
|
categories: { types: ['array'], default: [] },
|
|
23
|
-
copy: { types: ['boolean'], default:
|
|
23
|
+
copy: { types: ['boolean'], default: undefined },
|
|
24
24
|
html: { types: ['string'], default: undefined },
|
|
25
25
|
});
|
package/test/helpers/email.js
CHANGED
|
@@ -45,19 +45,26 @@ module.exports = {
|
|
|
45
45
|
},
|
|
46
46
|
|
|
47
47
|
{
|
|
48
|
-
name: '
|
|
48
|
+
name: 'default-template-used-when-omitted',
|
|
49
49
|
auth: 'admin',
|
|
50
|
-
timeout:
|
|
50
|
+
timeout: 30000,
|
|
51
51
|
|
|
52
52
|
async run({ http, assert, config }) {
|
|
53
53
|
const response = await http.post('admin/email', {
|
|
54
|
-
subject: 'BEM Test Email -
|
|
54
|
+
subject: 'BEM Test Email - Default Template',
|
|
55
55
|
to: [{ email: `_test-receiver@${config.domain}` }],
|
|
56
|
-
template: '',
|
|
57
56
|
copy: false,
|
|
57
|
+
data: {
|
|
58
|
+
email: {
|
|
59
|
+
subject: 'BEM Test Email - Default Template',
|
|
60
|
+
body: 'Testing that default template is used when not specified.',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
58
63
|
});
|
|
59
64
|
|
|
60
|
-
assert.
|
|
65
|
+
assert.isSuccess(response, 'Should succeed with default template');
|
|
66
|
+
assert.equal(response.data.status, 'sent', 'Status should be sent');
|
|
67
|
+
assert.equal(response.data.options.templateId, 'd-b7f8da3c98ad49a2ad1e187f3a67b546', 'Should use default template');
|
|
61
68
|
},
|
|
62
69
|
},
|
|
63
70
|
|
|
@@ -350,6 +357,39 @@ module.exports = {
|
|
|
350
357
|
},
|
|
351
358
|
},
|
|
352
359
|
|
|
360
|
+
{
|
|
361
|
+
name: 'svg-images-converted-to-png',
|
|
362
|
+
auth: 'admin',
|
|
363
|
+
timeout: 30000,
|
|
364
|
+
|
|
365
|
+
async run({ http, assert, config }) {
|
|
366
|
+
const response = await http.post('admin/email', {
|
|
367
|
+
subject: 'BEM Test Email - SVG to PNG',
|
|
368
|
+
to: `_test-receiver@${config.domain}`,
|
|
369
|
+
copy: false,
|
|
370
|
+
data: {
|
|
371
|
+
email: {
|
|
372
|
+
subject: 'BEM Test Email - SVG to PNG',
|
|
373
|
+
body: 'Testing that SVG images are converted to PNG for email.',
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
assert.isSuccess(response, 'Should send email');
|
|
379
|
+
assert.equal(response.data.status, 'sent', 'Status should be sent');
|
|
380
|
+
|
|
381
|
+
const appImages = response.data.options.dynamicTemplateData.app.images;
|
|
382
|
+
|
|
383
|
+
// Any image that was an SVG should now be a PNG (-x.svg → -1024.png)
|
|
384
|
+
for (const [key, value] of Object.entries(appImages)) {
|
|
385
|
+
assert.ok(
|
|
386
|
+
!String(value || '').endsWith('.svg'),
|
|
387
|
+
`app.images.${key} should not be an SVG (got: ${value})`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
|
|
353
393
|
{
|
|
354
394
|
name: 'sendat-iso-string-accepted',
|
|
355
395
|
auth: 'admin',
|