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 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: false,
2023
- error: "This email has been unsubscribed. Please contact support to resubscribe."
2024
- }, { status: 400 });
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
- return Response.json({
2027
- success: false,
2028
- error: "Already subscribed",
2029
- subscriber: {
2030
- id: subscriber2.id,
2031
- email: subscriber2.email,
2032
- subscriptionStatus: subscriber2.subscriptionStatus
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
- }, { status: 400 });
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({