backend-manager 5.0.92 → 5.0.93
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 -2
- 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 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +3 -2
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +3 -2
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +3 -2
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +3 -2
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +3 -2
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +3 -2
- package/src/manager/functions/core/actions/api/general/emails/general:download-app-link.js +2 -2
- package/src/manager/libraries/email.js +56 -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,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
|
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
|
uid,
|
|
15
|
+
userDoc,
|
|
15
16
|
assistant,
|
|
16
17
|
data: {
|
|
17
18
|
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,14 +12,24 @@ 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 {string} options.uid - User UID
|
|
15
|
+
* @param {string} options.uid - User UID
|
|
16
|
+
* @param {object} options.userDoc - User document data (used to get email directly, avoids race conditions)
|
|
16
17
|
* @param {object} options.assistant - Assistant instance
|
|
17
18
|
*/
|
|
18
|
-
function sendOrderEmail({ template, subject, categories, data, uid, assistant }) {
|
|
19
|
+
function sendOrderEmail({ template, subject, categories, data, uid, userDoc, assistant }) {
|
|
19
20
|
const email = assistant.Manager.Email(assistant);
|
|
20
21
|
|
|
22
|
+
// Use email directly from userDoc (already fetched by on-write.js, avoids redundant Firestore lookup)
|
|
23
|
+
const userEmail = userDoc?.auth?.email;
|
|
24
|
+
const userName = userDoc?.personal?.name?.first;
|
|
25
|
+
|
|
26
|
+
if (!userEmail) {
|
|
27
|
+
assistant.error(`sendOrderEmail(): No email found for uid=${uid}, skipping`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
const settings = {
|
|
22
|
-
to:
|
|
32
|
+
to: { email: userEmail, ...(userName && { name: userName }) },
|
|
23
33
|
subject,
|
|
24
34
|
template,
|
|
25
35
|
categories,
|
|
@@ -4,14 +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
|
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
|
uid,
|
|
15
|
+
userDoc,
|
|
15
16
|
assistant,
|
|
16
17
|
data: {
|
|
17
18
|
order: {
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js
CHANGED
|
@@ -5,16 +5,17 @@
|
|
|
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
|
uid,
|
|
18
|
+
userDoc,
|
|
18
19
|
assistant,
|
|
19
20
|
data: {
|
|
20
21
|
order: {
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js
CHANGED
|
@@ -4,14 +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
|
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
|
uid,
|
|
15
|
+
userDoc,
|
|
15
16
|
assistant,
|
|
16
17
|
data: {
|
|
17
18
|
order: {
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js
CHANGED
|
@@ -4,14 +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
|
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
|
uid,
|
|
15
|
+
userDoc,
|
|
15
16
|
assistant,
|
|
16
17
|
data: {
|
|
17
18
|
order: {
|
package/src/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js
CHANGED
|
@@ -4,15 +4,16 @@
|
|
|
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
|
uid,
|
|
16
|
+
userDoc,
|
|
16
17
|
assistant,
|
|
17
18
|
data: {
|
|
18
19
|
order: {
|
|
@@ -4,17 +4,18 @@
|
|
|
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
|
uid,
|
|
18
|
+
userDoc,
|
|
18
19
|
assistant,
|
|
19
20
|
data: {
|
|
20
21
|
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,7 +96,7 @@ 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) {
|
|
@@ -76,7 +104,7 @@ Email.prototype.build = async function (settings) {
|
|
|
76
104
|
}
|
|
77
105
|
|
|
78
106
|
// Add carbon copy recipients
|
|
79
|
-
if (
|
|
107
|
+
if (copy) {
|
|
80
108
|
cc.push({
|
|
81
109
|
email: app.email,
|
|
82
110
|
name: app.name,
|
|
@@ -116,17 +144,9 @@ Email.prototype.build = async function (settings) {
|
|
|
116
144
|
throw errorWithCode('Parameter subject is required', 400);
|
|
117
145
|
}
|
|
118
146
|
|
|
119
|
-
const templateId = settings.template;
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
}
|
|
147
|
+
const templateId = TEMPLATES[settings.template] || settings.template || TEMPLATES['default'];
|
|
148
|
+
const groupId = GROUPS[settings.group] || settings.group || GROUPS['default'];
|
|
149
|
+
const copy = settings.copy ?? true;
|
|
130
150
|
|
|
131
151
|
// Build categories
|
|
132
152
|
const categories = _.uniq([
|
|
@@ -139,7 +159,7 @@ Email.prototype.build = async function (settings) {
|
|
|
139
159
|
const sendAt = normalizeSendAt(settings.sendAt);
|
|
140
160
|
|
|
141
161
|
// Build unsubscribe URL
|
|
142
|
-
const unsubscribeUrl =
|
|
162
|
+
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
163
|
|
|
144
164
|
// Build signoff
|
|
145
165
|
const signoff = settings?.data?.signoff || {};
|
|
@@ -157,7 +177,7 @@ Email.prototype.build = async function (settings) {
|
|
|
157
177
|
const dynamicTemplateData = {
|
|
158
178
|
email: {
|
|
159
179
|
id: Manager.require('uuid').v4(),
|
|
160
|
-
subject: settings?.data?.email?.subject ||
|
|
180
|
+
subject: settings?.data?.email?.subject || subject,
|
|
161
181
|
preview: settings?.data?.email?.preview || null,
|
|
162
182
|
body: settings?.data?.email?.body || null,
|
|
163
183
|
unsubscribeUrl,
|
|
@@ -165,7 +185,7 @@ Email.prototype.build = async function (settings) {
|
|
|
165
185
|
footer: {
|
|
166
186
|
text: settings?.data?.email?.footer?.text || null,
|
|
167
187
|
},
|
|
168
|
-
carbonCopy:
|
|
188
|
+
carbonCopy: copy,
|
|
169
189
|
},
|
|
170
190
|
personalization: {
|
|
171
191
|
email: to[0].email,
|
|
@@ -229,6 +249,8 @@ Email.prototype.send = async function (settings) {
|
|
|
229
249
|
const admin = self.admin;
|
|
230
250
|
const assistant = self.assistant;
|
|
231
251
|
|
|
252
|
+
assistant.log(`Email.send(): to=${JSON.stringify(settings.to)}, subject=${settings.subject}, template=${settings.template}`);
|
|
253
|
+
|
|
232
254
|
// Build email from settings (throws with code: 400 on validation failure)
|
|
233
255
|
const email = await self.build(settings);
|
|
234
256
|
|
|
@@ -473,6 +495,24 @@ function errorWithCode(message, code) {
|
|
|
473
495
|
return err;
|
|
474
496
|
}
|
|
475
497
|
|
|
498
|
+
/**
|
|
499
|
+
* Convert SVG image URLs to PNG equivalents — email clients don't render SVGs.
|
|
500
|
+
* CDN naming convention: `-x.svg` → `-1024.png`
|
|
501
|
+
*/
|
|
502
|
+
function sanitizeImagesForEmail(images) {
|
|
503
|
+
const result = {};
|
|
504
|
+
|
|
505
|
+
for (const [key, value] of Object.entries(images)) {
|
|
506
|
+
if (typeof value === 'string' && value.endsWith('.svg')) {
|
|
507
|
+
result[key] = value.replace(/-x\.svg$/, '-1024.png');
|
|
508
|
+
} else {
|
|
509
|
+
result[key] = value;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return result;
|
|
514
|
+
}
|
|
515
|
+
|
|
476
516
|
/**
|
|
477
517
|
* URL-encode a value as base64
|
|
478
518
|
*/
|
|
@@ -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',
|