feat(newsletter): migrate to Beehiiv API for subscription management - #1321

Merged
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration
Jan 2, 2026
Merged

feat(newsletter): migrate to Beehiiv API for subscription management#1321
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

And update privacy policy

@NiallJoeMaher
NiallJoeMaher requested a review from a team as a code ownerJanuary 2, 2026 13:59
@vercel

vercelBot commented Jan 2, 2026

Copy link
Copy Markdown

@NiallJoeMaher is attempting to deploy a commit to the Codú Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitaiBot commented Jan 2, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Migrates newsletter handling to Beehiiv: removes client signup UI and server Email API action, adds Next.js redirects to the external newsletter site, updates privacy/terms with newsletter details, and implements Beehiiv-based subscribe/unsubscribe/status checks with error/Sentry handling.

Changes

Cohort / File(s)Summary
Legal documentation
app/(app)/(tsandcs)/privacy/page.mdx, app/(app)/(tsandcs)/tou/page.mdx
Updated Privacy "Last updated" date and added "Newsletter Subscriber Data" subsection; added new Newsletter Terms of Use document and linked it from privacy.
Removed newsletter pages
app/(standalone)/newsletter/page.tsx, app/(standalone)/newsletter/confirmed/page.tsx, app/(standalone)/newsletter/unsubscribed/page.tsx
Deleted main newsletter page, confirmation page, and unsubscribed page along with their metadata and UI components.
Removed client form & server action
app/(standalone)/newsletter/_form.tsx, app/(standalone)/newsletter/actions.ts
Removed client-side SignupForm and the server-side subscribeToNewsletter action (previous Email API/Zod validation flow).
Redirects config
next.config.js
Added async redirects() returning permanent redirects from /newsletter and /newsletter/:path* to https://newsletter.codu.co.
Beehiiv integration (server)
server/lib/newsletter.ts
Replaced Email API logic with Beehiiv API calls: subscribe, unsubscribe, and check-by-email; added isUserSubscribedToNewsletter(email: string): Promise<boolean); added error handling and Sentry logging.

Sequence Diagram(s)

sequenceDiagram
autonumber
participant Client as Client (Browser)
participant Next as Next.js Server
participant Lib as server/lib/newsletter
participant Bee as Beehiiv API
participant Sentry as Sentry
Client->>Next: POST /newsletter/subscribe (email)
Note right of Next: Next.js route/action receives form data
Next->>Lib: call subscribe(email)
Lib->>Bee: POST /v2/subscriptions (email, reactivate_existing, send_welcome_email)
alt 200 OK
Bee-->>Lib: 200 OK
Lib-->>Next: { success }
Next-->>Client: 200 (subscribed)
else error (4xx/5xx)
Bee-->>Lib: error
Lib->>Sentry: capture error
Lib-->>Next: { error }
Next-->>Client: 500/4xx (error)
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped from form to Beehiiv's door,
Bye-bye inputs I tended before.
Redirects guide the curious hare,
Terms and privacy now all fair.
A tiny hop, a bigger buzz—newsletter lore! ✨📬

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe pull request description is largely incomplete and fails to follow the template structure, providing only a minimal two-sentence comment without required sections.Add proper description sections: fill 'Pull Request details' with comprehensive info about the migration, specify any breaking changes, and include 'Associated Screenshots' section even if marked 'None'.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately describes the main change: migrating newsletter subscription management to Beehiiv API, which aligns with the primary code changes across multiple files.

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2662a3d and 57e8819.

📒 Files selected for processing (2)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/(app)/(tsandcs)/privacy/page.mdx (1)

7-7: Consider using HTTPS for internal links.

The link to Terms of Service uses http:// while other links in the codebase use https://. For consistency and security best practices, consider updating to HTTPS:

