Motivka

Substack Subscription in Chat Gate

Extended the chat signup gate to subscribe visitors to the Substack newsletter alongside creating a PocketBase subscriber record, using a non-blocking server-side POST so chat access is never delayed.

3 Phases
3 Tasks
1 Days

The Problem

The chat signup gate on the homepage collects a visitor's name and email, sets a motivka_visitor cookie, and creates a record in PocketBase's subscribers collection. But it did not actually subscribe them to the Substack newsletter. Only the footer NewsletterForm did that, via a direct form POST to Substack's public endpoint. Two separate subscription paths, and the more prominent one (the chat gate) was not doing the real work.

What I Built

The /api/visitor POST endpoint now performs dual subscription: PocketBase record creation and a server-side POST to https://motivka.substack.com/api/v1/free. Both operations run in parallel via Promise.allSettled since neither depends on the other.

The key design constraint was non-blocking behaviour. Substack sends a confirmation email independently -- visitors should never wait for that. If the Substack POST fails, the visitor still gets their cookie and can chat immediately. The response now includes a substackSubscribed boolean alongside the existing subscribed field so the client knows the outcome of each operation.

On the frontend, handleSignupSubmit in +page.svelte now checks result.substackSubscribed to set the motivka-newsletter-subscribed localStorage flag. This means the footer newsletter form will show "Subscribed" for visitors who entered via the chat gate -- a small UX consistency win.

Decisions

  • Server-side POST, not client redirect -- no new tab, no navigation away from the chat. The SvelteKit server calls Substack's endpoint directly using fetch with application/x-www-form-urlencoded content type.
  • Failure tolerance -- the Substack POST uses the same try/catch pattern as the PocketBase subscriber creation. Log the failure, set the flag to false, move on.
  • No form changes -- the SignupResponse component keeps its current name and email fields. No additional UI needed.

Testing

Red-Green-Refactor workflow. Tests mock both the PocketBase client and fetch (for the Substack POST), then assert: the correct URL is called, the email is sent as URL-encoded form data, substackSubscribed reflects success or failure, and the visitor cookie is set regardless of Substack outcome.

Features Delivered

Key Decisions

  • Server-side POST to Substack rather than client redirect — No navigation interruption; visitors stay in the chat flow
  • Non-blocking failure tolerance — Substack failure must never block the visitor cookie or chat access
  • Reused existing form-encoded POST format — Same public endpoint and content type as the footer newsletter form