Skip to content

Implement Newsletter API integration (subscribe, confirm, unsubscribe, preferences) #447

Description

@0xdevcollins

Integrate the Boundless Newsletter API into the frontend: double opt-in subscribe, confirmation and unsubscribe by token, legacy unsubscribe by email, and update preferences. The backend exposes the endpoints below; this issue covers Next.js proxy routes, client API layer, and UI so the app uses the new newsletter flow end-to-end.

Background / current state

  • Subscribe: components/overview/Newsletter.tsx uses newsletterSubscribe() from lib/api/waitlist.ts. The Next.js route app/api/newsletter/subscribe/route.ts currently proxies to waitlist (${backendUrl}/api/waitlist/subscribe), not the newsletter API.
  • Types: NewsletterSubscribeRequest in lib/api/waitlist.ts only has email and name; the new API adds source and tags.
  • No flows yet: Confirm (double opt-in), one-click unsubscribe by token, unsubscribe by email, or update preferences. No frontend pages for confirmation/unsubscribe success.

Backend API contract (reference)

Base URL: NEXT_PUBLIC_API_URL (e.g. http://localhost:8000 or https://staging-api.boundlessfi.xyz). All paths below are relative to the backend.

Method Endpoint Purpose
POST /api/newsletter/subscribe Subscribe email (double opt-in); rate limited 5 req/min
GET /api/newsletter/confirm/{token} Confirm subscription (302 → frontend)
GET /api/newsletter/unsubscribe/{token} One-click unsubscribe (302 → frontend)
POST /api/newsletter/unsubscribe Legacy unsubscribe by email
PATCH /api/newsletter/preferences Update topic tags for a subscriber

1. Subscribe — POST /api/newsletter/subscribe

  • Body (JSON):
    • email (string, required)
    • name (string, optional)
    • source (string, optional)
    • tags (string[], optional) — topic tags
  • Responses:
    • 201: Confirmation email sent — body e.g. { "message": "...", "id": "clx1234567890" }
    • 400: Invalid tags
    • 409: Email already subscribed
    • 429: Too many requests (5 per minute)
  • Behavior: Sends confirmation email (double opt-in). Rate limit: 5 requests per minute.

2. Confirm subscription — GET /api/newsletter/confirm/{token}

  • Path: token = confirmation token from email.
  • Responses:
    • 302: Redirect to frontend confirmation page
    • 400: Token expired
    • 404: Invalid/expired token

3. Unsubscribe by token — GET /api/newsletter/unsubscribe/{token}

  • Path: token = unsubscribe token from email footer.
  • Responses:
    • 302: Redirect to frontend unsubscribe confirmation page
    • 404: Invalid link

4. Unsubscribe by email — POST /api/newsletter/unsubscribe

  • Body (JSON): email (string, required).
  • Responses:
    • 200: Success
    • 404: Subscriber not found

5. Update preferences — PATCH /api/newsletter/preferences

  • Body (JSON):
    • email (string, required)
    • tags (string[], required) — valid values: bounties, hackathons, grants, updates
  • Responses:
    • 200: Success
    • 400: Invalid tags
    • 404: Subscriber not found

Tasks

1. Next.js API routes (proxy to backend)

  • 1.1 POST /api/newsletter/subscribe

    • Change proxy target from /api/waitlist/subscribe to /api/newsletter/subscribe.
    • Forward body as-is (email, name, source, tags).
    • Return backend status and body for 201, 400, 409, 429 (no swallowing of 4xx body).
    • Use same backendUrl pattern as in app/api/newsletter/subscribe/route.ts (from NEXT_PUBLIC_API_URL).
  • 1.2 GET /api/newsletter/confirm/[token]

    • Implement route that calls backend GET /api/newsletter/confirm/{token}.
    • If backend returns 302, redirect user to the Location header (frontend confirmation page).
    • If backend returns 400/404, redirect to a frontend page that shows “expired” or “invalid token” (e.g. /newsletter/confirm/error or same page with query param).
  • 1.3 GET /api/newsletter/unsubscribe/[token]

    • Same idea: call backend GET /api/newsletter/unsubscribe/{token}.
    • On 302, redirect to Location.
    • On 404, redirect to an error page or show “invalid unsubscribe link”.
  • 1.4 POST /api/newsletter/unsubscribe

    • Proxy body { email } to backend POST /api/newsletter/unsubscribe.
    • Return 200/404 and backend body as appropriate.
  • 1.5 PATCH /api/newsletter/preferences

    • Proxy body { email, tags } to backend PATCH /api/newsletter/preferences.
    • Return 200/400/404 and backend body.

2. Client API layer

  • 2.1 Types

    • Define request/response types for: subscribe, unsubscribe by email, update preferences.
    • Document allowed tags: bounties, hackathons, grants, updates.
  • 2.2 Subscribe

    • Extend or replace newsletterSubscribe so it:
      • Calls POST /api/newsletter/subscribe with email, name, source, tags.
      • Returns the 201 payload (e.g. message, id).
      • On 400/409/429, throws or returns a structured error so the UI can show specific messages (invalid tags / already subscribed / rate limited).
  • 2.3 Unsubscribe by email

    • New function that calls POST /api/newsletter/unsubscribe with { email }, handles 200/404.
  • 2.4 Update preferences

    • New function that calls PATCH /api/newsletter/preferences with { email, tags }, validates tags client-side against the allowed list, handles 200/400/404.
  • 2.5 Confirm / unsubscribe by token

    • No client call needed if links in emails point to Next.js routes above; user is sent to /api/newsletter/confirm/[token] or /api/newsletter/unsubscribe/[token] and the server redirects.
    • Alternatively, document that backend emails must use app domain links (e.g. {NEXT_PUBLIC_APP_URL}/api/newsletter/confirm/{token}) so the proxy routes are used.

3. Frontend UI

  • 3.1 Subscribe form (components/overview/Newsletter.tsx)

    • Keep required: email, name.
    • Add optional: source (e.g. fixed "website" or from props), tags (e.g. multi-select or checkboxes for bounties, hackathons, grants, updates).
    • On success (201): show “Confirmation email sent. Please check your inbox.” (or backend message).
    • On 409: show “This email is already subscribed.”
    • On 429: show “Too many attempts. Please try again in a minute.”
    • On 400: show “Invalid input” or “Invalid tags” as appropriate.
    • Ensure form is disabled or shows loading while submitting to avoid double submits and rate-limit issues.
  • 3.2 Confirmation success page

    • Add a page (e.g. app/(landing)/newsletter/confirmed/page.tsx) that users land on after clicking the confirmation link (via backend 302 or after proxy redirect).
    • Content: “You’re subscribed” / “Subscription confirmed” and optional link back to home or newsletter CTA.
  • 3.3 Confirmation error page

    • Page (e.g. app/(landing)/newsletter/confirm/error/page.tsx or query on same page) for expired/invalid token: “This confirmation link has expired or is invalid.”
  • 3.4 Unsubscribe success page

    • Page (e.g. app/(landing)/newsletter/unsubscribed/page.tsx) after one-click unsubscribe: “You have been unsubscribed.”
  • 3.5 Unsubscribe by email (optional)

    • If product needs it: small form (email only) that calls POST /api/newsletter/unsubscribe and shows success/not-found message. Could live on a “Manage subscription” or “Unsubscribe” page linked from footer or emails.
  • 3.6 Update preferences (optional)

    • If product needs it: page or modal where user enters email and selects tags (bounties, hackathons, grants, updates), then calls PATCH /api/newsletter/preferences. Show success/400/404.
  • If waitlist is fully replaced by newsletter for this flow, remove lib/api/waitlist.ts newsletterSubscribe and any references so the single source of truth is the new newsletter API and client helpers.


Acceptance criteria

  • POST /api/newsletter/subscribe proxies to backend /api/newsletter/subscribe and returns 201/400/409/429 with correct body.
  • Subscribe form sends email, name, and optionally source and tags; shows success and handles 409/429/400 with clear messages.
  • GET /api/newsletter/confirm/[token] and GET /api/newsletter/unsubscribe/[token] proxy to backend and redirect user to the appropriate frontend page (success or error).
  • Confirmation and unsubscribe success/error pages exist and render correct copy.
  • POST /api/newsletter/unsubscribe and PATCH /api/newsletter/preferences proxy to backend and return correct status/body.
  • Client helpers exist for subscribe, unsubscribe by email, and update preferences, with typed requests and error handling.
  • Valid tags and rate limit are documented; no (or minimal) references to waitlist for newsletter subscribe.

Files to touch (reference)

Area Files
API routes app/api/newsletter/subscribe/route.ts, new: app/api/newsletter/confirm/[token]/route.ts, app/api/newsletter/unsubscribe/[token]/route.ts, app/api/newsletter/unsubscribe/route.ts, app/api/newsletter/preferences/route.ts
Client API lib/api/waitlist.ts and/or new lib/api/newsletter.ts
UI components/overview/Newsletter.tsx, new: app/(landing)/newsletter/confirmed/page.tsx, app/(landing)/newsletter/confirm/error/page.tsx, app/(landing)/newsletter/unsubscribed/page.tsx (and optional manage/unsubscribe/preferences pages)
Config Backend must redirect 302 to NEXT_PUBLIC_APP_URL for confirm/unsubscribe, or use app-domain links to Next.js proxy routes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions