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
fetchwithapplication/x-www-form-urlencodedcontent 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
SignupResponsecomponent 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.