payload-plugin-newsletter 0.13.2 → 0.14.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/CHANGELOG.md +33 -0
- package/dist/index.cjs +144 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +144 -17
- package/dist/index.js.map +1 -1
- package/dist/types.d.cts +10 -0
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
## [0.14.0] - 2025-07-22
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- New authentication configuration options for flexible subscriber management
|
|
5
|
+
- `auth.allowUnsubscribedSignin`: Allow unsubscribed users to sign in
|
|
6
|
+
- `auth.allowResubscribe`: Allow unsubscribed users to resubscribe
|
|
7
|
+
- Improved subscribe endpoint behavior:
|
|
8
|
+
- Already subscribed users now receive a sign-in link instead of an error
|
|
9
|
+
- Unsubscribed users can resubscribe (when enabled)
|
|
10
|
+
- Enhanced response formats with status indicators:
|
|
11
|
+
- `requiresSubscribe`: Indicates user needs to subscribe first
|
|
12
|
+
- `requiresResubscribe`: Indicates user needs to resubscribe
|
|
13
|
+
- `wasResubscribed`: Indicates user was successfully resubscribed
|
|
14
|
+
- `alreadySubscribed`: Indicates user is already subscribed
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Sign-in endpoint now returns more detailed error information for better UX
|
|
18
|
+
- Subscribe endpoint handles existing subscribers more gracefully
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- Unsubscribed users can now manage their preferences when `allowUnsubscribedSignin` is enabled
|
|
22
|
+
|
|
23
|
+
## [0.13.3] - 2025-07-21
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- Support for custom redirect URLs after successful subscriber sign-in
|
|
27
|
+
- Added `redirectUrl` parameter to signin endpoint
|
|
28
|
+
- Magic link URLs now include the redirect parameter to maintain context after authentication
|
|
29
|
+
- Enables seamless user experience when accessing protected content
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- Magic link generation now properly includes redirect URL parameter
|
|
33
|
+
|
|
1
34
|
## [0.13.2] - 2025-07-21
|
|
2
35
|
|
|
3
36
|
### Fixed
|
package/dist/index.cjs
CHANGED
|
@@ -1278,6 +1278,44 @@ var createNewsletterSettingsGlobal = (pluginConfig) => {
|
|
|
1278
1278
|
}
|
|
1279
1279
|
]
|
|
1280
1280
|
},
|
|
1281
|
+
{
|
|
1282
|
+
label: "Brand Settings",
|
|
1283
|
+
fields: [
|
|
1284
|
+
{
|
|
1285
|
+
name: "brandSettings",
|
|
1286
|
+
type: "group",
|
|
1287
|
+
label: "Brand Settings",
|
|
1288
|
+
fields: [
|
|
1289
|
+
{
|
|
1290
|
+
name: "siteName",
|
|
1291
|
+
type: "text",
|
|
1292
|
+
label: "Site Name",
|
|
1293
|
+
required: true,
|
|
1294
|
+
defaultValue: "Newsletter",
|
|
1295
|
+
admin: {
|
|
1296
|
+
description: "Your website or newsletter name"
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
name: "siteUrl",
|
|
1301
|
+
type: "text",
|
|
1302
|
+
label: "Site URL",
|
|
1303
|
+
admin: {
|
|
1304
|
+
description: "Your website URL (optional)"
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
name: "logoUrl",
|
|
1309
|
+
type: "text",
|
|
1310
|
+
label: "Logo URL",
|
|
1311
|
+
admin: {
|
|
1312
|
+
description: "URL to your logo image (optional)"
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
]
|
|
1316
|
+
}
|
|
1317
|
+
]
|
|
1318
|
+
},
|
|
1281
1319
|
{
|
|
1282
1320
|
label: "Email Templates",
|
|
1283
1321
|
fields: [
|
|
@@ -1961,10 +1999,13 @@ function verifySessionToken(token) {
|
|
|
1961
1999
|
throw error;
|
|
1962
2000
|
}
|
|
1963
2001
|
}
|
|
1964
|
-
function generateMagicLinkURL(token, baseURL, config) {
|
|
2002
|
+
function generateMagicLinkURL(token, baseURL, config, redirectUrl) {
|
|
1965
2003
|
const path = config.auth?.magicLinkPath || "/newsletter/verify";
|
|
1966
2004
|
const url = new URL(path, baseURL);
|
|
1967
2005
|
url.searchParams.set("token", token);
|
|
2006
|
+
if (redirectUrl) {
|
|
2007
|
+
url.searchParams.set("redirect", redirectUrl);
|
|
2008
|
+
}
|
|
1968
2009
|
return url.toString();
|
|
1969
2010
|
}
|
|
1970
2011
|
|
|
@@ -2018,20 +2059,93 @@ var createSubscribeEndpoint = (config) => {
|
|
|
2018
2059
|
if (existing.docs.length > 0) {
|
|
2019
2060
|
const subscriber2 = existing.docs[0];
|
|
2020
2061
|
if (subscriber2.subscriptionStatus === "unsubscribed") {
|
|
2062
|
+
const allowResubscribe = config.auth?.allowResubscribe ?? false;
|
|
2063
|
+
if (!allowResubscribe) {
|
|
2064
|
+
return Response.json({
|
|
2065
|
+
success: false,
|
|
2066
|
+
error: "This email has been unsubscribed. Please contact support to resubscribe."
|
|
2067
|
+
}, { status: 400 });
|
|
2068
|
+
}
|
|
2069
|
+
const updated = await req.payload.update({
|
|
2070
|
+
collection: config.subscribersSlug || "subscribers",
|
|
2071
|
+
id: subscriber2.id,
|
|
2072
|
+
data: {
|
|
2073
|
+
subscriptionStatus: "active",
|
|
2074
|
+
resubscribedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2075
|
+
// Preserve preferences but update metadata
|
|
2076
|
+
signupMetadata: {
|
|
2077
|
+
...metadata,
|
|
2078
|
+
source: source || "resubscribe",
|
|
2079
|
+
resubscribedFrom: subscriber2.signupMetadata?.source
|
|
2080
|
+
}
|
|
2081
|
+
},
|
|
2082
|
+
overrideAccess: true
|
|
2083
|
+
});
|
|
2084
|
+
if (config.hooks?.afterSubscribe) {
|
|
2085
|
+
await config.hooks.afterSubscribe({
|
|
2086
|
+
doc: updated,
|
|
2087
|
+
req
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
const emailService = req.payload.newsletterEmailService;
|
|
2091
|
+
if (emailService) {
|
|
2092
|
+
const settings2 = await req.payload.findGlobal({
|
|
2093
|
+
slug: config.settingsSlug || "newsletter-settings"
|
|
2094
|
+
});
|
|
2095
|
+
const html = await renderEmail("welcome", {
|
|
2096
|
+
name: updated.name || "",
|
|
2097
|
+
email: updated.email,
|
|
2098
|
+
siteName: settings2?.brandSettings?.siteName || "Newsletter",
|
|
2099
|
+
siteUrl: req.payload.config.serverURL || ""
|
|
2100
|
+
});
|
|
2101
|
+
await emailService.send({
|
|
2102
|
+
to: updated.email,
|
|
2103
|
+
subject: `Welcome back to ${settings2?.brandSettings?.siteName || "our newsletter"}!`,
|
|
2104
|
+
html
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2021
2107
|
return Response.json({
|
|
2022
|
-
success:
|
|
2023
|
-
|
|
2024
|
-
|
|
2108
|
+
success: true,
|
|
2109
|
+
message: "Welcome back! You have been resubscribed.",
|
|
2110
|
+
subscriber: {
|
|
2111
|
+
id: updated.id,
|
|
2112
|
+
email: updated.email,
|
|
2113
|
+
subscriptionStatus: updated.subscriptionStatus
|
|
2114
|
+
},
|
|
2115
|
+
wasResubscribed: true
|
|
2116
|
+
});
|
|
2025
2117
|
}
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2118
|
+
if (subscriber2.subscriptionStatus === "active") {
|
|
2119
|
+
const token = generateMagicLinkToken(
|
|
2120
|
+
String(subscriber2.id),
|
|
2121
|
+
subscriber2.email,
|
|
2122
|
+
config
|
|
2123
|
+
);
|
|
2124
|
+
const serverURL = req.payload.config.serverURL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "";
|
|
2125
|
+
const magicLinkURL = generateMagicLinkURL(token, serverURL, config);
|
|
2126
|
+
const emailService = req.payload.newsletterEmailService;
|
|
2127
|
+
if (emailService) {
|
|
2128
|
+
const settings2 = await req.payload.findGlobal({
|
|
2129
|
+
slug: config.settingsSlug || "newsletter-settings"
|
|
2130
|
+
});
|
|
2131
|
+
const html = await renderEmail("signin", {
|
|
2132
|
+
magicLink: magicLinkURL,
|
|
2133
|
+
email: subscriber2.email,
|
|
2134
|
+
siteName: settings2?.brandSettings?.siteName || "Newsletter",
|
|
2135
|
+
expiresIn: config.auth?.tokenExpiration || "7d"
|
|
2136
|
+
});
|
|
2137
|
+
await emailService.send({
|
|
2138
|
+
to: subscriber2.email,
|
|
2139
|
+
subject: `Sign in to ${settings2?.brandSettings?.siteName || "your account"}`,
|
|
2140
|
+
html
|
|
2141
|
+
});
|
|
2033
2142
|
}
|
|
2034
|
-
|
|
2143
|
+
return Response.json({
|
|
2144
|
+
success: true,
|
|
2145
|
+
message: "You are already subscribed! Check your email for a sign-in link.",
|
|
2146
|
+
alreadySubscribed: true
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2035
2149
|
}
|
|
2036
2150
|
const ipAddress = req.ip || req.connection?.remoteAddress;
|
|
2037
2151
|
const maxPerIP = settings?.subscriptionSettings?.maxSubscribersPerIP || 10;
|
|
@@ -2553,7 +2667,7 @@ var createSigninEndpoint = (config) => {
|
|
|
2553
2667
|
handler: async (req) => {
|
|
2554
2668
|
try {
|
|
2555
2669
|
const data = await req.json();
|
|
2556
|
-
const { email } = data;
|
|
2670
|
+
const { email, redirectUrl } = data;
|
|
2557
2671
|
const validation = validateSubscriberData({ email });
|
|
2558
2672
|
if (!validation.valid) {
|
|
2559
2673
|
return Response.json({
|
|
@@ -2572,8 +2686,7 @@ var createSigninEndpoint = (config) => {
|
|
|
2572
2686
|
const result = await req.payload.find({
|
|
2573
2687
|
collection: config.subscribersSlug || "subscribers",
|
|
2574
2688
|
where: {
|
|
2575
|
-
email: { equals: email.toLowerCase() }
|
|
2576
|
-
subscriptionStatus: { equals: "active" }
|
|
2689
|
+
email: { equals: email.toLowerCase() }
|
|
2577
2690
|
},
|
|
2578
2691
|
limit: 1,
|
|
2579
2692
|
overrideAccess: true
|
|
@@ -2582,17 +2695,31 @@ var createSigninEndpoint = (config) => {
|
|
|
2582
2695
|
if (result.docs.length === 0) {
|
|
2583
2696
|
return Response.json({
|
|
2584
2697
|
success: false,
|
|
2585
|
-
error: "Email not found. Please subscribe first."
|
|
2698
|
+
error: "Email not found. Please subscribe first.",
|
|
2699
|
+
requiresSubscribe: true
|
|
2586
2700
|
}, { status: 404 });
|
|
2587
2701
|
}
|
|
2588
2702
|
const subscriber = result.docs[0];
|
|
2703
|
+
const allowUnsubscribed = config.auth?.allowUnsubscribedSignin ?? false;
|
|
2704
|
+
if (subscriber.subscriptionStatus === "unsubscribed" && !allowUnsubscribed) {
|
|
2705
|
+
return Response.json({
|
|
2706
|
+
success: false,
|
|
2707
|
+
error: "Your subscription is inactive. Please resubscribe to sign in.",
|
|
2708
|
+
subscriber: {
|
|
2709
|
+
id: subscriber.id,
|
|
2710
|
+
email: subscriber.email,
|
|
2711
|
+
subscriptionStatus: subscriber.subscriptionStatus
|
|
2712
|
+
},
|
|
2713
|
+
requiresResubscribe: true
|
|
2714
|
+
}, { status: 403 });
|
|
2715
|
+
}
|
|
2589
2716
|
const token = generateMagicLinkToken(
|
|
2590
2717
|
String(subscriber.id),
|
|
2591
2718
|
subscriber.email,
|
|
2592
2719
|
config
|
|
2593
2720
|
);
|
|
2594
2721
|
const serverURL = req.payload.config.serverURL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "";
|
|
2595
|
-
const magicLinkURL = generateMagicLinkURL(token, serverURL, config);
|
|
2722
|
+
const magicLinkURL = generateMagicLinkURL(token, serverURL, config, redirectUrl);
|
|
2596
2723
|
const emailService = req.payload.newsletterEmailService;
|
|
2597
2724
|
if (emailService) {
|
|
2598
2725
|
const settings = await req.payload.findGlobal({
|