payload-plugin-newsletter 0.13.3 → 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,25 @@
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
+
1
23
  ## [0.13.3] - 2025-07-21
2
24
 
3
25
  ### Added
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: [
@@ -2021,20 +2059,93 @@ var createSubscribeEndpoint = (config) => {
2021
2059
  if (existing.docs.length > 0) {
2022
2060
  const subscriber2 = existing.docs[0];
2023
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
+ }
2024
2107
  return Response.json({
2025
- success: false,
2026
- error: "This email has been unsubscribed. Please contact support to resubscribe."
2027
- }, { 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
+ });
2028
2117
  }
2029
- return Response.json({
2030
- success: false,
2031
- error: "Already subscribed",
2032
- subscriber: {
2033
- id: subscriber2.id,
2034
- email: subscriber2.email,
2035
- 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
+ });
2036
2142
  }
2037
- }, { 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
+ }
2038
2149
  }
2039
2150
  const ipAddress = req.ip || req.connection?.remoteAddress;
2040
2151
  const maxPerIP = settings?.subscriptionSettings?.maxSubscribersPerIP || 10;
@@ -2575,8 +2686,7 @@ var createSigninEndpoint = (config) => {
2575
2686
  const result = await req.payload.find({
2576
2687
  collection: config.subscribersSlug || "subscribers",
2577
2688
  where: {
2578
- email: { equals: email.toLowerCase() },
2579
- subscriptionStatus: { equals: "active" }
2689
+ email: { equals: email.toLowerCase() }
2580
2690
  },
2581
2691
  limit: 1,
2582
2692
  overrideAccess: true
@@ -2585,10 +2695,24 @@ var createSigninEndpoint = (config) => {
2585
2695
  if (result.docs.length === 0) {
2586
2696
  return Response.json({
2587
2697
  success: false,
2588
- error: "Email not found. Please subscribe first."
2698
+ error: "Email not found. Please subscribe first.",
2699
+ requiresSubscribe: true
2589
2700
  }, { status: 404 });
2590
2701
  }
2591
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
+ }
2592
2716
  const token = generateMagicLinkToken(
2593
2717
  String(subscriber.id),
2594
2718
  subscriber.email,