@kb0912/notification-brevo 1.0.16 → 2.0.0
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/.medusa/server/src/admin/index.js +1004 -3
- package/.medusa/server/src/admin/index.mjs +1004 -3
- package/.medusa/server/src/api/admin/brevo-plugin-settings/analytics/route.js +78 -0
- package/.medusa/server/src/api/admin/brevo-plugin-settings/route.js +27 -0
- package/.medusa/server/src/api/hooks/brevo-webhook/route.js +40 -0
- package/.medusa/server/src/jobs/abandoned-cart.js +5 -11
- package/.medusa/server/src/jobs/promotion-expiry-reminder.js +122 -0
- package/.medusa/server/src/jobs/review-request.js +106 -0
- package/.medusa/server/src/jobs/winback.js +108 -0
- package/.medusa/server/src/modules/brevo-settings/index.js +13 -0
- package/.medusa/server/src/modules/brevo-settings/migrations/Migration20260306173214.js +15 -0
- package/.medusa/server/src/modules/brevo-settings/migrations/Migration20260307045432.js +14 -0
- package/.medusa/server/src/modules/brevo-settings/migrations/Migration20260307075811.js +14 -0
- package/.medusa/server/src/modules/brevo-settings/models/brevo-settings.js +79 -0
- package/.medusa/server/src/modules/brevo-settings/service.js +114 -0
- package/.medusa/server/src/providers/notifications-brevo/services.js +321 -176
- package/.medusa/server/src/workflows/index.js +25 -0
- package/.medusa/server/src/workflows/send-abandoned-cart.js +37 -8
- package/.medusa/server/src/workflows/send-customer-created.js +159 -4
- package/.medusa/server/src/workflows/send-order-canceled.js +27 -3
- package/.medusa/server/src/workflows/send-order-confirmation.js +63 -10
- package/.medusa/server/src/workflows/send-order-delivered.js +52 -0
- package/.medusa/server/src/workflows/send-review-request.js +37 -0
- package/.medusa/server/src/workflows/send-shipment-confirmation.js +33 -10
- package/.medusa/server/src/workflows/send-winback.js +34 -0
- package/.medusa/server/src/workflows/steps/check-abandoned-carts.js +184 -58
- package/.medusa/server/src/workflows/steps/resolve-locale.js +25 -0
- package/.medusa/server/src/workflows/steps/send-multi-channel.js +81 -0
- package/.medusa/server/src/workflows/steps/send-notification.js +1 -1
- package/.medusa/server/src/workflows/steps/sync-brevo-contact.js +57 -0
- package/.medusa/server/src/workflows/steps/track-brevo-event.js +47 -0
- package/README.md +315 -60
- package/package.json +21 -23
- package/.medusa/server/src/api/admin/plugin/route.js +0 -7
- package/.medusa/server/src/config.js +0 -12
- package/.medusa/server/src/loaders/brevo.js +0 -13
- package/.medusa/server/src/subscribers/password-reset.js +0 -18
- package/.medusa/server/src/subscribers/promotion-new-customer-created.js +0 -21
- package/.medusa/server/src/workflows/send-password-reset.js +0 -30
- package/.medusa/server/src/workflows/send-promotion-notification.js +0 -24
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GET = GET;
|
|
4
|
+
const BREVO_API = "https://api.brevo.com/v3";
|
|
5
|
+
// All event types that can be tagged
|
|
6
|
+
const EVENT_TAGS = [
|
|
7
|
+
"order.placed",
|
|
8
|
+
"order.canceled",
|
|
9
|
+
"order.delivered",
|
|
10
|
+
"customer.created",
|
|
11
|
+
"promotion-new-customer",
|
|
12
|
+
"promotion-expiry-reminder",
|
|
13
|
+
"shipment.confirmed",
|
|
14
|
+
"cart.abandoned",
|
|
15
|
+
"cart.abandoned.discount",
|
|
16
|
+
"review.request",
|
|
17
|
+
"winback",
|
|
18
|
+
];
|
|
19
|
+
async function fetchBrevoStats(apiKey, startDate, endDate, tag) {
|
|
20
|
+
const params = new URLSearchParams({ startDate, endDate });
|
|
21
|
+
if (tag)
|
|
22
|
+
params.set("tag", tag);
|
|
23
|
+
const res = await fetch(`${BREVO_API}/smtp/statistics/aggregatedReport?${params}`, {
|
|
24
|
+
headers: { "api-key": apiKey, "Accept": "application/json" },
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok)
|
|
27
|
+
return null;
|
|
28
|
+
return res.json();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Analytics API — returns overall + per-event email statistics from Brevo.
|
|
32
|
+
*/
|
|
33
|
+
async function GET(req, res) {
|
|
34
|
+
const logger = req.scope.resolve("logger");
|
|
35
|
+
try {
|
|
36
|
+
const apiKey = process.env.BREVO_API_KEY;
|
|
37
|
+
if (!apiKey) {
|
|
38
|
+
return res.status(400).json({ error: "BREVO_API_KEY not configured" });
|
|
39
|
+
}
|
|
40
|
+
const endDate = new Date().toISOString().split("T")[0];
|
|
41
|
+
const startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split("T")[0];
|
|
42
|
+
// Fetch overall stats
|
|
43
|
+
const overall = await fetchBrevoStats(apiKey, startDate, endDate);
|
|
44
|
+
const parseStats = (data) => ({
|
|
45
|
+
delivered: data?.requests || 0,
|
|
46
|
+
opened: data?.uniqueOpens || data?.opens || 0,
|
|
47
|
+
clicked: data?.uniqueClicks || data?.clicks || 0,
|
|
48
|
+
bounced: (data?.hardBounces || 0) + (data?.softBounces || 0),
|
|
49
|
+
blocked: data?.blocked || 0,
|
|
50
|
+
unsubscribed: data?.unsubscribed || 0,
|
|
51
|
+
invalid: data?.invalid || 0,
|
|
52
|
+
});
|
|
53
|
+
const stats = overall ? parseStats(overall) : {
|
|
54
|
+
delivered: 0, opened: 0, clicked: 0, bounced: 0, blocked: 0, unsubscribed: 0, invalid: 0,
|
|
55
|
+
};
|
|
56
|
+
// Fetch per-event stats in parallel
|
|
57
|
+
const perEventResults = await Promise.allSettled(EVENT_TAGS.map(async (tag) => {
|
|
58
|
+
const data = await fetchBrevoStats(apiKey, startDate, endDate, tag);
|
|
59
|
+
return { tag, stats: data ? parseStats(data) : null };
|
|
60
|
+
}));
|
|
61
|
+
const perEvent = {};
|
|
62
|
+
for (const result of perEventResults) {
|
|
63
|
+
if (result.status === "fulfilled" && result.value.stats) {
|
|
64
|
+
const { tag, stats: tagStats } = result.value;
|
|
65
|
+
// Only include events that have activity
|
|
66
|
+
if (tagStats.delivered > 0 || tagStats.opened > 0) {
|
|
67
|
+
perEvent[tag] = tagStats;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
res.json({ stats, perEvent, startDate, endDate });
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
logger.error(`[Brevo] Analytics error: ${error.message}`);
|
|
75
|
+
res.status(500).json({ error: "Failed to fetch analytics" });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL2JyZXZvLXBsdWdpbi1zZXR0aW5ncy9hbmFseXRpY3Mvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFrQ0Esa0JBd0RDO0FBeEZELE1BQU0sU0FBUyxHQUFHLDBCQUEwQixDQUFBO0FBRTVDLHFDQUFxQztBQUNyQyxNQUFNLFVBQVUsR0FBRztJQUNmLGNBQWM7SUFDZCxnQkFBZ0I7SUFDaEIsaUJBQWlCO0lBQ2pCLGtCQUFrQjtJQUNsQix3QkFBd0I7SUFDeEIsMkJBQTJCO0lBQzNCLG9CQUFvQjtJQUNwQixnQkFBZ0I7SUFDaEIseUJBQXlCO0lBQ3pCLGdCQUFnQjtJQUNoQixTQUFTO0NBQ1osQ0FBQTtBQUVELEtBQUssVUFBVSxlQUFlLENBQUMsTUFBYyxFQUFFLFNBQWlCLEVBQUUsT0FBZSxFQUFFLEdBQVk7SUFDM0YsTUFBTSxNQUFNLEdBQUcsSUFBSSxlQUFlLENBQUMsRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLENBQUMsQ0FBQTtJQUMxRCxJQUFJLEdBQUc7UUFBRSxNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxHQUFHLENBQUMsQ0FBQTtJQUUvQixNQUFNLEdBQUcsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLFNBQVMscUNBQXFDLE1BQU0sRUFBRSxFQUFFO1FBQy9FLE9BQU8sRUFBRSxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLGtCQUFrQixFQUFFO0tBQy9ELENBQUMsQ0FBQTtJQUVGLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRTtRQUFFLE9BQU8sSUFBSSxDQUFBO0lBQ3hCLE9BQU8sR0FBRyxDQUFDLElBQUksRUFBa0IsQ0FBQTtBQUNyQyxDQUFDO0FBRUQ7O0dBRUc7QUFDSSxLQUFLLFVBQVUsR0FBRyxDQUNyQixHQUFrQixFQUNsQixHQUFtQjtJQUVuQixNQUFNLE1B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
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GET = GET;
|
|
4
|
+
exports.POST = POST;
|
|
5
|
+
const brevo_settings_1 = require("../../../modules/brevo-settings");
|
|
6
|
+
async function GET(req, res) {
|
|
7
|
+
const brevoSettingsService = req.scope.resolve(brevo_settings_1.BREVO_SETTINGS_MODULE);
|
|
8
|
+
try {
|
|
9
|
+
const settings = await brevoSettingsService.getSettings();
|
|
10
|
+
res.json({ settings });
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
res.status(500).json({ error: "Failed to fetch Brevo settings" });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function POST(req, res) {
|
|
17
|
+
const brevoSettingsService = req.scope.resolve(brevo_settings_1.BREVO_SETTINGS_MODULE);
|
|
18
|
+
try {
|
|
19
|
+
const settings = await brevoSettingsService.upsertSettings(req.body);
|
|
20
|
+
res.json({ settings });
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error("[Brevo] Settings save error:", error?.message || error);
|
|
24
|
+
res.status(500).json({ error: error?.message || "Failed to update Brevo settings" });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL2JyZXZvLXBsdWdpbi1zZXR0aW5ncy9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUlBLGtCQWNDO0FBRUQsb0JBZUM7QUFqQ0Qsb0VBQXVFO0FBRWhFLEtBQUssVUFBVSxHQUFHLENBQ3ZCLEdBQWtCLEVBQ2xCLEdBQW1CO0lBRW5CLE1BQU0sb0JBQW9CLEdBQStCLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUN4RSxzQ0FBcUIsQ0FDdEIsQ0FBQTtJQUVELElBQUksQ0FBQztRQUNILE1BQU0sUUFBUSxHQUFHLE1BQU0sb0JBQW9CLENBQUMsV0FBVyxFQUFFLENBQUE7UUFDekQsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUE7SUFDeEIsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLEtBQUssRUFBRSxnQ0FBZ0MsRUFBRSxDQUFDLENBQUE7SUFDbkUsQ0FBQztBQUNILENBQUM7QUFFTSxLQUFLLFVBQVUsSUFBSSxDQUN4QixHQUFrQixFQUNsQixHQUFtQjtJQUVuQixNQUFNLG9CQUFvQixHQUErQixHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FDeEUsc0NBQXFCLENBQ3RCLENBQUE7SUFFRCxJQUFJLENBQUM7UUFDSCxNQUFNLFFBQVEsR0FBRyxNQUFNLG9CQUFvQixDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsSUFBMkIsQ0FBQyxDQUFBO1FBQzNGLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFBO0lBQ3hCLENBQUM7SUFBQyxPQUFPLEtBQVUsRUFBRSxDQUFDO1FBQ3BCLE9BQU8sQ0FBQyxLQUFLLENBQUMsOEJBQThCLEVBQUUsS0FBSyxFQUFFLE9BQU8sSUFBSSxLQUFLLENBQUMsQ0FBQTtRQUN0RSxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsT0FBTyxJQUFJLGlDQUFpQyxFQUFFLENBQUMsQ0FBQTtJQUN0RixDQUFDO0FBQ0gsQ0FBQyJ9
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.POST = POST;
|
|
4
|
+
const brevo_settings_1 = require("../../../modules/brevo-settings");
|
|
5
|
+
/**
|
|
6
|
+
* Brevo webhook endpoint for email event tracking.
|
|
7
|
+
* This is a PUBLIC route (no admin auth required) — Brevo sends POSTs here.
|
|
8
|
+
*
|
|
9
|
+
* Configure in Brevo: Settings > Webhooks > Add webhook URL:
|
|
10
|
+
* https://your-domain.com/hooks/brevo-webhook
|
|
11
|
+
*/
|
|
12
|
+
async function POST(req, res) {
|
|
13
|
+
const logger = req.scope.resolve("logger");
|
|
14
|
+
try {
|
|
15
|
+
const brevoSettingsService = req.scope.resolve(brevo_settings_1.BREVO_SETTINGS_MODULE);
|
|
16
|
+
const settings = await brevoSettingsService.getSettings();
|
|
17
|
+
if (!settings?.webhook_enabled) {
|
|
18
|
+
return res.status(200).json({ message: "Webhook disabled" });
|
|
19
|
+
}
|
|
20
|
+
// Optionally verify webhook secret
|
|
21
|
+
if (settings.webhook_secret) {
|
|
22
|
+
const signature = req.headers["x-brevo-signature"] || req.headers["x-mailin-custom"];
|
|
23
|
+
if (signature !== settings.webhook_secret) {
|
|
24
|
+
return res.status(401).json({ error: "Invalid webhook signature" });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const payload = req.body;
|
|
28
|
+
const event = payload?.event || payload?.["event-type"] || "unknown";
|
|
29
|
+
const email = payload?.email || payload?.to || "";
|
|
30
|
+
const messageId = payload?.["message-id"] || payload?.messageId || "";
|
|
31
|
+
const tag = payload?.tag || "";
|
|
32
|
+
logger.info(`[Brevo Webhook] event=${event} | email=${email} | messageId=${messageId} | tag=${tag}`);
|
|
33
|
+
res.status(200).json({ received: true, event });
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger.error(`[Brevo Webhook] Error: ${error.message}`);
|
|
37
|
+
res.status(500).json({ error: "Webhook processing failed" });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2hvb2tzL2JyZXZvLXdlYmhvb2svcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFVQSxvQkFxQ0M7QUE5Q0Qsb0VBQXVFO0FBRXZFOzs7Ozs7R0FNRztBQUNJLEtBQUssVUFBVSxJQUFJLENBQ3RCLEdBQWtCLEVBQ2xCLEdBQW1CO0lBRW5CLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFBO0lBRTFDLElBQUksQ0FBQztRQUNELE1BQU0sb0JBQW9CLEdBQVEsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsc0NBQXFCLENBQUMsQ0FBQTtRQUMxRSxNQUFNLFFBQVEsR0FBRyxNQUFNLG9CQUFvQixDQUFDLFdBQVcsRUFBRSxDQUFBO1FBRXpELElBQUksQ0FBQyxRQUFRLEVBQUUsZUFBZSxFQUFFLENBQUM7WUFDN0IsT0FBTyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxDQUFDLENBQUE7UUFDaEUsQ0FBQztRQUVELG1DQUFtQztRQUNuQyxJQUFJLFFBQVEsQ0FBQyxjQUFjLEVBQUUsQ0FBQztZQUMxQixNQUFNLFNBQVMsR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxDQUFBO1lBQ3BGLElBQUksU0FBUyxLQUFLLFFBQVEsQ0FBQyxjQUFjLEVBQUUsQ0FBQztnQkFDeEMsT0FBTyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLEtBQUssRUFBRSwyQkFBMkIsRUFBRSxDQUFDLENBQUE7WUFDdkUsQ0FBQztRQUNMLENBQUM7UUFFRCxNQUFNLE9BQU8sR0FBRyxHQUFHLENBQUMsSUFBMkIsQ0FBQTtRQUMvQyxNQUFNLEtBQUssR0FBRyxPQUFPLEVBQUUsS0FBSyxJQUFJLE9BQU8sRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLFNBQVMsQ0FBQTtRQUNwRSxNQUFNLEtBQUssR0FBRyxPQUFPLEVBQUUsS0FBSyxJQUFJLE9BQU8sRUFBRSxFQUFFLElBQUksRUFBRSxDQUFBO1FBQ2pELE1BQU0sU0FBUyxHQUFHLE9BQU8sRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLE9BQU8sRUFBRSxTQUFTLElBQUksRUFBRSxDQUFBO1FBQ3JFLE1BQU0sR0FBRyxHQUFHLE9BQU8sRUFBRSxHQUFHLElBQUksRUFBRSxDQUFBO1FBRTlCLE1BQU0sQ0FBQyxJQUFJLENBQ1AseUJBQXlCLEtBQUssWUFBWSxLQUFLLGdCQUFnQixTQUFTLFVBQVUsR0FBRyxFQUFFLENBQzFGLENBQUE7UUFFRCxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQTtJQUNuRCxDQUFDO0lBQUMsT0FBTyxLQUFVLEVBQUUsQ0FBQztRQUNsQixNQUFNLENBQUMsS0FBSyxDQUFDLDBCQUEwQixLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQTtRQUN2RCxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLEtBQUssRUFBRSwyQkFBMkIsRUFBRSxDQUFDLENBQUE7SUFDaEUsQ0FBQztBQUNMLENBQUMifQ==
|
|
@@ -6,21 +6,15 @@ const check_abandoned_carts_1 = require("../workflows/steps/check-abandoned-cart
|
|
|
6
6
|
async function checkAbandonedCartsJob(container) {
|
|
7
7
|
const logger = container.resolve("logger");
|
|
8
8
|
try {
|
|
9
|
-
await (0, check_abandoned_carts_1.checkAbandonedCartsWorkflow)(container).run({
|
|
10
|
-
input: {
|
|
11
|
-
container,
|
|
12
|
-
},
|
|
13
|
-
});
|
|
9
|
+
await (0, check_abandoned_carts_1.checkAbandonedCartsWorkflow)(container).run({});
|
|
14
10
|
}
|
|
15
11
|
catch (error) {
|
|
16
|
-
logger.error(
|
|
12
|
+
logger.error(`[Brevo] Abandoned cart job error: ${error.message}`);
|
|
17
13
|
}
|
|
18
14
|
}
|
|
19
|
-
const oneMin = 60 * 1000;
|
|
20
15
|
exports.config = {
|
|
21
16
|
name: "check-abandoned-carts",
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
},
|
|
17
|
+
// Cron expression: run every 15 minutes
|
|
18
|
+
schedule: "*/15 * * * *",
|
|
25
19
|
};
|
|
26
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
20
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWJhbmRvbmVkLWNhcnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvam9icy9hYmFuZG9uZWQtY2FydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFHQSx5Q0FRQztBQVZELG9GQUFzRjtBQUV2RSxLQUFLLFVBQVUsc0JBQXNCLENBQUMsU0FBMEI7SUFDN0UsTUFBTSxNQUFNLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQTtJQUUxQyxJQUFJLENBQUM7UUFDSCxNQUFNLElBQUEsbURBQTJCLEVBQUMsU0FBUyxDQUFDLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFBO0lBQ3RELENBQUM7SUFBQyxPQUFPLEtBQVUsRUFBRSxDQUFDO1FBQ3BCLE1BQU0sQ0FBQyxLQUFLLENBQUMscUNBQXFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFBO0lBQ3BFLENBQUM7QUFDSCxDQUFDO0FBRVksUUFBQSxNQUFNLEdBQUc7SUFDcEIsSUFBSSxFQUFFLHVCQUF1QjtJQUM3Qix3Q0FBd0M7SUFDeEMsUUFBUSxFQUFFLGNBQWM7Q0FDekIsQ0FBQSJ9
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.config = void 0;
|
|
4
|
+
exports.default = promotionExpiryReminder;
|
|
5
|
+
const brevo_settings_1 = require("../modules/brevo-settings");
|
|
6
|
+
const BATCH_SIZE = 100;
|
|
7
|
+
/**
|
|
8
|
+
* Scheduled Job: Check for discount codes about to expire.
|
|
9
|
+
*
|
|
10
|
+
* Strategy: Query promotions with campaigns ending within the reminder window,
|
|
11
|
+
* then find the associated customer via the promotion's customer_id rule.
|
|
12
|
+
* Uses pagination to avoid loading all promotions at once.
|
|
13
|
+
*
|
|
14
|
+
* Schedule: Runs once daily at 9 AM.
|
|
15
|
+
*/
|
|
16
|
+
async function promotionExpiryReminder(container) {
|
|
17
|
+
const logger = container.resolve("logger");
|
|
18
|
+
let settings;
|
|
19
|
+
try {
|
|
20
|
+
const brevoSettingsService = container.resolve(brevo_settings_1.BREVO_SETTINGS_MODULE);
|
|
21
|
+
settings = await brevoSettingsService.getSettings();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!settings?.promotion_expiry_reminder_enabled || !settings?.promotion_expiry_reminder_template_id) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const daysBefore = settings.promotion_expiry_reminder_days_before || 3;
|
|
30
|
+
const codePrefix = (settings.promotion_code_prefix || "WELCOME").toUpperCase();
|
|
31
|
+
logger.info(`[Brevo] Checking promotion expiry reminders (${daysBefore} days before, prefix=${codePrefix})`);
|
|
32
|
+
const now = new Date();
|
|
33
|
+
const reminderWindowStart = now;
|
|
34
|
+
const reminderWindowEnd = new Date(now.getTime() + daysBefore * 24 * 60 * 60 * 1000);
|
|
35
|
+
let sentCount = 0;
|
|
36
|
+
let offset = 0;
|
|
37
|
+
try {
|
|
38
|
+
const promotionService = container.resolve("promotion");
|
|
39
|
+
const customerService = container.resolve("customer");
|
|
40
|
+
const notificationService = container.resolve("notification");
|
|
41
|
+
while (true) {
|
|
42
|
+
const promotions = await promotionService.listPromotions({ status: "active" }, {
|
|
43
|
+
relations: ["campaign", "rules", "rules.values"],
|
|
44
|
+
skip: offset,
|
|
45
|
+
take: BATCH_SIZE,
|
|
46
|
+
});
|
|
47
|
+
if (!promotions.length)
|
|
48
|
+
break;
|
|
49
|
+
for (const promo of promotions) {
|
|
50
|
+
// Only process promotions with our prefix
|
|
51
|
+
if (!promo.code?.startsWith(codePrefix + "-"))
|
|
52
|
+
continue;
|
|
53
|
+
// Check campaign end date is in the reminder window
|
|
54
|
+
const campaign = promo.campaign;
|
|
55
|
+
if (!campaign?.ends_at)
|
|
56
|
+
continue;
|
|
57
|
+
const endsAt = new Date(campaign.ends_at);
|
|
58
|
+
if (endsAt <= reminderWindowStart)
|
|
59
|
+
continue; // Already expired
|
|
60
|
+
if (endsAt > reminderWindowEnd)
|
|
61
|
+
continue; // Not yet in reminder window
|
|
62
|
+
// Find customer_id from promotion rules
|
|
63
|
+
const customerRule = promo.rules?.find((r) => r.attribute === "customer_id" && r.operator === "eq");
|
|
64
|
+
if (!customerRule?.values?.length)
|
|
65
|
+
continue;
|
|
66
|
+
const customerId = customerRule.values[0].value;
|
|
67
|
+
// Check if reminder was already sent (stored in customer metadata)
|
|
68
|
+
let customer;
|
|
69
|
+
try {
|
|
70
|
+
customer = await customerService.retrieveCustomer(customerId);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
continue; // Customer may have been deleted
|
|
74
|
+
}
|
|
75
|
+
const metadata = (customer?.metadata || {});
|
|
76
|
+
const reminderKey = `promo_reminder_${promo.code}`;
|
|
77
|
+
if (metadata[reminderKey])
|
|
78
|
+
continue; // Already sent
|
|
79
|
+
const daysLeft = Math.ceil((endsAt.getTime() - now.getTime()) / (24 * 60 * 60 * 1000));
|
|
80
|
+
logger.info(`[Brevo] Sending expiry reminder to ${customer.email} — code ${promo.code} expires in ${daysLeft} day(s)`);
|
|
81
|
+
try {
|
|
82
|
+
await notificationService.createNotifications({
|
|
83
|
+
to: customer.email,
|
|
84
|
+
channel: "email",
|
|
85
|
+
template: "promotion-expiry-reminder",
|
|
86
|
+
data: {
|
|
87
|
+
first_name: customer.first_name,
|
|
88
|
+
last_name: customer.last_name,
|
|
89
|
+
promotion_code: promo.code,
|
|
90
|
+
expires_at: endsAt.toISOString(),
|
|
91
|
+
days_left: daysLeft,
|
|
92
|
+
_settings: settings,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
// Mark reminder as sent in customer metadata
|
|
96
|
+
await customerService.updateCustomers(customerId, {
|
|
97
|
+
metadata: {
|
|
98
|
+
...metadata,
|
|
99
|
+
[reminderKey]: true,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
sentCount++;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
logger.warn(`[Brevo] Expiry reminder failed for ${customer.email}: ${error?.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
offset += BATCH_SIZE;
|
|
109
|
+
if (promotions.length < BATCH_SIZE)
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
logger.info(`[Brevo] Expiry reminder check complete. Sent ${sentCount} reminders.`);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.error(`[Brevo] Expiry reminder job error: ${error?.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.config = {
|
|
119
|
+
name: "promotion-expiry-reminder",
|
|
120
|
+
schedule: "0 9 * * *", // 9 AM daily
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvbW90aW9uLWV4cGlyeS1yZW1pbmRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9qb2JzL3Byb21vdGlvbi1leHBpcnktcmVtaW5kZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBY0EsMENBb0hDO0FBaklELDhEQUFpRTtBQUVqRSxNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUE7QUFFdEI7Ozs7Ozs7O0dBUUc7QUFDWSxLQUFLLFVBQVUsdUJBQXVCLENBQUMsU0FBMEI7SUFDNUUsTUFBTSxNQUFNLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQTtJQUUxQyxJQUFJLFFBQWEsQ0FBQTtJQUNqQixJQUFJLENBQUM7UUFDRCxNQUFNLG9CQUFvQixHQUFRLFNBQVMsQ0FBQyxPQUFPLENBQUMsc0NBQXFCLENBQUMsQ0FBQTtRQUMxRSxRQUFRLEdBQUcsTUFBTSxvQkFBb0IsQ0FBQyxXQUFXLEVBQUUsQ0FBQTtJQUN2RCxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ0wsT0FBTTtJQUNWLENBQUM7SUFFRCxJQUFJLENBQUMsUUFBUSxFQUFFLGlDQUFpQyxJQUFJLENBQUMsUUFBUSxFQUFFLHFDQUFxQyxFQUFFLENBQUM7UUFDbkcsT0FBTTtJQUNWLENBQUM7SUFFRCxNQUFNLFVBQVUsR0FBRyxRQUFRLENBQUMscUNBQXFDLElBQUksQ0FBQyxDQUFBO0lBQ3RFLE1BQU0sVUFBVSxHQUFHLENBQUMsUUFBUSxDQUFDLHFCQUFxQixJQUFJLFNBQVMsQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFBO0lBRTlFLE1BQU0sQ0FBQyxJQUFJLENBQUMsZ0RBQWdELFVBQVUsd0JBQXdCLFVBQVUsR0FBRyxDQUFDLENBQUE7SUFFNUcsTUFBTSxHQUFHLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQTtJQUN0QixNQUFNLG1CQUFtQixHQUFHLEdBQUcsQ0FBQTtJQUMvQixNQUFNLGlCQUFpQixHQUFHLElBQUksSUFB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
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.config = void 0;
|
|
4
|
+
exports.default = reviewRequestJob;
|
|
5
|
+
const brevo_settings_1 = require("../modules/brevo-settings");
|
|
6
|
+
const send_review_request_1 = require("../workflows/send-review-request");
|
|
7
|
+
const BATCH_SIZE = 50;
|
|
8
|
+
/**
|
|
9
|
+
* Scheduled job: Send review request emails for COMPLETED orders
|
|
10
|
+
* (no returns/refunds) after the configured number of days.
|
|
11
|
+
*
|
|
12
|
+
* Guard: Uses metadata flag `review_request_sent` as primary dedup.
|
|
13
|
+
* Window: Scans orders completed in the last 30 days (wide) to
|
|
14
|
+
* catch any missed orders if the job was down. The metadata flag
|
|
15
|
+
* prevents duplicate sends.
|
|
16
|
+
*/
|
|
17
|
+
async function reviewRequestJob(container) {
|
|
18
|
+
const logger = container.resolve("logger");
|
|
19
|
+
const query = container.resolve("query");
|
|
20
|
+
let settings;
|
|
21
|
+
try {
|
|
22
|
+
const svc = container.resolve(brevo_settings_1.BREVO_SETTINGS_MODULE);
|
|
23
|
+
settings = await svc.getSettings();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!settings?.review_request_enabled)
|
|
29
|
+
return;
|
|
30
|
+
const daysAfter = settings.review_request_days_after || 7;
|
|
31
|
+
// Wide window: target date to 30 days ago — flag prevents duplicates
|
|
32
|
+
const targetDate = new Date(Date.now() - daysAfter * 24 * 60 * 60 * 1000);
|
|
33
|
+
const windowStart = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
|
34
|
+
let sent = 0;
|
|
35
|
+
let offset = 0;
|
|
36
|
+
try {
|
|
37
|
+
while (true) {
|
|
38
|
+
const { data: orders } = await query.graph({
|
|
39
|
+
entity: "order",
|
|
40
|
+
fields: [
|
|
41
|
+
"id", "email", "display_id", "created_at", "status", "metadata",
|
|
42
|
+
"shipping_address.first_name", "shipping_address.last_name",
|
|
43
|
+
"items.product_title", "items.thumbnail",
|
|
44
|
+
"returns.id",
|
|
45
|
+
"customer.metadata",
|
|
46
|
+
],
|
|
47
|
+
filters: {
|
|
48
|
+
created_at: { $gte: windowStart.toISOString(), $lte: targetDate.toISOString() },
|
|
49
|
+
status: "completed",
|
|
50
|
+
},
|
|
51
|
+
pagination: {
|
|
52
|
+
skip: offset,
|
|
53
|
+
take: BATCH_SIZE,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
if (!orders.length)
|
|
57
|
+
break;
|
|
58
|
+
for (const order of orders) {
|
|
59
|
+
if (!order.email)
|
|
60
|
+
continue;
|
|
61
|
+
// Skip orders that have been returned/refunded
|
|
62
|
+
const returns = order.returns || [];
|
|
63
|
+
if (returns.length > 0)
|
|
64
|
+
continue;
|
|
65
|
+
// Primary guard: skip if already sent
|
|
66
|
+
const meta = order.metadata || {};
|
|
67
|
+
if (meta.review_request_sent)
|
|
68
|
+
continue;
|
|
69
|
+
try {
|
|
70
|
+
await (0, send_review_request_1.sendReviewRequestWorkflow)(container).run({
|
|
71
|
+
input: {
|
|
72
|
+
email: order.email,
|
|
73
|
+
orderId: order.id,
|
|
74
|
+
displayId: String(order.display_id),
|
|
75
|
+
customerName: [order.shipping_address?.first_name, order.shipping_address?.last_name].filter(Boolean).join(" "),
|
|
76
|
+
items: order.items || [],
|
|
77
|
+
locale: order.customer?.metadata?.preferred_locale || null,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
const orderService = container.resolve("order");
|
|
81
|
+
await orderService.updateOrders(order.id, {
|
|
82
|
+
metadata: { ...meta, review_request_sent: true },
|
|
83
|
+
});
|
|
84
|
+
sent++;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
logger.error(`[Brevo] Review request failed for order ${order.id}: ${err.message}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
offset += BATCH_SIZE;
|
|
91
|
+
if (orders.length < BATCH_SIZE)
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
if (sent > 0) {
|
|
95
|
+
logger.info(`[Brevo] Sent ${sent} review request emails`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
logger.error(`[Brevo] Review request job error: ${err.message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.config = {
|
|
103
|
+
name: "review-request",
|
|
104
|
+
schedule: "0 10 * * *",
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV2aWV3LXJlcXVlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvam9icy9yZXZpZXctcmVxdWVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFlQSxtQ0F3RkM7QUF0R0QsOERBQWlFO0FBQ2pFLDBFQUE0RTtBQUU1RSxNQUFNLFVBQVUsR0FBRyxFQUFFLENBQUE7QUFFckI7Ozs7Ozs7O0dBUUc7QUFDWSxLQUFLLFVBQVUsZ0JBQWdCLENBQUMsU0FBMEI7SUFDckUsTUFBTSxNQUFNLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQTtJQUMxQyxNQUFNLEtBQUssR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFBO0lBRXhDLElBQUksUUFBYSxDQUFBO0lBQ2pCLElBQUksQ0FBQztRQUNELE1BQU0sR0FBRyxHQUFRLFNBQVMsQ0FBQyxPQUFPLENBQUMsc0NBQXFCLENBQUMsQ0FBQTtRQUN6RCxRQUFRLEdBQUcsTUFBTSxHQUFHLENBQUMsV0FBVyxFQUFFLENBQUE7SUFDdEMsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNMLE9BQU07SUFDVixDQUFDO0lBRUQsSUFBSSxDQUFDLFFBQVEsRUFBRSxzQkFBc0I7UUFBRSxPQUFNO0lBRTdDLE1BQU0sU0FBUyxHQUFHLFFBQVEsQ0FBQyx5QkFBeUIsSUFBSSxDQUFDLENBQUE7SUFDekQscUVBQXFFO0lBQ3JFLE1BQU0sVUFBVSxHQUFHLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxTQUFTLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUE7SUFDekUsTUFBTSxXQUFXLEdBQUcsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQTtJQUVuRSxJ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
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.config = void 0;
|
|
4
|
+
exports.default = winbackJob;
|
|
5
|
+
const brevo_settings_1 = require("../modules/brevo-settings");
|
|
6
|
+
const send_winback_1 = require("../workflows/send-winback");
|
|
7
|
+
const BATCH_SIZE = 100;
|
|
8
|
+
/**
|
|
9
|
+
* Scheduled job: Find customers whose LAST COMPLETED order was X+ days ago
|
|
10
|
+
* and send a win-back email. Uses pagination to avoid fetching all customers
|
|
11
|
+
* at once. Tracks `winback_sent_at` in customer metadata to prevent duplicates.
|
|
12
|
+
*/
|
|
13
|
+
async function winbackJob(container) {
|
|
14
|
+
const logger = container.resolve("logger");
|
|
15
|
+
const query = container.resolve("query");
|
|
16
|
+
let settings;
|
|
17
|
+
try {
|
|
18
|
+
const svc = container.resolve(brevo_settings_1.BREVO_SETTINGS_MODULE);
|
|
19
|
+
settings = await svc.getSettings();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!settings?.winback_enabled)
|
|
25
|
+
return;
|
|
26
|
+
const daysInactive = settings.winback_days_inactive || 30;
|
|
27
|
+
const cutoffDate = new Date(Date.now() - daysInactive * 24 * 60 * 60 * 1000);
|
|
28
|
+
let sent = 0;
|
|
29
|
+
let offset = 0;
|
|
30
|
+
try {
|
|
31
|
+
while (true) {
|
|
32
|
+
const { data: customers } = await query.graph({
|
|
33
|
+
entity: "customer",
|
|
34
|
+
fields: [
|
|
35
|
+
"id", "email", "first_name", "last_name", "metadata",
|
|
36
|
+
"orders.created_at", "orders.status",
|
|
37
|
+
],
|
|
38
|
+
filters: {
|
|
39
|
+
has_account: true,
|
|
40
|
+
},
|
|
41
|
+
pagination: {
|
|
42
|
+
skip: offset,
|
|
43
|
+
take: BATCH_SIZE,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
if (!customers.length)
|
|
47
|
+
break;
|
|
48
|
+
for (const customer of customers) {
|
|
49
|
+
if (!customer.email)
|
|
50
|
+
continue;
|
|
51
|
+
const meta = customer.metadata || {};
|
|
52
|
+
// Skip if win-back already sent recently
|
|
53
|
+
if (meta.winback_sent_at) {
|
|
54
|
+
const lastSent = new Date(meta.winback_sent_at);
|
|
55
|
+
const daysSinceSent = (Date.now() - lastSent.getTime()) / (24 * 60 * 60 * 1000);
|
|
56
|
+
if (daysSinceSent < daysInactive)
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Only count COMPLETED orders (ignore canceled, pending, etc.)
|
|
60
|
+
const allOrders = customer.orders || [];
|
|
61
|
+
const completedOrders = allOrders.filter((o) => o.status === "completed");
|
|
62
|
+
// Skip customers with no completed orders
|
|
63
|
+
if (!completedOrders.length)
|
|
64
|
+
continue;
|
|
65
|
+
// Find the date of the most recent completed order
|
|
66
|
+
const lastCompletedDate = completedOrders
|
|
67
|
+
.map((o) => new Date(o.created_at).getTime())
|
|
68
|
+
.reduce((max, t) => Math.max(max, t), 0);
|
|
69
|
+
// Skip if customer has a recent completed order (still active)
|
|
70
|
+
if (lastCompletedDate > cutoffDate.getTime())
|
|
71
|
+
continue;
|
|
72
|
+
try {
|
|
73
|
+
await (0, send_winback_1.sendWinbackWorkflow)(container).run({
|
|
74
|
+
input: {
|
|
75
|
+
email: customer.email,
|
|
76
|
+
customerName: [customer.first_name, customer.last_name].filter(Boolean).join(" "),
|
|
77
|
+
lastOrderDate: new Date(lastCompletedDate).toLocaleDateString(),
|
|
78
|
+
daysInactive,
|
|
79
|
+
locale: meta?.preferred_locale || null,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
const customerService = container.resolve("customer");
|
|
83
|
+
await customerService.updateCustomers(customer.id, {
|
|
84
|
+
metadata: { ...meta, winback_sent_at: new Date().toISOString() },
|
|
85
|
+
});
|
|
86
|
+
sent++;
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
logger.error(`[Brevo] Win-back failed for ${customer.email}: ${err.message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
offset += BATCH_SIZE;
|
|
93
|
+
if (customers.length < BATCH_SIZE)
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
if (sent > 0) {
|
|
97
|
+
logger.info(`[Brevo] Sent ${sent} win-back emails`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
logger.error(`[Brevo] Win-back job error: ${err.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.config = {
|
|
105
|
+
name: "winback-campaign",
|
|
106
|
+
schedule: "0 11 * * *",
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2luYmFjay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9qb2JzL3dpbmJhY2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBV0EsNkJBa0dDO0FBNUdELDhEQUFpRTtBQUNqRSw0REFBK0Q7QUFFL0QsTUFBTSxVQUFVLEdBQUcsR0FBRyxDQUFBO0FBRXRCOzs7O0dBSUc7QUFDWSxLQUFLLFVBQVUsVUFBVSxDQUFDLFNBQTBCO0lBQy9ELE1BQU0sTUFBTSxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUE7SUFDMUMsTUFBTSxLQUFLLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQTtJQUV4QyxJQUFJLFFBQWEsQ0FBQTtJQUNqQixJQUFJLENBQUM7UUFDRCxNQUFNLEdBQUcsR0FBUSxTQUFTLENBQUMsT0FBTyxDQUFDLHNDQUFxQixDQUFDLENBQUE7UUFDekQsUUFBUSxHQUFHLE1BQU0sR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFBO0lBQ3RDLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDTCxPQUFNO0lBQ1YsQ0FBQztJQUVELElBQUksQ0FBQyxRQUFRLEVBQUUsZUFBZTtRQUFFLE9BQU07SUFFdEMsTUFBTSxZQUFZLEdBQUcsUUFBUSxDQUFDLHFCQUFxQixJQUFJLEVBQUUsQ0FBQTtJQUN6RCxNQUFNLFVBQVUsR0FBRyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsWUFBWSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFBO0lBRTVFLElBQUksSUFBSSxHQUFHLENBQUMsQ0FBQTtJQUNaLElBQUksTUFBTSxHQUFHLENBQUMsQ0FBQTtJQUVkLElBQUksQ0FBQztRQUNELE9BQU8sSUFBSSxFQUFFLENBQUM7WUFDVixNQUFNLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxHQUFHLE1BQU0sS0FBSyxDQUFDLEtB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
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BREVO_SETTINGS_MODULE = void 0;
|
|
7
|
+
const service_1 = __importDefault(require("./service"));
|
|
8
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
9
|
+
exports.BREVO_SETTINGS_MODULE = "brevo_settings";
|
|
10
|
+
exports.default = (0, utils_1.Module)(exports.BREVO_SETTINGS_MODULE, {
|
|
11
|
+
service: service_1.default,
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9icmV2by1zZXR0aW5ncy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQSx3REFBa0Q7QUFDbEQscURBQWtEO0FBRXJDLFFBQUEscUJBQXFCLEdBQUcsZ0JBQWdCLENBQUE7QUFFckQsa0JBQWUsSUFBQSxjQUFNLEVBQUMsNkJBQXFCLEVBQUU7SUFDekMsT0FBTyxFQUFFLGlCQUEwQjtDQUN0QyxDQUFDLENBQUEifQ==
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Migration20260306173214 = void 0;
|
|
4
|
+
const migrations_1 = require("@medusajs/framework/mikro-orm/migrations");
|
|
5
|
+
class Migration20260306173214 extends migrations_1.Migration {
|
|
6
|
+
async up() {
|
|
7
|
+
this.addSql(`create table if not exists "brevo_settings" ("id" text not null, "order_placed_template_id" text null, "order_canceled_template_id" text null, "order_delivered_template_id" text null, "customer_created_template_id" text null, "promotion_new_customer_template_id" text null, "shipment_confirmed_template_id" text null, "abandoned_cart_template_id" text null, "review_request_template_id" text null, "winback_template_id" text null, "sender_name" text null, "order_placed_enabled" boolean not null default true, "order_canceled_enabled" boolean not null default true, "order_delivered_enabled" boolean not null default true, "customer_created_enabled" boolean not null default true, "promotion_enabled" boolean not null default true, "shipment_confirmed_enabled" boolean not null default true, "abandoned_cart_enabled" boolean not null default false, "abandoned_cart_intervals" jsonb null, "abandoned_cart_max_emails" integer not null default 3, "abandoned_cart_discount_enabled" boolean not null default false, "abandoned_cart_discount_type" text not null default 'percentage', "abandoned_cart_discount_value" integer not null default 10, "abandoned_cart_discount_max_uses" integer not null default 1, "abandoned_cart_discount_expires_hours" integer not null default 48, "abandoned_cart_discount_template_id" text null, "contact_sync_enabled" boolean not null default false, "contact_sync_list_id" integer null, "event_tracking_enabled" boolean not null default false, "review_request_enabled" boolean not null default false, "review_request_days_after" integer not null default 7, "winback_enabled" boolean not null default false, "winback_days_inactive" integer not null default 30, "multilang_enabled" boolean not null default false, "multilang_templates" jsonb null, "webhook_enabled" boolean not null default false, "webhook_secret" text null, "whatsapp_enabled" boolean not null default false, "whatsapp_order_placed_template" text null, "whatsapp_shipment_template" text null, "whatsapp_abandoned_cart_template" text null, "sms_enabled" boolean not null default false, "sms_sender" text null, "sms_order_placed_content" text null, "sms_shipment_content" text null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "brevo_settings_pkey" primary key ("id"));`);
|
|
8
|
+
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_brevo_settings_deleted_at" ON "brevo_settings" ("deleted_at") WHERE deleted_at IS NULL;`);
|
|
9
|
+
}
|
|
10
|
+
async down() {
|
|
11
|
+
this.addSql(`drop table if exists "brevo_settings" cascade;`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.Migration20260306173214 = Migration20260306173214;
|
|
15
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNjAzMDYxNzMyMTQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9icmV2by1zZXR0aW5ncy9taWdyYXRpb25zL01pZ3JhdGlvbjIwMjYwMzA2MTczMjE0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHlFQUFxRTtBQUVyRSxNQUFhLHVCQUF3QixTQUFRLHNCQUFTO0lBRTNDLEtBQUssQ0FBQyxFQUFFO1FBQ2YsSUFBSSxDQUFDLE1BQU0sQ0FBQyxtekVBQW16RSxDQUFDLENBQUM7UUFDajBFLElBQUksQ0FBQyxNQUFNLENBQUMseUhBQXlILENBQUMsQ0FBQztJQUN6SSxDQUFDO0lBRVEsS0FBSyxDQUFDLElBQUk7UUFDakIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxnREFBZ0QsQ0FBQyxDQUFDO0lBQ2hFLENBQUM7Q0FFRjtBQVhELDBEQVdDIn0=
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Migration20260307045432 = void 0;
|
|
4
|
+
const migrations_1 = require("@medusajs/framework/mikro-orm/migrations");
|
|
5
|
+
class Migration20260307045432 extends migrations_1.Migration {
|
|
6
|
+
async up() {
|
|
7
|
+
this.addSql(`alter table if exists "brevo_settings" add column if not exists "promotion_auto_create" boolean not null default false, add column if not exists "promotion_discount_type" text not null default 'percentage', add column if not exists "promotion_discount_value" integer not null default 10, add column if not exists "promotion_expiry_days" integer not null default 30, add column if not exists "promotion_expiry_reminder_enabled" boolean not null default false, add column if not exists "promotion_expiry_reminder_template_id" text null, add column if not exists "promotion_expiry_reminder_days_before" integer not null default 3;`);
|
|
8
|
+
}
|
|
9
|
+
async down() {
|
|
10
|
+
this.addSql(`alter table if exists "brevo_settings" drop column if exists "promotion_auto_create", drop column if exists "promotion_discount_type", drop column if exists "promotion_discount_value", drop column if exists "promotion_expiry_days", drop column if exists "promotion_expiry_reminder_enabled", drop column if exists "promotion_expiry_reminder_template_id", drop column if exists "promotion_expiry_reminder_days_before";`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.Migration20260307045432 = Migration20260307045432;
|
|
14
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNjAzMDcwNDU0MzIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9icmV2by1zZXR0aW5ncy9taWdyYXRpb25zL01pZ3JhdGlvbjIwMjYwMzA3MDQ1NDMyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHlFQUFxRTtBQUVyRSxNQUFhLHVCQUF3QixTQUFRLHNCQUFTO0lBRTNDLEtBQUssQ0FBQyxFQUFFO1FBQ2YsSUFBSSxDQUFDLE1BQU0sQ0FBQyxxbkJBQXFuQixDQUFDLENBQUM7SUFDcm9CLENBQUM7SUFFUSxLQUFLLENBQUMsSUFBSTtRQUNqQixJQUFJLENBQUMsTUFBTSxDQUFDLGthQUFrYSxDQUFDLENBQUM7SUFDbGIsQ0FBQztDQUVGO0FBVkQsMERBVUMifQ==
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Migration20260307075811 = void 0;
|
|
4
|
+
const migrations_1 = require("@medusajs/framework/mikro-orm/migrations");
|
|
5
|
+
class Migration20260307075811 extends migrations_1.Migration {
|
|
6
|
+
async up() {
|
|
7
|
+
this.addSql(`alter table if exists "brevo_settings" add column if not exists "promotion_excluded_currencies" jsonb null, add column if not exists "promotion_code_prefix" text not null default 'WELCOME', add column if not exists "abandoned_cart_discount_excluded_currencies" jsonb null, add column if not exists "abandoned_cart_discount_code_prefix" text not null default 'COMEBACK';`);
|
|
8
|
+
}
|
|
9
|
+
async down() {
|
|
10
|
+
this.addSql(`alter table if exists "brevo_settings" drop column if exists "promotion_excluded_currencies", drop column if exists "promotion_code_prefix", drop column if exists "abandoned_cart_discount_excluded_currencies", drop column if exists "abandoned_cart_discount_code_prefix";`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.Migration20260307075811 = Migration20260307075811;
|
|
14
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNjAzMDcwNzU4MTEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9icmV2by1zZXR0aW5ncy9taWdyYXRpb25zL01pZ3JhdGlvbjIwMjYwMzA3MDc1ODExLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHlFQUFxRTtBQUVyRSxNQUFhLHVCQUF3QixTQUFRLHNCQUFTO0lBRTNDLEtBQUssQ0FBQyxFQUFFO1FBQ2YsSUFBSSxDQUFDLE1BQU0sQ0FBQyxtWEFBbVgsQ0FBQyxDQUFDO0lBQ25ZLENBQUM7SUFFUSxLQUFLLENBQUMsSUFBSTtRQUNqQixJQUFJLENBQUMsTUFBTSxDQUFDLGdSQUFnUixDQUFDLENBQUM7SUFDaFMsQ0FBQztDQUVGO0FBVkQsMERBVUMifQ==
|