-This Policy supplements and is governed by our [Terms of Service](http://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](http://www.codu.co/tou).+This Policy supplements and is governed by our [Terms of Service](https://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](https://www.codu.co/tou).
server/lib/newsletter.ts (2)

70-98: Inconsistent error logging between subscribe and unsubscribe paths.

The subscribe path (line 54) logs failures to Sentry before throwing, but the unsubscribe path throws errors without Sentry logging. Consider adding Sentry capture for consistency and better observability:

Suggested improvement
 if (!getResponse.ok) {
if (getResponse.status === 404) {
return { message: "Successfully unsubscribed from the newsletter." };
}
+ const errorData = await getResponse.text();+ Sentry.captureMessage(`Beehiiv get subscription failed: ${errorData}`);
throw new Error("Failed to find subscription");
}
// ...
if (updateResponse.ok) {
return { message: "Successfully unsubscribed from the newsletter." };
} else {
+ const errorData = await updateResponse.text();+ Sentry.captureMessage(`Beehiiv unsubscribe failed: ${errorData}`);
throw new Error("Failed to unsubscribe from the newsletter");
}

34-48: Consider adding request timeouts for external API calls.

The fetch calls to Beehiiv have no timeout configured. If the Beehiiv API becomes slow or unresponsive, these calls could hang indefinitely. While the caller in auth.ts wraps this in try/catch so it won't block user creation, adding timeouts improves resilience:

Example using AbortSignal timeout
constresponse=awaitfetch(`${BEEHIIV_API_BASE}/publications/${BEEHIIV_PUBLICATION_ID}/subscriptions`,{method: "POST",headers: {"Content-Type": "application/json",Authorization: `Bearer ${BEEHIIV_API_KEY}`,},body: JSON.stringify({
email,reactivate_existing: true,send_welcome_email: false,}),signal: AbortSignal.timeout(10000),// 10 second timeout},);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ead81ed and 2662a3d.

📒 Files selected for processing (9)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • next.config.js
  • server/lib/newsletter.ts
💤 Files with no reviewable changes (5)
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
🧰 Additional context used
🧬 Code graph analysis (1)
server/lib/newsletter.ts (2)
app/(standalone)/newsletter/actions.ts (1)
  • subscribeToNewsletter (15-63)
auth.ts (1)
  • createUser (85-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (6)
next.config.js (1)

30-34: Wildcard path is discarded in the redirect destination.

The :path* parameter is captured but not used in the destination URL. All sub-paths like /newsletter/archive or /newsletter/confirmed will redirect to the root https://newsletter.codu.co instead of preserving the path.

If this is intentional (e.g., the external newsletter service handles all routing from its root), this is fine. Otherwise, consider appending the path:

 {
source: "/newsletter/:path*",
- destination: "https://newsletter.codu.co",+ destination: "https://newsletter.codu.co/:path*",
permanent: true,
},
app/(app)/(tsandcs)/tou/page.mdx (1)

1-106: Well-structured Terms of Use document.

The document comprehensively covers essential ToU sections including eligibility, intellectual property, disclaimers, and governing law. The 16-year age requirement aligns with GDPR requirements.

app/(app)/(tsandcs)/privacy/page.mdx (1)

95-106: Good addition documenting newsletter data collection.

The new "Newsletter Subscriber Data" section clearly documents the data collected through Beehiiv, including engagement metrics and geographic location, with appropriate disclosure about the third-party platform and unsubscribe mechanism.

server/lib/newsletter.ts (3)

5-25: Clean type definitions and config helper.

The interfaces properly model the Beehiiv API response structure, and getBeehiivConfig() fails fast with a clear error message when required environment variables are missing.


33-56: Subscribe flow looks solid.

Good use of reactivate_existing: true to handle re-subscription cases, and proper Sentry logging before throwing on failure. The send_welcome_email: false aligns with the existing welcome email flow in auth.ts.


119-126: Verify subscription status handling logic.

The function only returns true for "active" status. Based on the interface, subscriptions can also be "validating" or "pending".

If a user is in "validating" or "pending" state (e.g., double opt-in flow), this will return false. Verify this is the intended behavior for your use case.

Also, consider adding Sentry logging on line 125 for consistency with the subscribe path:

 } else {
+ const errorData = await response.text();+ Sentry.captureMessage(`Beehiiv subscription check failed: ${errorData}`);
throw new Error("Failed to check newsletter subscription");
}

Comment threadapp/(app)/(tsandcs)/privacy/page.mdx Outdated
Comment threadapp/(app)/(tsandcs)/tou/page.mdx Outdated
@NiallJoeMaher
NiallJoeMaher merged commit 1bdefc7 into codu-code:developJan 2, 2026
4 of 5 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/newsletter-migration branch January 2, 2026 14:08
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(newsletter): migrate to Beehiiv API for subscription management - #1321

Merged
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration
Jan 2, 2026
Merged

feat(newsletter): migrate to Beehiiv API for subscription management#1321
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

And update privacy policy

@NiallJoeMaher
NiallJoeMaher requested a review from a team as a code ownerJanuary 2, 2026 13:59
@vercel

vercelBot commented Jan 2, 2026

Copy link
Copy Markdown

@NiallJoeMaher is attempting to deploy a commit to the Codú Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitaiBot commented Jan 2, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Migrates newsletter handling to Beehiiv: removes client signup UI and server Email API action, adds Next.js redirects to the external newsletter site, updates privacy/terms with newsletter details, and implements Beehiiv-based subscribe/unsubscribe/status checks with error/Sentry handling.

Changes

Cohort / File(s)Summary
Legal documentation
app/(app)/(tsandcs)/privacy/page.mdx, app/(app)/(tsandcs)/tou/page.mdx
Updated Privacy "Last updated" date and added "Newsletter Subscriber Data" subsection; added new Newsletter Terms of Use document and linked it from privacy.
Removed newsletter pages
app/(standalone)/newsletter/page.tsx, app/(standalone)/newsletter/confirmed/page.tsx, app/(standalone)/newsletter/unsubscribed/page.tsx
Deleted main newsletter page, confirmation page, and unsubscribed page along with their metadata and UI components.
Removed client form & server action
app/(standalone)/newsletter/_form.tsx, app/(standalone)/newsletter/actions.ts
Removed client-side SignupForm and the server-side subscribeToNewsletter action (previous Email API/Zod validation flow).
Redirects config
next.config.js
Added async redirects() returning permanent redirects from /newsletter and /newsletter/:path* to https://newsletter.codu.co.
Beehiiv integration (server)
server/lib/newsletter.ts
Replaced Email API logic with Beehiiv API calls: subscribe, unsubscribe, and check-by-email; added isUserSubscribedToNewsletter(email: string): Promise<boolean); added error handling and Sentry logging.

Sequence Diagram(s)

sequenceDiagram
autonumber
participant Client as Client (Browser)
participant Next as Next.js Server
participant Lib as server/lib/newsletter
participant Bee as Beehiiv API
participant Sentry as Sentry
Client->>Next: POST /newsletter/subscribe (email)
Note right of Next: Next.js route/action receives form data
Next->>Lib: call subscribe(email)
Lib->>Bee: POST /v2/subscriptions (email, reactivate_existing, send_welcome_email)
alt 200 OK
Bee-->>Lib: 200 OK
Lib-->>Next: { success }
Next-->>Client: 200 (subscribed)
else error (4xx/5xx)
Bee-->>Lib: error
Lib->>Sentry: capture error
Lib-->>Next: { error }
Next-->>Client: 500/4xx (error)
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped from form to Beehiiv's door,
Bye-bye inputs I tended before.
Redirects guide the curious hare,
Terms and privacy now all fair.
A tiny hop, a bigger buzz—newsletter lore! ✨📬

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe pull request description is largely incomplete and fails to follow the template structure, providing only a minimal two-sentence comment without required sections.Add proper description sections: fill 'Pull Request details' with comprehensive info about the migration, specify any breaking changes, and include 'Associated Screenshots' section even if marked 'None'.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately describes the main change: migrating newsletter subscription management to Beehiiv API, which aligns with the primary code changes across multiple files.

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2662a3d and 57e8819.

📒 Files selected for processing (2)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/(app)/(tsandcs)/privacy/page.mdx (1)

7-7: Consider using HTTPS for internal links.

The link to Terms of Service uses http:// while other links in the codebase use https://. For consistency and security best practices, consider updating to HTTPS:

-This Policy supplements and is governed by our [Terms of Service](http://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](http://www.codu.co/tou).+This Policy supplements and is governed by our [Terms of Service](https://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](https://www.codu.co/tou).
server/lib/newsletter.ts (2)

70-98: Inconsistent error logging between subscribe and unsubscribe paths.

The subscribe path (line 54) logs failures to Sentry before throwing, but the unsubscribe path throws errors without Sentry logging. Consider adding Sentry capture for consistency and better observability:

Suggested improvement
 if (!getResponse.ok) {
if (getResponse.status === 404) {
return { message: "Successfully unsubscribed from the newsletter." };
}
+ const errorData = await getResponse.text();+ Sentry.captureMessage(`Beehiiv get subscription failed: ${errorData}`);
throw new Error("Failed to find subscription");
}
// ...
if (updateResponse.ok) {
return { message: "Successfully unsubscribed from the newsletter." };
} else {
+ const errorData = await updateResponse.text();+ Sentry.captureMessage(`Beehiiv unsubscribe failed: ${errorData}`);
throw new Error("Failed to unsubscribe from the newsletter");
}

34-48: Consider adding request timeouts for external API calls.

The fetch calls to Beehiiv have no timeout configured. If the Beehiiv API becomes slow or unresponsive, these calls could hang indefinitely. While the caller in auth.ts wraps this in try/catch so it won't block user creation, adding timeouts improves resilience:

Example using AbortSignal timeout
constresponse=awaitfetch(`${BEEHIIV_API_BASE}/publications/${BEEHIIV_PUBLICATION_ID}/subscriptions`,{method: "POST",headers: {"Content-Type": "application/json",Authorization: `Bearer ${BEEHIIV_API_KEY}`,},body: JSON.stringify({
email,reactivate_existing: true,send_welcome_email: false,}),signal: AbortSignal.timeout(10000),// 10 second timeout},);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ead81ed and 2662a3d.

📒 Files selected for processing (9)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • next.config.js
  • server/lib/newsletter.ts
💤 Files with no reviewable changes (5)
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
🧰 Additional context used
🧬 Code graph analysis (1)
server/lib/newsletter.ts (2)
app/(standalone)/newsletter/actions.ts (1)
  • subscribeToNewsletter (15-63)
auth.ts (1)
  • createUser (85-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (6)
next.config.js (1)

30-34: Wildcard path is discarded in the redirect destination.

The :path* parameter is captured but not used in the destination URL. All sub-paths like /newsletter/archive or /newsletter/confirmed will redirect to the root https://newsletter.codu.co instead of preserving the path.

If this is intentional (e.g., the external newsletter service handles all routing from its root), this is fine. Otherwise, consider appending the path:

 {
source: "/newsletter/:path*",
- destination: "https://newsletter.codu.co",+ destination: "https://newsletter.codu.co/:path*",
permanent: true,
},
app/(app)/(tsandcs)/tou/page.mdx (1)

1-106: Well-structured Terms of Use document.

The document comprehensively covers essential ToU sections including eligibility, intellectual property, disclaimers, and governing law. The 16-year age requirement aligns with GDPR requirements.

app/(app)/(tsandcs)/privacy/page.mdx (1)

95-106: Good addition documenting newsletter data collection.

The new "Newsletter Subscriber Data" section clearly documents the data collected through Beehiiv, including engagement metrics and geographic location, with appropriate disclosure about the third-party platform and unsubscribe mechanism.

server/lib/newsletter.ts (3)

5-25: Clean type definitions and config helper.

The interfaces properly model the Beehiiv API response structure, and getBeehiivConfig() fails fast with a clear error message when required environment variables are missing.


33-56: Subscribe flow looks solid.

Good use of reactivate_existing: true to handle re-subscription cases, and proper Sentry logging before throwing on failure. The send_welcome_email: false aligns with the existing welcome email flow in auth.ts.


119-126: Verify subscription status handling logic.

The function only returns true for "active" status. Based on the interface, subscriptions can also be "validating" or "pending".

If a user is in "validating" or "pending" state (e.g., double opt-in flow), this will return false. Verify this is the intended behavior for your use case.

Also, consider adding Sentry logging on line 125 for consistency with the subscribe path:

 } else {
+ const errorData = await response.text();+ Sentry.captureMessage(`Beehiiv subscription check failed: ${errorData}`);
throw new Error("Failed to check newsletter subscription");
}

Comment threadapp/(app)/(tsandcs)/privacy/page.mdx Outdated
Comment threadapp/(app)/(tsandcs)/tou/page.mdx Outdated
@NiallJoeMaher
NiallJoeMaher merged commit 1bdefc7 into codu-code:developJan 2, 2026
4 of 5 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/newsletter-migration branch January 2, 2026 14:08
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(newsletter): migrate to Beehiiv API for subscription management - #1321

Merged
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration
Jan 2, 2026
Merged

feat(newsletter): migrate to Beehiiv API for subscription management#1321
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

And update privacy policy

@NiallJoeMaher
NiallJoeMaher requested a review from a team as a code ownerJanuary 2, 2026 13:59
@vercel

vercelBot commented Jan 2, 2026

Copy link
Copy Markdown

@NiallJoeMaher is attempting to deploy a commit to the Codú Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitaiBot commented Jan 2, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Migrates newsletter handling to Beehiiv: removes client signup UI and server Email API action, adds Next.js redirects to the external newsletter site, updates privacy/terms with newsletter details, and implements Beehiiv-based subscribe/unsubscribe/status checks with error/Sentry handling.

Changes

Cohort / File(s)Summary
Legal documentation
app/(app)/(tsandcs)/privacy/page.mdx, app/(app)/(tsandcs)/tou/page.mdx
Updated Privacy "Last updated" date and added "Newsletter Subscriber Data" subsection; added new Newsletter Terms of Use document and linked it from privacy.
Removed newsletter pages
app/(standalone)/newsletter/page.tsx, app/(standalone)/newsletter/confirmed/page.tsx, app/(standalone)/newsletter/unsubscribed/page.tsx
Deleted main newsletter page, confirmation page, and unsubscribed page along with their metadata and UI components.
Removed client form & server action
app/(standalone)/newsletter/_form.tsx, app/(standalone)/newsletter/actions.ts
Removed client-side SignupForm and the server-side subscribeToNewsletter action (previous Email API/Zod validation flow).
Redirects config
next.config.js
Added async redirects() returning permanent redirects from /newsletter and /newsletter/:path* to https://newsletter.codu.co.
Beehiiv integration (server)
server/lib/newsletter.ts
Replaced Email API logic with Beehiiv API calls: subscribe, unsubscribe, and check-by-email; added isUserSubscribedToNewsletter(email: string): Promise<boolean); added error handling and Sentry logging.

Sequence Diagram(s)

sequenceDiagram
autonumber
participant Client as Client (Browser)
participant Next as Next.js Server
participant Lib as server/lib/newsletter
participant Bee as Beehiiv API
participant Sentry as Sentry
Client->>Next: POST /newsletter/subscribe (email)
Note right of Next: Next.js route/action receives form data
Next->>Lib: call subscribe(email)
Lib->>Bee: POST /v2/subscriptions (email, reactivate_existing, send_welcome_email)
alt 200 OK
Bee-->>Lib: 200 OK
Lib-->>Next: { success }
Next-->>Client: 200 (subscribed)
else error (4xx/5xx)
Bee-->>Lib: error
Lib->>Sentry: capture error
Lib-->>Next: { error }
Next-->>Client: 500/4xx (error)
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped from form to Beehiiv's door,
Bye-bye inputs I tended before.
Redirects guide the curious hare,
Terms and privacy now all fair.
A tiny hop, a bigger buzz—newsletter lore! ✨📬

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe pull request description is largely incomplete and fails to follow the template structure, providing only a minimal two-sentence comment without required sections.Add proper description sections: fill 'Pull Request details' with comprehensive info about the migration, specify any breaking changes, and include 'Associated Screenshots' section even if marked 'None'.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately describes the main change: migrating newsletter subscription management to Beehiiv API, which aligns with the primary code changes across multiple files.

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2662a3d and 57e8819.

📒 Files selected for processing (2)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/(app)/(tsandcs)/privacy/page.mdx (1)

7-7: Consider using HTTPS for internal links.

The link to Terms of Service uses http:// while other links in the codebase use https://. For consistency and security best practices, consider updating to HTTPS:

-This Policy supplements and is governed by our [Terms of Service](http://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](http://www.codu.co/tou).+This Policy supplements and is governed by our [Terms of Service](https://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](https://www.codu.co/tou).
server/lib/newsletter.ts (2)

70-98: Inconsistent error logging between subscribe and unsubscribe paths.

The subscribe path (line 54) logs failures to Sentry before throwing, but the unsubscribe path throws errors without Sentry logging. Consider adding Sentry capture for consistency and better observability:

Suggested improvement
 if (!getResponse.ok) {
if (getResponse.status === 404) {
return { message: "Successfully unsubscribed from the newsletter." };
}
+ const errorData = await getResponse.text();+ Sentry.captureMessage(`Beehiiv get subscription failed: ${errorData}`);
throw new Error("Failed to find subscription");
}
// ...
if (updateResponse.ok) {
return { message: "Successfully unsubscribed from the newsletter." };
} else {
+ const errorData = await updateResponse.text();+ Sentry.captureMessage(`Beehiiv unsubscribe failed: ${errorData}`);
throw new Error("Failed to unsubscribe from the newsletter");
}

34-48: Consider adding request timeouts for external API calls.

The fetch calls to Beehiiv have no timeout configured. If the Beehiiv API becomes slow or unresponsive, these calls could hang indefinitely. While the caller in auth.ts wraps this in try/catch so it won't block user creation, adding timeouts improves resilience:

Example using AbortSignal timeout
constresponse=awaitfetch(`${BEEHIIV_API_BASE}/publications/${BEEHIIV_PUBLICATION_ID}/subscriptions`,{method: "POST",headers: {"Content-Type": "application/json",Authorization: `Bearer ${BEEHIIV_API_KEY}`,},body: JSON.stringify({
email,reactivate_existing: true,send_welcome_email: false,}),signal: AbortSignal.timeout(10000),// 10 second timeout},);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ead81ed and 2662a3d.

📒 Files selected for processing (9)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • next.config.js
  • server/lib/newsletter.ts
💤 Files with no reviewable changes (5)
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
🧰 Additional context used
🧬 Code graph analysis (1)
server/lib/newsletter.ts (2)
app/(standalone)/newsletter/actions.ts (1)
  • subscribeToNewsletter (15-63)
auth.ts (1)
  • createUser (85-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (6)
next.config.js (1)

30-34: Wildcard path is discarded in the redirect destination.

The :path* parameter is captured but not used in the destination URL. All sub-paths like /newsletter/archive or /newsletter/confirmed will redirect to the root https://newsletter.codu.co instead of preserving the path.

If this is intentional (e.g., the external newsletter service handles all routing from its root), this is fine. Otherwise, consider appending the path:

 {
source: "/newsletter/:path*",
- destination: "https://newsletter.codu.co",+ destination: "https://newsletter.codu.co/:path*",
permanent: true,
},
app/(app)/(tsandcs)/tou/page.mdx (1)

1-106: Well-structured Terms of Use document.

The document comprehensively covers essential ToU sections including eligibility, intellectual property, disclaimers, and governing law. The 16-year age requirement aligns with GDPR requirements.

app/(app)/(tsandcs)/privacy/page.mdx (1)

95-106: Good addition documenting newsletter data collection.

The new "Newsletter Subscriber Data" section clearly documents the data collected through Beehiiv, including engagement metrics and geographic location, with appropriate disclosure about the third-party platform and unsubscribe mechanism.

server/lib/newsletter.ts (3)

5-25: Clean type definitions and config helper.

The interfaces properly model the Beehiiv API response structure, and getBeehiivConfig() fails fast with a clear error message when required environment variables are missing.


33-56: Subscribe flow looks solid.

Good use of reactivate_existing: true to handle re-subscription cases, and proper Sentry logging before throwing on failure. The send_welcome_email: false aligns with the existing welcome email flow in auth.ts.


119-126: Verify subscription status handling logic.

The function only returns true for "active" status. Based on the interface, subscriptions can also be "validating" or "pending".

If a user is in "validating" or "pending" state (e.g., double opt-in flow), this will return false. Verify this is the intended behavior for your use case.

Also, consider adding Sentry logging on line 125 for consistency with the subscribe path:

 } else {
+ const errorData = await response.text();+ Sentry.captureMessage(`Beehiiv subscription check failed: ${errorData}`);
throw new Error("Failed to check newsletter subscription");
}

Comment threadapp/(app)/(tsandcs)/privacy/page.mdx Outdated
Comment threadapp/(app)/(tsandcs)/tou/page.mdx Outdated
@NiallJoeMaher
NiallJoeMaher merged commit 1bdefc7 into codu-code:developJan 2, 2026
4 of 5 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/newsletter-migration branch January 2, 2026 14:08
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(newsletter): migrate to Beehiiv API for subscription management - #1321

Merged
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration
Jan 2, 2026
Merged

feat(newsletter): migrate to Beehiiv API for subscription management#1321
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

And update privacy policy

@NiallJoeMaher
NiallJoeMaher requested a review from a team as a code ownerJanuary 2, 2026 13:59
@vercel

vercelBot commented Jan 2, 2026

Copy link
Copy Markdown

@NiallJoeMaher is attempting to deploy a commit to the Codú Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitaiBot commented Jan 2, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Migrates newsletter handling to Beehiiv: removes client signup UI and server Email API action, adds Next.js redirects to the external newsletter site, updates privacy/terms with newsletter details, and implements Beehiiv-based subscribe/unsubscribe/status checks with error/Sentry handling.

Changes

Cohort / File(s)Summary
Legal documentation
app/(app)/(tsandcs)/privacy/page.mdx, app/(app)/(tsandcs)/tou/page.mdx
Updated Privacy "Last updated" date and added "Newsletter Subscriber Data" subsection; added new Newsletter Terms of Use document and linked it from privacy.
Removed newsletter pages
app/(standalone)/newsletter/page.tsx, app/(standalone)/newsletter/confirmed/page.tsx, app/(standalone)/newsletter/unsubscribed/page.tsx
Deleted main newsletter page, confirmation page, and unsubscribed page along with their metadata and UI components.
Removed client form & server action
app/(standalone)/newsletter/_form.tsx, app/(standalone)/newsletter/actions.ts
Removed client-side SignupForm and the server-side subscribeToNewsletter action (previous Email API/Zod validation flow).
Redirects config
next.config.js
Added async redirects() returning permanent redirects from /newsletter and /newsletter/:path* to https://newsletter.codu.co.
Beehiiv integration (server)
server/lib/newsletter.ts
Replaced Email API logic with Beehiiv API calls: subscribe, unsubscribe, and check-by-email; added isUserSubscribedToNewsletter(email: string): Promise<boolean); added error handling and Sentry logging.

Sequence Diagram(s)

sequenceDiagram
autonumber
participant Client as Client (Browser)
participant Next as Next.js Server
participant Lib as server/lib/newsletter
participant Bee as Beehiiv API
participant Sentry as Sentry
Client->>Next: POST /newsletter/subscribe (email)
Note right of Next: Next.js route/action receives form data
Next->>Lib: call subscribe(email)
Lib->>Bee: POST /v2/subscriptions (email, reactivate_existing, send_welcome_email)
alt 200 OK
Bee-->>Lib: 200 OK
Lib-->>Next: { success }
Next-->>Client: 200 (subscribed)
else error (4xx/5xx)
Bee-->>Lib: error
Lib->>Sentry: capture error
Lib-->>Next: { error }
Next-->>Client: 500/4xx (error)
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped from form to Beehiiv's door,
Bye-bye inputs I tended before.
Redirects guide the curious hare,
Terms and privacy now all fair.
A tiny hop, a bigger buzz—newsletter lore! ✨📬

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe pull request description is largely incomplete and fails to follow the template structure, providing only a minimal two-sentence comment without required sections.Add proper description sections: fill 'Pull Request details' with comprehensive info about the migration, specify any breaking changes, and include 'Associated Screenshots' section even if marked 'None'.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately describes the main change: migrating newsletter subscription management to Beehiiv API, which aligns with the primary code changes across multiple files.

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2662a3d and 57e8819.

📒 Files selected for processing (2)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/(app)/(tsandcs)/privacy/page.mdx (1)

7-7: Consider using HTTPS for internal links.

The link to Terms of Service uses http:// while other links in the codebase use https://. For consistency and security best practices, consider updating to HTTPS:

-This Policy supplements and is governed by our [Terms of Service](http://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](http://www.codu.co/tou).+This Policy supplements and is governed by our [Terms of Service](https://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](https://www.codu.co/tou).
server/lib/newsletter.ts (2)

70-98: Inconsistent error logging between subscribe and unsubscribe paths.

The subscribe path (line 54) logs failures to Sentry before throwing, but the unsubscribe path throws errors without Sentry logging. Consider adding Sentry capture for consistency and better observability:

Suggested improvement
 if (!getResponse.ok) {
if (getResponse.status === 404) {
return { message: "Successfully unsubscribed from the newsletter." };
}
+ const errorData = await getResponse.text();+ Sentry.captureMessage(`Beehiiv get subscription failed: ${errorData}`);
throw new Error("Failed to find subscription");
}
// ...
if (updateResponse.ok) {
return { message: "Successfully unsubscribed from the newsletter." };
} else {
+ const errorData = await updateResponse.text();+ Sentry.captureMessage(`Beehiiv unsubscribe failed: ${errorData}`);
throw new Error("Failed to unsubscribe from the newsletter");
}

34-48: Consider adding request timeouts for external API calls.

The fetch calls to Beehiiv have no timeout configured. If the Beehiiv API becomes slow or unresponsive, these calls could hang indefinitely. While the caller in auth.ts wraps this in try/catch so it won't block user creation, adding timeouts improves resilience:

Example using AbortSignal timeout
constresponse=awaitfetch(`${BEEHIIV_API_BASE}/publications/${BEEHIIV_PUBLICATION_ID}/subscriptions`,{method: "POST",headers: {"Content-Type": "application/json",Authorization: `Bearer ${BEEHIIV_API_KEY}`,},body: JSON.stringify({
email,reactivate_existing: true,send_welcome_email: false,}),signal: AbortSignal.timeout(10000),// 10 second timeout},);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ead81ed and 2662a3d.

📒 Files selected for processing (9)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • next.config.js
  • server/lib/newsletter.ts
💤 Files with no reviewable changes (5)
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
🧰 Additional context used
🧬 Code graph analysis (1)
server/lib/newsletter.ts (2)
app/(standalone)/newsletter/actions.ts (1)
  • subscribeToNewsletter (15-63)
auth.ts (1)
  • createUser (85-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (6)
next.config.js (1)

30-34: Wildcard path is discarded in the redirect destination.

The :path* parameter is captured but not used in the destination URL. All sub-paths like /newsletter/archive or /newsletter/confirmed will redirect to the root https://newsletter.codu.co instead of preserving the path.

If this is intentional (e.g., the external newsletter service handles all routing from its root), this is fine. Otherwise, consider appending the path:

 {
source: "/newsletter/:path*",
- destination: "https://newsletter.codu.co",+ destination: "https://newsletter.codu.co/:path*",
permanent: true,
},
app/(app)/(tsandcs)/tou/page.mdx (1)

1-106: Well-structured Terms of Use document.

The document comprehensively covers essential ToU sections including eligibility, intellectual property, disclaimers, and governing law. The 16-year age requirement aligns with GDPR requirements.

app/(app)/(tsandcs)/privacy/page.mdx (1)

95-106: Good addition documenting newsletter data collection.

The new "Newsletter Subscriber Data" section clearly documents the data collected through Beehiiv, including engagement metrics and geographic location, with appropriate disclosure about the third-party platform and unsubscribe mechanism.

server/lib/newsletter.ts (3)

5-25: Clean type definitions and config helper.

The interfaces properly model the Beehiiv API response structure, and getBeehiivConfig() fails fast with a clear error message when required environment variables are missing.


33-56: Subscribe flow looks solid.

Good use of reactivate_existing: true to handle re-subscription cases, and proper Sentry logging before throwing on failure. The send_welcome_email: false aligns with the existing welcome email flow in auth.ts.


119-126: Verify subscription status handling logic.

The function only returns true for "active" status. Based on the interface, subscriptions can also be "validating" or "pending".

If a user is in "validating" or "pending" state (e.g., double opt-in flow), this will return false. Verify this is the intended behavior for your use case.

Also, consider adding Sentry logging on line 125 for consistency with the subscribe path:

 } else {
+ const errorData = await response.text();+ Sentry.captureMessage(`Beehiiv subscription check failed: ${errorData}`);
throw new Error("Failed to check newsletter subscription");
}

Comment threadapp/(app)/(tsandcs)/privacy/page.mdx Outdated
Comment threadapp/(app)/(tsandcs)/tou/page.mdx Outdated
@NiallJoeMaher
NiallJoeMaher merged commit 1bdefc7 into codu-code:developJan 2, 2026
4 of 5 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/newsletter-migration branch January 2, 2026 14:08
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(newsletter): migrate to Beehiiv API for subscription management - #1321

Merged
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration
Jan 2, 2026
Merged

feat(newsletter): migrate to Beehiiv API for subscription management#1321
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

And update privacy policy

@NiallJoeMaher
NiallJoeMaher requested a review from a team as a code ownerJanuary 2, 2026 13:59
@vercel

vercelBot commented Jan 2, 2026

Copy link
Copy Markdown

@NiallJoeMaher is attempting to deploy a commit to the Codú Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitaiBot commented Jan 2, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Migrates newsletter handling to Beehiiv: removes client signup UI and server Email API action, adds Next.js redirects to the external newsletter site, updates privacy/terms with newsletter details, and implements Beehiiv-based subscribe/unsubscribe/status checks with error/Sentry handling.

Changes

Cohort / File(s)Summary
Legal documentation
app/(app)/(tsandcs)/privacy/page.mdx, app/(app)/(tsandcs)/tou/page.mdx
Updated Privacy "Last updated" date and added "Newsletter Subscriber Data" subsection; added new Newsletter Terms of Use document and linked it from privacy.
Removed newsletter pages
app/(standalone)/newsletter/page.tsx, app/(standalone)/newsletter/confirmed/page.tsx, app/(standalone)/newsletter/unsubscribed/page.tsx
Deleted main newsletter page, confirmation page, and unsubscribed page along with their metadata and UI components.
Removed client form & server action
app/(standalone)/newsletter/_form.tsx, app/(standalone)/newsletter/actions.ts
Removed client-side SignupForm and the server-side subscribeToNewsletter action (previous Email API/Zod validation flow).
Redirects config
next.config.js
Added async redirects() returning permanent redirects from /newsletter and /newsletter/:path* to https://newsletter.codu.co.
Beehiiv integration (server)
server/lib/newsletter.ts
Replaced Email API logic with Beehiiv API calls: subscribe, unsubscribe, and check-by-email; added isUserSubscribedToNewsletter(email: string): Promise<boolean); added error handling and Sentry logging.

Sequence Diagram(s)

sequenceDiagram
autonumber
participant Client as Client (Browser)
participant Next as Next.js Server
participant Lib as server/lib/newsletter
participant Bee as Beehiiv API
participant Sentry as Sentry
Client->>Next: POST /newsletter/subscribe (email)
Note right of Next: Next.js route/action receives form data
Next->>Lib: call subscribe(email)
Lib->>Bee: POST /v2/subscriptions (email, reactivate_existing, send_welcome_email)
alt 200 OK
Bee-->>Lib: 200 OK
Lib-->>Next: { success }
Next-->>Client: 200 (subscribed)
else error (4xx/5xx)
Bee-->>Lib: error
Lib->>Sentry: capture error
Lib-->>Next: { error }
Next-->>Client: 500/4xx (error)
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped from form to Beehiiv's door,
Bye-bye inputs I tended before.
Redirects guide the curious hare,
Terms and privacy now all fair.
A tiny hop, a bigger buzz—newsletter lore! ✨📬

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe pull request description is largely incomplete and fails to follow the template structure, providing only a minimal two-sentence comment without required sections.Add proper description sections: fill 'Pull Request details' with comprehensive info about the migration, specify any breaking changes, and include 'Associated Screenshots' section even if marked 'None'.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately describes the main change: migrating newsletter subscription management to Beehiiv API, which aligns with the primary code changes across multiple files.

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2662a3d and 57e8819.

📒 Files selected for processing (2)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/(app)/(tsandcs)/privacy/page.mdx (1)

7-7: Consider using HTTPS for internal links.

The link to Terms of Service uses http:// while other links in the codebase use https://. For consistency and security best practices, consider updating to HTTPS:

-This Policy supplements and is governed by our [Terms of Service](http://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](http://www.codu.co/tou).+This Policy supplements and is governed by our [Terms of Service](https://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](https://www.codu.co/tou).
server/lib/newsletter.ts (2)

70-98: Inconsistent error logging between subscribe and unsubscribe paths.

The subscribe path (line 54) logs failures to Sentry before throwing, but the unsubscribe path throws errors without Sentry logging. Consider adding Sentry capture for consistency and better observability:

Suggested improvement
 if (!getResponse.ok) {
if (getResponse.status === 404) {
return { message: "Successfully unsubscribed from the newsletter." };
}
+ const errorData = await getResponse.text();+ Sentry.captureMessage(`Beehiiv get subscription failed: ${errorData}`);
throw new Error("Failed to find subscription");
}
// ...
if (updateResponse.ok) {
return { message: "Successfully unsubscribed from the newsletter." };
} else {
+ const errorData = await updateResponse.text();+ Sentry.captureMessage(`Beehiiv unsubscribe failed: ${errorData}`);
throw new Error("Failed to unsubscribe from the newsletter");
}

34-48: Consider adding request timeouts for external API calls.

The fetch calls to Beehiiv have no timeout configured. If the Beehiiv API becomes slow or unresponsive, these calls could hang indefinitely. While the caller in auth.ts wraps this in try/catch so it won't block user creation, adding timeouts improves resilience:

Example using AbortSignal timeout
constresponse=awaitfetch(`${BEEHIIV_API_BASE}/publications/${BEEHIIV_PUBLICATION_ID}/subscriptions`,{method: "POST",headers: {"Content-Type": "application/json",Authorization: `Bearer ${BEEHIIV_API_KEY}`,},body: JSON.stringify({
email,reactivate_existing: true,send_welcome_email: false,}),signal: AbortSignal.timeout(10000),// 10 second timeout},);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ead81ed and 2662a3d.

📒 Files selected for processing (9)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • next.config.js
  • server/lib/newsletter.ts
💤 Files with no reviewable changes (5)
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
🧰 Additional context used
🧬 Code graph analysis (1)
server/lib/newsletter.ts (2)
app/(standalone)/newsletter/actions.ts (1)
  • subscribeToNewsletter (15-63)
auth.ts (1)
  • createUser (85-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (6)
next.config.js (1)

30-34: Wildcard path is discarded in the redirect destination.

The :path* parameter is captured but not used in the destination URL. All sub-paths like /newsletter/archive or /newsletter/confirmed will redirect to the root https://newsletter.codu.co instead of preserving the path.

If this is intentional (e.g., the external newsletter service handles all routing from its root), this is fine. Otherwise, consider appending the path:

 {
source: "/newsletter/:path*",
- destination: "https://newsletter.codu.co",+ destination: "https://newsletter.codu.co/:path*",
permanent: true,
},
app/(app)/(tsandcs)/tou/page.mdx (1)

1-106: Well-structured Terms of Use document.

The document comprehensively covers essential ToU sections including eligibility, intellectual property, disclaimers, and governing law. The 16-year age requirement aligns with GDPR requirements.

app/(app)/(tsandcs)/privacy/page.mdx (1)

95-106: Good addition documenting newsletter data collection.

The new "Newsletter Subscriber Data" section clearly documents the data collected through Beehiiv, including engagement metrics and geographic location, with appropriate disclosure about the third-party platform and unsubscribe mechanism.

server/lib/newsletter.ts (3)

5-25: Clean type definitions and config helper.

The interfaces properly model the Beehiiv API response structure, and getBeehiivConfig() fails fast with a clear error message when required environment variables are missing.


33-56: Subscribe flow looks solid.

Good use of reactivate_existing: true to handle re-subscription cases, and proper Sentry logging before throwing on failure. The send_welcome_email: false aligns with the existing welcome email flow in auth.ts.


119-126: Verify subscription status handling logic.

The function only returns true for "active" status. Based on the interface, subscriptions can also be "validating" or "pending".

If a user is in "validating" or "pending" state (e.g., double opt-in flow), this will return false. Verify this is the intended behavior for your use case.

Also, consider adding Sentry logging on line 125 for consistency with the subscribe path:

 } else {
+ const errorData = await response.text();+ Sentry.captureMessage(`Beehiiv subscription check failed: ${errorData}`);
throw new Error("Failed to check newsletter subscription");
}

Comment threadapp/(app)/(tsandcs)/privacy/page.mdx Outdated
Comment threadapp/(app)/(tsandcs)/tou/page.mdx Outdated
@NiallJoeMaher
NiallJoeMaher merged commit 1bdefc7 into codu-code:developJan 2, 2026
4 of 5 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/newsletter-migration branch January 2, 2026 14:08
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(newsletter): migrate to Beehiiv API for subscription management - #1321

Merged
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration
Jan 2, 2026
Merged

feat(newsletter): migrate to Beehiiv API for subscription management#1321
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

And update privacy policy

@NiallJoeMaher
NiallJoeMaher requested a review from a team as a code ownerJanuary 2, 2026 13:59
@vercel

vercelBot commented Jan 2, 2026

Copy link
Copy Markdown

@NiallJoeMaher is attempting to deploy a commit to the Codú Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitaiBot commented Jan 2, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Migrates newsletter handling to Beehiiv: removes client signup UI and server Email API action, adds Next.js redirects to the external newsletter site, updates privacy/terms with newsletter details, and implements Beehiiv-based subscribe/unsubscribe/status checks with error/Sentry handling.

Changes

Cohort / File(s)Summary
Legal documentation
app/(app)/(tsandcs)/privacy/page.mdx, app/(app)/(tsandcs)/tou/page.mdx
Updated Privacy "Last updated" date and added "Newsletter Subscriber Data" subsection; added new Newsletter Terms of Use document and linked it from privacy.
Removed newsletter pages
app/(standalone)/newsletter/page.tsx, app/(standalone)/newsletter/confirmed/page.tsx, app/(standalone)/newsletter/unsubscribed/page.tsx
Deleted main newsletter page, confirmation page, and unsubscribed page along with their metadata and UI components.
Removed client form & server action
app/(standalone)/newsletter/_form.tsx, app/(standalone)/newsletter/actions.ts
Removed client-side SignupForm and the server-side subscribeToNewsletter action (previous Email API/Zod validation flow).
Redirects config
next.config.js
Added async redirects() returning permanent redirects from /newsletter and /newsletter/:path* to https://newsletter.codu.co.
Beehiiv integration (server)
server/lib/newsletter.ts
Replaced Email API logic with Beehiiv API calls: subscribe, unsubscribe, and check-by-email; added isUserSubscribedToNewsletter(email: string): Promise<boolean); added error handling and Sentry logging.

Sequence Diagram(s)

sequenceDiagram
autonumber
participant Client as Client (Browser)
participant Next as Next.js Server
participant Lib as server/lib/newsletter
participant Bee as Beehiiv API
participant Sentry as Sentry
Client->>Next: POST /newsletter/subscribe (email)
Note right of Next: Next.js route/action receives form data
Next->>Lib: call subscribe(email)
Lib->>Bee: POST /v2/subscriptions (email, reactivate_existing, send_welcome_email)
alt 200 OK
Bee-->>Lib: 200 OK
Lib-->>Next: { success }
Next-->>Client: 200 (subscribed)
else error (4xx/5xx)
Bee-->>Lib: error
Lib->>Sentry: capture error
Lib-->>Next: { error }
Next-->>Client: 500/4xx (error)
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped from form to Beehiiv's door,
Bye-bye inputs I tended before.
Redirects guide the curious hare,
Terms and privacy now all fair.
A tiny hop, a bigger buzz—newsletter lore! ✨📬

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe pull request description is largely incomplete and fails to follow the template structure, providing only a minimal two-sentence comment without required sections.Add proper description sections: fill 'Pull Request details' with comprehensive info about the migration, specify any breaking changes, and include 'Associated Screenshots' section even if marked 'None'.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately describes the main change: migrating newsletter subscription management to Beehiiv API, which aligns with the primary code changes across multiple files.

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2662a3d and 57e8819.

📒 Files selected for processing (2)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/(app)/(tsandcs)/privacy/page.mdx (1)

7-7: Consider using HTTPS for internal links.

The link to Terms of Service uses http:// while other links in the codebase use https://. For consistency and security best practices, consider updating to HTTPS:

-This Policy supplements and is governed by our [Terms of Service](http://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](http://www.codu.co/tou).+This Policy supplements and is governed by our [Terms of Service](https://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](https://www.codu.co/tou).
server/lib/newsletter.ts (2)

70-98: Inconsistent error logging between subscribe and unsubscribe paths.

The subscribe path (line 54) logs failures to Sentry before throwing, but the unsubscribe path throws errors without Sentry logging. Consider adding Sentry capture for consistency and better observability:

Suggested improvement
 if (!getResponse.ok) {
if (getResponse.status === 404) {
return { message: "Successfully unsubscribed from the newsletter." };
}
+ const errorData = await getResponse.text();+ Sentry.captureMessage(`Beehiiv get subscription failed: ${errorData}`);
throw new Error("Failed to find subscription");
}
// ...
if (updateResponse.ok) {
return { message: "Successfully unsubscribed from the newsletter." };
} else {
+ const errorData = await updateResponse.text();+ Sentry.captureMessage(`Beehiiv unsubscribe failed: ${errorData}`);
throw new Error("Failed to unsubscribe from the newsletter");
}

34-48: Consider adding request timeouts for external API calls.

The fetch calls to Beehiiv have no timeout configured. If the Beehiiv API becomes slow or unresponsive, these calls could hang indefinitely. While the caller in auth.ts wraps this in try/catch so it won't block user creation, adding timeouts improves resilience:

Example using AbortSignal timeout
constresponse=awaitfetch(`${BEEHIIV_API_BASE}/publications/${BEEHIIV_PUBLICATION_ID}/subscriptions`,{method: "POST",headers: {"Content-Type": "application/json",Authorization: `Bearer ${BEEHIIV_API_KEY}`,},body: JSON.stringify({
email,reactivate_existing: true,send_welcome_email: false,}),signal: AbortSignal.timeout(10000),// 10 second timeout},);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ead81ed and 2662a3d.

📒 Files selected for processing (9)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • next.config.js
  • server/lib/newsletter.ts
💤 Files with no reviewable changes (5)
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
🧰 Additional context used
🧬 Code graph analysis (1)
server/lib/newsletter.ts (2)
app/(standalone)/newsletter/actions.ts (1)
  • subscribeToNewsletter (15-63)
auth.ts (1)
  • createUser (85-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (6)
next.config.js (1)

30-34: Wildcard path is discarded in the redirect destination.

The :path* parameter is captured but not used in the destination URL. All sub-paths like /newsletter/archive or /newsletter/confirmed will redirect to the root https://newsletter.codu.co instead of preserving the path.

If this is intentional (e.g., the external newsletter service handles all routing from its root), this is fine. Otherwise, consider appending the path:

 {
source: "/newsletter/:path*",
- destination: "https://newsletter.codu.co",+ destination: "https://newsletter.codu.co/:path*",
permanent: true,
},
app/(app)/(tsandcs)/tou/page.mdx (1)

1-106: Well-structured Terms of Use document.

The document comprehensively covers essential ToU sections including eligibility, intellectual property, disclaimers, and governing law. The 16-year age requirement aligns with GDPR requirements.

app/(app)/(tsandcs)/privacy/page.mdx (1)

95-106: Good addition documenting newsletter data collection.

The new "Newsletter Subscriber Data" section clearly documents the data collected through Beehiiv, including engagement metrics and geographic location, with appropriate disclosure about the third-party platform and unsubscribe mechanism.

server/lib/newsletter.ts (3)

5-25: Clean type definitions and config helper.

The interfaces properly model the Beehiiv API response structure, and getBeehiivConfig() fails fast with a clear error message when required environment variables are missing.


33-56: Subscribe flow looks solid.

Good use of reactivate_existing: true to handle re-subscription cases, and proper Sentry logging before throwing on failure. The send_welcome_email: false aligns with the existing welcome email flow in auth.ts.


119-126: Verify subscription status handling logic.

The function only returns true for "active" status. Based on the interface, subscriptions can also be "validating" or "pending".

If a user is in "validating" or "pending" state (e.g., double opt-in flow), this will return false. Verify this is the intended behavior for your use case.

Also, consider adding Sentry logging on line 125 for consistency with the subscribe path:

 } else {
+ const errorData = await response.text();+ Sentry.captureMessage(`Beehiiv subscription check failed: ${errorData}`);
throw new Error("Failed to check newsletter subscription");
}

Comment threadapp/(app)/(tsandcs)/privacy/page.mdx Outdated
Comment threadapp/(app)/(tsandcs)/tou/page.mdx Outdated
@NiallJoeMaher
NiallJoeMaher merged commit 1bdefc7 into codu-code:developJan 2, 2026
4 of 5 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/newsletter-migration branch January 2, 2026 14:08
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(newsletter): migrate to Beehiiv API for subscription management - #1321

Merged
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration
Jan 2, 2026
Merged

feat(newsletter): migrate to Beehiiv API for subscription management#1321
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

And update privacy policy

@NiallJoeMaher
NiallJoeMaher requested a review from a team as a code ownerJanuary 2, 2026 13:59
@vercel

vercelBot commented Jan 2, 2026

Copy link
Copy Markdown

@NiallJoeMaher is attempting to deploy a commit to the Codú Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitaiBot commented Jan 2, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Migrates newsletter handling to Beehiiv: removes client signup UI and server Email API action, adds Next.js redirects to the external newsletter site, updates privacy/terms with newsletter details, and implements Beehiiv-based subscribe/unsubscribe/status checks with error/Sentry handling.

Changes

Cohort / File(s)Summary
Legal documentation
app/(app)/(tsandcs)/privacy/page.mdx, app/(app)/(tsandcs)/tou/page.mdx
Updated Privacy "Last updated" date and added "Newsletter Subscriber Data" subsection; added new Newsletter Terms of Use document and linked it from privacy.
Removed newsletter pages
app/(standalone)/newsletter/page.tsx, app/(standalone)/newsletter/confirmed/page.tsx, app/(standalone)/newsletter/unsubscribed/page.tsx
Deleted main newsletter page, confirmation page, and unsubscribed page along with their metadata and UI components.
Removed client form & server action
app/(standalone)/newsletter/_form.tsx, app/(standalone)/newsletter/actions.ts
Removed client-side SignupForm and the server-side subscribeToNewsletter action (previous Email API/Zod validation flow).
Redirects config
next.config.js
Added async redirects() returning permanent redirects from /newsletter and /newsletter/:path* to https://newsletter.codu.co.
Beehiiv integration (server)
server/lib/newsletter.ts
Replaced Email API logic with Beehiiv API calls: subscribe, unsubscribe, and check-by-email; added isUserSubscribedToNewsletter(email: string): Promise<boolean); added error handling and Sentry logging.

Sequence Diagram(s)

sequenceDiagram
autonumber
participant Client as Client (Browser)
participant Next as Next.js Server
participant Lib as server/lib/newsletter
participant Bee as Beehiiv API
participant Sentry as Sentry
Client->>Next: POST /newsletter/subscribe (email)
Note right of Next: Next.js route/action receives form data
Next->>Lib: call subscribe(email)
Lib->>Bee: POST /v2/subscriptions (email, reactivate_existing, send_welcome_email)
alt 200 OK
Bee-->>Lib: 200 OK
Lib-->>Next: { success }
Next-->>Client: 200 (subscribed)
else error (4xx/5xx)
Bee-->>Lib: error
Lib->>Sentry: capture error
Lib-->>Next: { error }
Next-->>Client: 500/4xx (error)
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped from form to Beehiiv's door,
Bye-bye inputs I tended before.
Redirects guide the curious hare,
Terms and privacy now all fair.
A tiny hop, a bigger buzz—newsletter lore! ✨📬

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe pull request description is largely incomplete and fails to follow the template structure, providing only a minimal two-sentence comment without required sections.Add proper description sections: fill 'Pull Request details' with comprehensive info about the migration, specify any breaking changes, and include 'Associated Screenshots' section even if marked 'None'.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately describes the main change: migrating newsletter subscription management to Beehiiv API, which aligns with the primary code changes across multiple files.

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2662a3d and 57e8819.

📒 Files selected for processing (2)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/(app)/(tsandcs)/privacy/page.mdx (1)

7-7: Consider using HTTPS for internal links.

The link to Terms of Service uses http:// while other links in the codebase use https://. For consistency and security best practices, consider updating to HTTPS:

-This Policy supplements and is governed by our [Terms of Service](http://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](http://www.codu.co/tou).+This Policy supplements and is governed by our [Terms of Service](https://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](https://www.codu.co/tou).
server/lib/newsletter.ts (2)

70-98: Inconsistent error logging between subscribe and unsubscribe paths.

The subscribe path (line 54) logs failures to Sentry before throwing, but the unsubscribe path throws errors without Sentry logging. Consider adding Sentry capture for consistency and better observability:

Suggested improvement
 if (!getResponse.ok) {
if (getResponse.status === 404) {
return { message: "Successfully unsubscribed from the newsletter." };
}
+ const errorData = await getResponse.text();+ Sentry.captureMessage(`Beehiiv get subscription failed: ${errorData}`);
throw new Error("Failed to find subscription");
}
// ...
if (updateResponse.ok) {
return { message: "Successfully unsubscribed from the newsletter." };
} else {
+ const errorData = await updateResponse.text();+ Sentry.captureMessage(`Beehiiv unsubscribe failed: ${errorData}`);
throw new Error("Failed to unsubscribe from the newsletter");
}

34-48: Consider adding request timeouts for external API calls.

The fetch calls to Beehiiv have no timeout configured. If the Beehiiv API becomes slow or unresponsive, these calls could hang indefinitely. While the caller in auth.ts wraps this in try/catch so it won't block user creation, adding timeouts improves resilience:

Example using AbortSignal timeout
constresponse=awaitfetch(`${BEEHIIV_API_BASE}/publications/${BEEHIIV_PUBLICATION_ID}/subscriptions`,{method: "POST",headers: {"Content-Type": "application/json",Authorization: `Bearer ${BEEHIIV_API_KEY}`,},body: JSON.stringify({
email,reactivate_existing: true,send_welcome_email: false,}),signal: AbortSignal.timeout(10000),// 10 second timeout},);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ead81ed and 2662a3d.

📒 Files selected for processing (9)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • next.config.js
  • server/lib/newsletter.ts
💤 Files with no reviewable changes (5)
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
🧰 Additional context used
🧬 Code graph analysis (1)
server/lib/newsletter.ts (2)
app/(standalone)/newsletter/actions.ts (1)
  • subscribeToNewsletter (15-63)
auth.ts (1)
  • createUser (85-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (6)
next.config.js (1)

30-34: Wildcard path is discarded in the redirect destination.

The :path* parameter is captured but not used in the destination URL. All sub-paths like /newsletter/archive or /newsletter/confirmed will redirect to the root https://newsletter.codu.co instead of preserving the path.

If this is intentional (e.g., the external newsletter service handles all routing from its root), this is fine. Otherwise, consider appending the path:

 {
source: "/newsletter/:path*",
- destination: "https://newsletter.codu.co",+ destination: "https://newsletter.codu.co/:path*",
permanent: true,
},
app/(app)/(tsandcs)/tou/page.mdx (1)

1-106: Well-structured Terms of Use document.

The document comprehensively covers essential ToU sections including eligibility, intellectual property, disclaimers, and governing law. The 16-year age requirement aligns with GDPR requirements.

app/(app)/(tsandcs)/privacy/page.mdx (1)

95-106: Good addition documenting newsletter data collection.

The new "Newsletter Subscriber Data" section clearly documents the data collected through Beehiiv, including engagement metrics and geographic location, with appropriate disclosure about the third-party platform and unsubscribe mechanism.

server/lib/newsletter.ts (3)

5-25: Clean type definitions and config helper.

The interfaces properly model the Beehiiv API response structure, and getBeehiivConfig() fails fast with a clear error message when required environment variables are missing.


33-56: Subscribe flow looks solid.

Good use of reactivate_existing: true to handle re-subscription cases, and proper Sentry logging before throwing on failure. The send_welcome_email: false aligns with the existing welcome email flow in auth.ts.


119-126: Verify subscription status handling logic.

The function only returns true for "active" status. Based on the interface, subscriptions can also be "validating" or "pending".

If a user is in "validating" or "pending" state (e.g., double opt-in flow), this will return false. Verify this is the intended behavior for your use case.

Also, consider adding Sentry logging on line 125 for consistency with the subscribe path:

 } else {
+ const errorData = await response.text();+ Sentry.captureMessage(`Beehiiv subscription check failed: ${errorData}`);
throw new Error("Failed to check newsletter subscription");
}

Comment threadapp/(app)/(tsandcs)/privacy/page.mdx Outdated
Comment threadapp/(app)/(tsandcs)/tou/page.mdx Outdated
@NiallJoeMaher
NiallJoeMaher merged commit 1bdefc7 into codu-code:developJan 2, 2026
4 of 5 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/newsletter-migration branch January 2, 2026 14:08
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@NiallJoeMaher
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(newsletter): migrate to Beehiiv API for subscription management - #1321

Merged
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration
Jan 2, 2026
Merged

feat(newsletter): migrate to Beehiiv API for subscription management#1321
NiallJoeMaher merged 3 commits into
codu-code:developfrom
NiallJoeMaher:feat/newsletter-migration

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

And update privacy policy

@NiallJoeMaher
NiallJoeMaher requested a review from a team as a code ownerJanuary 2, 2026 13:59
@vercel

vercelBot commented Jan 2, 2026

Copy link
Copy Markdown

@NiallJoeMaher is attempting to deploy a commit to the Codú Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitaiBot commented Jan 2, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Migrates newsletter handling to Beehiiv: removes client signup UI and server Email API action, adds Next.js redirects to the external newsletter site, updates privacy/terms with newsletter details, and implements Beehiiv-based subscribe/unsubscribe/status checks with error/Sentry handling.

Changes

Cohort / File(s)Summary
Legal documentation
app/(app)/(tsandcs)/privacy/page.mdx, app/(app)/(tsandcs)/tou/page.mdx
Updated Privacy "Last updated" date and added "Newsletter Subscriber Data" subsection; added new Newsletter Terms of Use document and linked it from privacy.
Removed newsletter pages
app/(standalone)/newsletter/page.tsx, app/(standalone)/newsletter/confirmed/page.tsx, app/(standalone)/newsletter/unsubscribed/page.tsx
Deleted main newsletter page, confirmation page, and unsubscribed page along with their metadata and UI components.
Removed client form & server action
app/(standalone)/newsletter/_form.tsx, app/(standalone)/newsletter/actions.ts
Removed client-side SignupForm and the server-side subscribeToNewsletter action (previous Email API/Zod validation flow).
Redirects config
next.config.js
Added async redirects() returning permanent redirects from /newsletter and /newsletter/:path* to https://newsletter.codu.co.
Beehiiv integration (server)
server/lib/newsletter.ts
Replaced Email API logic with Beehiiv API calls: subscribe, unsubscribe, and check-by-email; added isUserSubscribedToNewsletter(email: string): Promise<boolean); added error handling and Sentry logging.

Sequence Diagram(s)

sequenceDiagram
autonumber
participant Client as Client (Browser)
participant Next as Next.js Server
participant Lib as server/lib/newsletter
participant Bee as Beehiiv API
participant Sentry as Sentry
Client->>Next: POST /newsletter/subscribe (email)
Note right of Next: Next.js route/action receives form data
Next->>Lib: call subscribe(email)
Lib->>Bee: POST /v2/subscriptions (email, reactivate_existing, send_welcome_email)
alt 200 OK
Bee-->>Lib: 200 OK
Lib-->>Next: { success }
Next-->>Client: 200 (subscribed)
else error (4xx/5xx)
Bee-->>Lib: error
Lib->>Sentry: capture error
Lib-->>Next: { error }
Next-->>Client: 500/4xx (error)
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped from form to Beehiiv's door,
Bye-bye inputs I tended before.
Redirects guide the curious hare,
Terms and privacy now all fair.
A tiny hop, a bigger buzz—newsletter lore! ✨📬

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe pull request description is largely incomplete and fails to follow the template structure, providing only a minimal two-sentence comment without required sections.Add proper description sections: fill 'Pull Request details' with comprehensive info about the migration, specify any breaking changes, and include 'Associated Screenshots' section even if marked 'None'.
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately describes the main change: migrating newsletter subscription management to Beehiiv API, which aligns with the primary code changes across multiple files.

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2662a3d and 57e8819.

📒 Files selected for processing (2)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/(app)/(tsandcs)/privacy/page.mdx (1)

7-7: Consider using HTTPS for internal links.

The link to Terms of Service uses http:// while other links in the codebase use https://. For consistency and security best practices, consider updating to HTTPS:

-This Policy supplements and is governed by our [Terms of Service](http://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](http://www.codu.co/tou).+This Policy supplements and is governed by our [Terms of Service](https://www.codu.co/terms) ("Terms") and [Newsletter Terms of Use](https://www.codu.co/tou).
server/lib/newsletter.ts (2)

70-98: Inconsistent error logging between subscribe and unsubscribe paths.

The subscribe path (line 54) logs failures to Sentry before throwing, but the unsubscribe path throws errors without Sentry logging. Consider adding Sentry capture for consistency and better observability:

Suggested improvement
 if (!getResponse.ok) {
if (getResponse.status === 404) {
return { message: "Successfully unsubscribed from the newsletter." };
}
+ const errorData = await getResponse.text();+ Sentry.captureMessage(`Beehiiv get subscription failed: ${errorData}`);
throw new Error("Failed to find subscription");
}
// ...
if (updateResponse.ok) {
return { message: "Successfully unsubscribed from the newsletter." };
} else {
+ const errorData = await updateResponse.text();+ Sentry.captureMessage(`Beehiiv unsubscribe failed: ${errorData}`);
throw new Error("Failed to unsubscribe from the newsletter");
}

34-48: Consider adding request timeouts for external API calls.

The fetch calls to Beehiiv have no timeout configured. If the Beehiiv API becomes slow or unresponsive, these calls could hang indefinitely. While the caller in auth.ts wraps this in try/catch so it won't block user creation, adding timeouts improves resilience:

Example using AbortSignal timeout
constresponse=awaitfetch(`${BEEHIIV_API_BASE}/publications/${BEEHIIV_PUBLICATION_ID}/subscriptions`,{method: "POST",headers: {"Content-Type": "application/json",Authorization: `Bearer ${BEEHIIV_API_KEY}`,},body: JSON.stringify({
email,reactivate_existing: true,send_welcome_email: false,}),signal: AbortSignal.timeout(10000),// 10 second timeout},);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ead81ed and 2662a3d.

📒 Files selected for processing (9)
  • app/(app)/(tsandcs)/privacy/page.mdx
  • app/(app)/(tsandcs)/tou/page.mdx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • next.config.js
  • server/lib/newsletter.ts
💤 Files with no reviewable changes (5)
  • app/(standalone)/newsletter/page.tsx
  • app/(standalone)/newsletter/unsubscribed/page.tsx
  • app/(standalone)/newsletter/confirmed/page.tsx
  • app/(standalone)/newsletter/_form.tsx
  • app/(standalone)/newsletter/actions.ts
🧰 Additional context used
🧬 Code graph analysis (1)
server/lib/newsletter.ts (2)
app/(standalone)/newsletter/actions.ts (1)
  • subscribeToNewsletter (15-63)
auth.ts (1)
  • createUser (85-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (6)
next.config.js (1)

30-34: Wildcard path is discarded in the redirect destination.

The :path* parameter is captured but not used in the destination URL. All sub-paths like /newsletter/archive or /newsletter/confirmed will redirect to the root https://newsletter.codu.co instead of preserving the path.

If this is intentional (e.g., the external newsletter service handles all routing from its root), this is fine. Otherwise, consider appending the path:

 {
source: "/newsletter/:path*",
- destination: "https://newsletter.codu.co",+ destination: "https://newsletter.codu.co/:path*",
permanent: true,
},
app/(app)/(tsandcs)/tou/page.mdx (1)

1-106: Well-structured Terms of Use document.

The document comprehensively covers essential ToU sections including eligibility, intellectual property, disclaimers, and governing law. The 16-year age requirement aligns with GDPR requirements.

app/(app)/(tsandcs)/privacy/page.mdx (1)

95-106: Good addition documenting newsletter data collection.

The new "Newsletter Subscriber Data" section clearly documents the data collected through Beehiiv, including engagement metrics and geographic location, with appropriate disclosure about the third-party platform and unsubscribe mechanism.

server/lib/newsletter.ts (3)

5-25: Clean type definitions and config helper.

The interfaces properly model the Beehiiv API response structure, and getBeehiivConfig() fails fast with a clear error message when required environment variables are missing.


33-56: Subscribe flow looks solid.

Good use of reactivate_existing: true to handle re-subscription cases, and proper Sentry logging before throwing on failure. The send_welcome_email: false aligns with the existing welcome email flow in auth.ts.


119-126: Verify subscription status handling logic.

The function only returns true for "active" status. Based on the interface, subscriptions can also be "validating" or "pending".

If a user is in "validating" or "pending" state (e.g., double opt-in flow), this will return false. Verify this is the intended behavior for your use case.

Also, consider adding Sentry logging on line 125 for consistency with the subscribe path:

 } else {
+ const errorData = await response.text();+ Sentry.captureMessage(`Beehiiv subscription check failed: ${errorData}`);
throw new Error("Failed to check newsletter subscription");
}

Comment threadapp/(app)/(tsandcs)/privacy/page.mdx Outdated
Comment threadapp/(app)/(tsandcs)/tou/page.mdx Outdated
@NiallJoeMaher
NiallJoeMaher merged commit 1bdefc7 into codu-code:developJan 2, 2026
4 of 5 checks passed
@NiallJoeMaher
NiallJoeMaher deleted the feat/newsletter-migration branch January 2, 2026 14:08
@coderabbitaicoderabbitaiBot mentioned this pull request Jan 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@NiallJoeMaher