backend-manager 5.0.154 → 5.0.157
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/CHANGELOG.md +41 -0
- package/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/src/cli/commands/setup-tests/firestore-indexes-required.js +1 -1
- package/src/cli/commands/setup-tests/functions-package.js +3 -1
- package/src/cli/commands/setup-tests/{required-indexes.js → helpers/required-indexes.js} +22 -0
- package/src/cli/commands/setup-tests/helpers/seed-campaigns.js +132 -0
- package/src/cli/commands/setup-tests/index.js +2 -0
- package/src/cli/commands/setup-tests/marketing-campaigns-seeded.js +109 -0
- package/src/manager/cron/daily/marketing-prune.js +140 -0
- package/src/manager/cron/frequent/marketing-campaigns.js +158 -0
- package/src/manager/events/auth/on-create.js +36 -1
- package/src/manager/libraries/email/constants.js +46 -2
- package/src/manager/libraries/email/index.js +13 -3
- package/src/manager/libraries/email/marketing/index.js +205 -33
- package/src/manager/libraries/email/providers/beehiiv.js +90 -0
- package/src/manager/libraries/email/providers/sendgrid.js +179 -1
- package/src/manager/libraries/email/transactional/index.js +17 -0
- package/src/manager/libraries/email/utm.js +116 -0
- package/src/manager/libraries/notification.js +223 -0
- package/src/manager/routes/admin/notification/post.js +16 -241
- package/src/manager/routes/marketing/campaign/delete.js +45 -0
- package/src/manager/routes/marketing/campaign/get.js +69 -0
- package/src/manager/routes/marketing/campaign/post.js +161 -0
- package/src/manager/routes/marketing/campaign/put.js +122 -0
- package/src/manager/routes/user/data-request/delete.js +3 -1
- package/src/manager/routes/user/data-request/get.js +3 -1
- package/src/manager/routes/user/data-request/post.js +3 -1
- package/src/manager/routes/user/delete.js +4 -3
- package/src/manager/routes/user/signup/post.js +10 -8
- package/src/manager/schemas/marketing/campaign/delete.js +6 -0
- package/src/manager/schemas/marketing/campaign/get.js +11 -0
- package/src/manager/schemas/marketing/campaign/post.js +35 -0
- package/src/manager/schemas/marketing/campaign/put.js +35 -0
- package/templates/backend-manager-config.json +3 -0
|
@@ -190,6 +190,8 @@ function sendDownloadEmail(assistant, user, requestId, downloads) {
|
|
|
190
190
|
const Manager = assistant.Manager;
|
|
191
191
|
const mailer = Manager.Email(assistant);
|
|
192
192
|
const uid = user.auth.uid;
|
|
193
|
+
const firstName = user.personal?.name?.first;
|
|
194
|
+
const greeting = firstName ? `Hey ${firstName}, your` : 'Your';
|
|
193
195
|
const downloadDate = assistant.meta.startTime.timestamp;
|
|
194
196
|
|
|
195
197
|
mailer.send({
|
|
@@ -205,7 +207,7 @@ function sendDownloadEmail(assistant, user, requestId, downloads) {
|
|
|
205
207
|
},
|
|
206
208
|
body: {
|
|
207
209
|
title: 'Data Download Confirmation',
|
|
208
|
-
message:
|
|
210
|
+
message: `${greeting} personal data export has been successfully downloaded.
|
|
209
211
|
|
|
210
212
|
**Download details:**
|
|
211
213
|
|
|
@@ -91,6 +91,8 @@ function sendConfirmationEmail(assistant, user, requestId, reason) {
|
|
|
91
91
|
const Manager = assistant.Manager;
|
|
92
92
|
const mailer = Manager.Email(assistant);
|
|
93
93
|
const uid = user.auth.uid;
|
|
94
|
+
const firstName = user.personal?.name?.first;
|
|
95
|
+
const greeting = firstName ? `Hey ${firstName}, we've` : `We've`;
|
|
94
96
|
const reasonLine = reason
|
|
95
97
|
? `\n\n**Reason provided:** ${reason}`
|
|
96
98
|
: '';
|
|
@@ -108,7 +110,7 @@ function sendConfirmationEmail(assistant, user, requestId, reason) {
|
|
|
108
110
|
},
|
|
109
111
|
body: {
|
|
110
112
|
title: 'Data Request Received',
|
|
111
|
-
message:
|
|
113
|
+
message: `${greeting} received your request for a copy of your personal data.${reasonLine}
|
|
112
114
|
|
|
113
115
|
**What happens next:**
|
|
114
116
|
|
|
@@ -81,7 +81,7 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
|
|
|
81
81
|
// Send confirmation email (fire-and-forget)
|
|
82
82
|
const shouldSend = !assistant.isTesting() || process.env.TEST_EXTENDED_MODE;
|
|
83
83
|
if (email && shouldSend) {
|
|
84
|
-
sendConfirmationEmail(assistant, email, reason);
|
|
84
|
+
sendConfirmationEmail(assistant, email, reason, userData?.personal?.name?.first);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
return assistant.respond({ success: true });
|
|
@@ -90,10 +90,11 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
|
|
|
90
90
|
/**
|
|
91
91
|
* Send account deletion confirmation email (fire-and-forget)
|
|
92
92
|
*/
|
|
93
|
-
function sendConfirmationEmail(assistant, email, reason) {
|
|
93
|
+
function sendConfirmationEmail(assistant, email, reason, firstName) {
|
|
94
94
|
const Manager = assistant.Manager;
|
|
95
95
|
const brandName = Manager.config.brand.name;
|
|
96
96
|
const mailer = Manager.Email(assistant);
|
|
97
|
+
const greeting = firstName ? `Hey ${firstName}, your` : 'Your';
|
|
97
98
|
const reasonLine = reason
|
|
98
99
|
? `\n\n**Reason provided:** ${reason}`
|
|
99
100
|
: '';
|
|
@@ -111,7 +112,7 @@ function sendConfirmationEmail(assistant, email, reason) {
|
|
|
111
112
|
},
|
|
112
113
|
body: {
|
|
113
114
|
title: 'Account Deleted',
|
|
114
|
-
message:
|
|
115
|
+
message: `${greeting} **${brandName}** account and all associated personal data have been permanently deleted from our systems. This action is irreversible.${reasonLine}
|
|
115
116
|
|
|
116
117
|
**What this means:**
|
|
117
118
|
|
|
@@ -77,7 +77,7 @@ module.exports = async ({ assistant, user, settings, libraries }) => {
|
|
|
77
77
|
|
|
78
78
|
// 6. Send emails + marketing (non-blocking, fire-and-forget)
|
|
79
79
|
syncMarketingContact(assistant, uid, email);
|
|
80
|
-
sendWelcomeEmails(assistant, uid);
|
|
80
|
+
sendWelcomeEmails(assistant, uid, inferred?.firstName);
|
|
81
81
|
|
|
82
82
|
return assistant.respond({ signedUp: true });
|
|
83
83
|
};
|
|
@@ -264,7 +264,7 @@ async function syncMarketingContact(assistant, uid, email) {
|
|
|
264
264
|
/**
|
|
265
265
|
* Send welcome, checkup, and feedback emails (non-blocking, fire-and-forget)
|
|
266
266
|
*/
|
|
267
|
-
function sendWelcomeEmails(assistant, uid) {
|
|
267
|
+
function sendWelcomeEmails(assistant, uid, firstName) {
|
|
268
268
|
const shouldSend = !assistant.isTesting() || process.env.TEST_EXTENDED_MODE;
|
|
269
269
|
|
|
270
270
|
if (!shouldSend) {
|
|
@@ -272,17 +272,18 @@ function sendWelcomeEmails(assistant, uid) {
|
|
|
272
272
|
return;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
sendWelcomeEmail(assistant, uid).catch(e => assistant.error('signup(): sendWelcomeEmail failed:', e));
|
|
276
|
-
sendCheckupEmail(assistant, uid).catch(e => assistant.error('signup(): sendCheckupEmail failed:', e));
|
|
275
|
+
sendWelcomeEmail(assistant, uid, firstName).catch(e => assistant.error('signup(): sendWelcomeEmail failed:', e));
|
|
276
|
+
sendCheckupEmail(assistant, uid, firstName).catch(e => assistant.error('signup(): sendCheckupEmail failed:', e));
|
|
277
277
|
sendFeedbackEmail(assistant, uid).catch(e => assistant.error('signup(): sendFeedbackEmail failed:', e));
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
/**
|
|
281
281
|
* Send welcome email (immediate)
|
|
282
282
|
*/
|
|
283
|
-
function sendWelcomeEmail(assistant, uid) {
|
|
283
|
+
function sendWelcomeEmail(assistant, uid, firstName) {
|
|
284
284
|
const Manager = assistant.Manager;
|
|
285
285
|
const mailer = Manager.Email(assistant);
|
|
286
|
+
const greeting = firstName ? `Hey ${firstName}, welcome` : 'Welcome';
|
|
286
287
|
|
|
287
288
|
return mailer.send({
|
|
288
289
|
to: uid,
|
|
@@ -297,7 +298,7 @@ function sendWelcomeEmail(assistant, uid) {
|
|
|
297
298
|
},
|
|
298
299
|
body: {
|
|
299
300
|
title: `Welcome to ${Manager.config.brand.name}!`,
|
|
300
|
-
message:
|
|
301
|
+
message: `${greeting} aboard!
|
|
301
302
|
|
|
302
303
|
I'm Ian, the founder and CEO of **${Manager.config.brand.name}**, and I'm thrilled to have you with us. Your journey begins today, and we are committed to supporting you every step of the way.
|
|
303
304
|
|
|
@@ -322,9 +323,10 @@ Thank you for choosing **${Manager.config.brand.name}**. Here's to new beginning
|
|
|
322
323
|
/**
|
|
323
324
|
* Send checkup email (7 days after signup)
|
|
324
325
|
*/
|
|
325
|
-
function sendCheckupEmail(assistant, uid) {
|
|
326
|
+
function sendCheckupEmail(assistant, uid, firstName) {
|
|
326
327
|
const Manager = assistant.Manager;
|
|
327
328
|
const mailer = Manager.Email(assistant);
|
|
329
|
+
const greeting = firstName ? `Hey ${firstName}` : 'Hi there';
|
|
328
330
|
|
|
329
331
|
return mailer.send({
|
|
330
332
|
to: uid,
|
|
@@ -340,7 +342,7 @@ function sendCheckupEmail(assistant, uid) {
|
|
|
340
342
|
},
|
|
341
343
|
body: {
|
|
342
344
|
title: `How's everything going?`,
|
|
343
|
-
message:
|
|
345
|
+
message: `${greeting},
|
|
344
346
|
|
|
345
347
|
It's Ian again from **${Manager.config.brand.name}**. Just checking in to see how things are going for you.
|
|
346
348
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema for GET /marketing/campaign
|
|
3
|
+
*/
|
|
4
|
+
module.exports = () => ({
|
|
5
|
+
id: { types: ['string'], default: '' },
|
|
6
|
+
start: { types: ['string', 'number'], default: '' },
|
|
7
|
+
end: { types: ['string', 'number'], default: '' },
|
|
8
|
+
status: { types: ['string'], default: '' },
|
|
9
|
+
type: { types: ['string'], default: '' },
|
|
10
|
+
limit: { types: ['string', 'number'], default: 100 },
|
|
11
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema for POST /marketing/campaign
|
|
3
|
+
*/
|
|
4
|
+
module.exports = () => ({
|
|
5
|
+
// Identity
|
|
6
|
+
id: { types: ['string'], default: '' },
|
|
7
|
+
type: { types: ['string'], default: 'email' },
|
|
8
|
+
|
|
9
|
+
// Content
|
|
10
|
+
name: { types: ['string'], default: undefined, required: true },
|
|
11
|
+
subject: { types: ['string'], default: undefined, required: true },
|
|
12
|
+
preheader: { types: ['string'], default: '' },
|
|
13
|
+
template: { types: ['string'], default: 'default' },
|
|
14
|
+
content: { types: ['string'], default: '' },
|
|
15
|
+
data: { types: ['object'], default: {} },
|
|
16
|
+
|
|
17
|
+
// Targeting
|
|
18
|
+
lists: { types: ['array'], default: [] },
|
|
19
|
+
segments: { types: ['array'], default: [] },
|
|
20
|
+
excludeSegments: { types: ['array'], default: [] },
|
|
21
|
+
all: { types: ['boolean'], default: false },
|
|
22
|
+
|
|
23
|
+
// Scheduling
|
|
24
|
+
sendAt: { types: ['string', 'number'], default: '' },
|
|
25
|
+
recurrence: { types: ['object'], default: undefined }, // { pattern: 'weekly'|'monthly'|'quarterly'|'yearly'|'daily', hour?, day?, month? }
|
|
26
|
+
|
|
27
|
+
// UTM
|
|
28
|
+
utm: { types: ['object'], default: {} },
|
|
29
|
+
|
|
30
|
+
// Config
|
|
31
|
+
sender: { types: ['string'], default: 'marketing' },
|
|
32
|
+
providers: { types: ['array'], default: [] },
|
|
33
|
+
group: { types: ['string'], default: '' },
|
|
34
|
+
categories: { types: ['array'], default: [] },
|
|
35
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema for PUT /marketing/campaign
|
|
3
|
+
* All fields optional except id — only provided fields are updated.
|
|
4
|
+
*/
|
|
5
|
+
module.exports = () => ({
|
|
6
|
+
id: { types: ['string'], default: undefined, required: true },
|
|
7
|
+
type: { types: ['string'], default: '' },
|
|
8
|
+
|
|
9
|
+
// Content
|
|
10
|
+
name: { types: ['string'], default: '' },
|
|
11
|
+
subject: { types: ['string'], default: '' },
|
|
12
|
+
preheader: { types: ['string'], default: '' },
|
|
13
|
+
template: { types: ['string'], default: '' },
|
|
14
|
+
content: { types: ['string'], default: '' },
|
|
15
|
+
data: { types: ['object'], default: undefined },
|
|
16
|
+
|
|
17
|
+
// Targeting
|
|
18
|
+
lists: { types: ['array'], default: undefined },
|
|
19
|
+
segments: { types: ['array'], default: undefined },
|
|
20
|
+
excludeSegments: { types: ['array'], default: undefined },
|
|
21
|
+
all: { types: ['boolean'], default: undefined },
|
|
22
|
+
|
|
23
|
+
// Scheduling
|
|
24
|
+
sendAt: { types: ['string', 'number'], default: '' },
|
|
25
|
+
recurrence: { types: ['object'], default: undefined },
|
|
26
|
+
|
|
27
|
+
// UTM
|
|
28
|
+
utm: { types: ['object'], default: undefined },
|
|
29
|
+
|
|
30
|
+
// Config
|
|
31
|
+
sender: { types: ['string'], default: '' },
|
|
32
|
+
providers: { types: ['array'], default: undefined },
|
|
33
|
+
group: { types: ['string'], default: '' },
|
|
34
|
+
categories: { types: ['array'], default: undefined },
|
|
35
|
+
});
|