Skip to content

Work · Custom AI product · Digital publishing product · sibling of CS-04

AI book-writing application

Live and selling, but half of it was never instrumented

The production application behind an AI-assisted publishing product — six guided stages from a blank page to a published book, with AI generating chapters, titles, outlines and cover art. Built and hardened across both services: features shipped, regressions root-caused to the exact line, AI spend right-sized per job, and the purchase funnel instrumented end to end.

  • Next.js
  • FastAPI
  • MongoDB
  • Stripe
  • LangChain
  • Role · sole designer & builder
  • 7 min
front-end components on one atomic hierarchy
131Scope
backend tests passing before each deploy
57Scope
AI models, each assigned to the job it’s priced for
3Scope
System size
System flow · 9 stages · 44 components reconstructed from the build record

The situation

The product was already live. Customers signed up, paid, and walked through six guided stages from a blank page to a finished book. AI wrote chapters, titles, outlines and cover art along the way. The thing worked, and people were buying it.

What nobody could see was the half that had never been wired up. New customers were meant to get an onboarding tutorial. It flashed for an instant on first use and then never came back, for exactly the people it was built for. Purchases vanished from the ads platform's view the moment somebody reached the checkout page. The largest and most expensive AI model was billed for every job, whether that job was writing a chapter or polishing a paragraph.

I was brought in to work across both halves of the product, the web front end and the Python service behind it. The brief was to ship features, find what was quietly wrong, and make the money side of the funnel legible.

What was broken

  • The onboarding tutorial marked itself seen at the moment it opened, then a routing rewrite destroyed and remounted the page. The flag was already written, so the popup never returned.
  • Every AI job ran on the largest model available. Paragraph polish cost the same per call as long-form chapter writing.
  • Purchases were untracked past the checkout page, so no server-side signal ever reached the ads platform or the CRM.
  • Trial limits existed as policy rather than code. Without a hard expiry check at access time, an unswept trial user kept free access indefinitely.
  • The front end had no test harness at all, so none of the trial, offer or tutorial rules could be proven before a deploy.
  • A newer registration page never wrote the entry-source marker, which sent fresh trial signups into a no-credits paywall on their first landing.

Constraints

  • Two separate services, and the browser was never allowed to talk to the Python service directly.
  • Everything shipped against a product that was already live and taking payments.
  • The free-trial offer had to be withdrawable and later restorable by configuration, never by reverting code.
  • The CRM owned message content and soft timing. The product owned credit state, and the two could not be allowed to disagree.
  • No front-end test harness existed, so any rule that needed proving had to be moved somewhere testable first.

FIG 01 · System Flow

How the system actually runs

Every component below is reconstructed from the project’s own build records. Nothing is added to make it look bigger.

components
44
components
stages
9
stages
decisions & guards
9
decisions & guards
AI
3
AI
human handoffs
2
human handoffs
platforms
6
platforms
  1. Stage 1, Entry & signup: Registration, source attribution, and the session the whole product runs on.
    1. Product landing page (Source, Next.js). A public page offering sign in, sign up, and — when the trial flag is on — a free-trial entry link carrying a source parameter. Connects to Trial registration page when trial entry link; Register BFF handler when paid register.
    2. Trial registration page (Capture, Next.js). A dedicated trial-funnel registration page with its own hero, form card and mobile layout, designed and handed off before it was built. Connects to Entry-source tracker when source param present.
    3. Entry-source tracker (Guard, React + session storage). Reads the source query parameter and stores an entry-source marker, itself gated by the trial flag so the marker is cleared when trials are off. Connects to Register BFF handler when entry source attached; Production smoke test when missing marker found live.
    4. Register BFF handler (API / service, Next.js route handler). Forwards the whole signup body, entry source included, to the Python service; the browser never calls that service directly. Connects to Signup endpoint when proxied.
    5. Signup endpoint (API / service, FastAPI). Creates the user document, persists the entry source, auto-generates a username, and builds the trial fields when the entry is a trial and the flag is on. Connects to Trial state fields when trial entry + flag on; CRM signup event when background task; Auth & recovery surface when auto-login.
    6. Auth & recovery surface (Guard, FastAPI + BFF). Login, session, refresh, logout, username availability and the one-time-code forgot/verify/reset chain, paired front and back. Connects to Six-stage progress spine when authenticated; First-login popup when first login.
  2. Stage 2, Trial & credits: One expiring free credit, a clock, a drip, and a switch that can withdraw the whole offer.
    1. Trial state fields (Data store, MongoDB). Six fields on the user document: a trial credit flag, clock start, hard expiry, a state string, a book-started guard and a completion stamp. Connects to Rolling trial clock when clock set at signup; Expiry checked on access when hard deadline.
    2. Rolling trial clock (Wait, FastAPI). A rolling clock from signup, later shortened, with a deferred start when signup lands inside quiet hours so the clock begins at the next allowed local morning. Connects to Countdown banner phases when expiry exposed; Ten-touch drip runner when elapsed hours; Trial lifecycle sweep when elapsed hours.
    3. Countdown banner phases (Workflow, Framework-free JS module). A pure module mapping time remaining to four banner phases, each with its own colour and button label, unit-tested without a browser. Connects to Three-way offer branch when expired phase.
    4. Expiry checked on access (Guard, FastAPI). Access predicates check the hard deadline at access time, not just the credit flag, with a flag-only fallback for older documents that carry no expiry. Connects to Credit metering gate when past deadline, not free.
    5. Credit metering gate (Guard, FastAPI). Writing and completing skip the paid-credit deduction while a trial credit is live; otherwise the normal subscription-then-credits logic runs untouched. Connects to Streamed chapter writing when trial credit active; Three-way offer branch when no credit.
    6. One-book trial cap (Guard, FastAPI). A second book created inside a trial is refused with its own dedicated error code. Connects to Three-way offer branch when second book refused.
    7. Trial kill switch (Guard, Environment flag, both services). One flag per service, both defaulting off, gating every trial surface — the entry link, badge, banner, chapter-limit copy, header, source tracker, signup field, access predicates and the lifecycle sweep. Connects to Trial state fields when flag off, paid entry only.
    8. Trial lifecycle sweep (Scheduled job, Cron to a secured endpoint). A secret-header endpoint sweeping trial users by hours elapsed and driving the state transitions, including a deliberate hard-cut then re-grant window before the final revoke. Connects to Three-way offer branch when terminal state.
    9. Ten-touch drip runner (Scheduled job, FastAPI + cron). Ten elapsed-hour touches from welcome to the revenue branch, each carrying a progress flag that makes it skippable once the author has outgrown it. Connects to Ten-touch drip runner when deferred out of quiet hours; Three-way offer branch when revenue touch.
  3. Stage 3, Six guided stages: Blank page to finished manuscript on one screen the author never has to leave.
    1. Six-stage progress spine (Workflow, React). A granular step string mapped to a six-stage index, with chapter progress interpolated inside the writing band. Connects to Guided writing screen; Stage-aware tutorial when stage index to part.
    2. Guided writing screen (Workflow, React + BFF chat handler). The single surface hosting the setup questions, every generation step, the credit-gate modals, the tutorial modal and the countdown banner. Connects to Create book when new book.
    3. Create book (API / service, FastAPI). Creates the book document from a minimal payload and invokes no AI at all, so creating a book costs nothing. Connects to One-book trial cap when inside a trial; Short-form jobs when setup answered; Invisible-tutorial regression when no book id in the URL yet.
    4. Manuscript export (Scheduled job, FastAPI + PDF renderer). Assembles and exports the finished manuscript; export is also the act that consumes the trial credit, behind a once-guard. Connects to Book completed when once-guard passes.
    5. Book completed (Outcome, FastAPI). Marks the book done, flips trial state to completed, and fires the event that drives congratulations, tagging and the upsell. Connects to Three-way offer branch when finished branch; Server-side conversions when completion event.
  4. Stage 4, AI generation: Three models, each assigned to the job it is priced for.
    1. Short-form jobs (AI, FastAPI + hosted model). Summary refinement and paragraph polish, plus book titles, chapter titles, part titles, chapter summaries, key points and outline generate or update. Connects to Streamed chapter writing when outline approved.
    2. Streamed chapter writing (AI, FastAPI + chat model). Long-form chapter generation streamed to the browser, with chapter update and edit paths behind it. Connects to Cover art jobs when chapters written; Manuscript export when author exports.
    3. Cover art jobs (AI, FastAPI + image model, media CDN). Front, back and full-spread cover generation plus an iterate path, with a per-book tries counter, a cover history and a select and download path. Connects to Book completed when cover selected.
    4. Per-job model assignment (Guard, FastAPI). A documented call-site table putting long-form writing and the default chat helper on a flagship general model, paragraph polish on its mini sibling, and leaving only cover art on the largest image-capable model. Connects to Streamed chapter writing when long-form, flagship model; Short-form jobs when polish, mini model; Cover art jobs when cover art, image model; Backend test suite when full suite re-run.
  5. Stage 5, BFF, service & data: The browser never touches the Python service; the rules live where they can be tested.
    1. 37 BFF handlers (API / service, Next.js route handlers). Auth, book, chapter, cover and checkout handlers acting as a backend-for-frontend over 14 page routes. Connects to Python service when every browser request.
    2. Eight logic modules (Guard, Framework-free modules + node test runner). Trial banner phases, offer selection, tutorial stages, tutorial anchors, the getting-started popup, timezone and phone normalization, and the feature-flag helper — each with a colocated unit test. Connects to Countdown banner phases when phase maths; Offers page when offer table; Stage-aware tutorial when stage to part table.
    3. Python service (API / service, FastAPI). The service half of the 100 API endpoints across the two services, spanning admin, AI generation, auth, book, cover, chapter, payment and an automated-build router. Connects to Three collections when reads and writes.
    4. Three collections (Data store, MongoDB). Users (auth, credits, trial state, drip touches), books (chapters, covers, settings) and payments (checkout sessions and their status).
    5. Admin console (Human, Next.js + FastAPI admin router). User and book listing, search and delete, per-user and by-email credit set and adjust, and manual triggers for the scheduled endpoints. Connects to Trial lifecycle sweep when manual sweep; Three collections when credit fix without a deploy.
    6. Automatic manuscript build (Scheduled job, FastAPI background task). After a funnel purchase, builds the buyer whole book into a real account so they log in and find it already written. Connects to Book completed when build succeeds.
  6. Stage 6, Offers & checkout: A three-way post-trial branch, checkout sessions, and a webhook that cannot double-fire.
    1. Three-way offer branch (Decision, CRM + in-app offers page). At the revenue touch a contact goes one of three ways: finished authors to the publishing package, contacts carrying the done-for-you interest tag to the done-for-you package, and non-finishers to done-for-you with an entry credit fallback. Connects to Offers page when in-app offer; External funnel offers when publishing or done-for-you.
    2. Offers page (Payment, Next.js). A pure price and copy table drives three cards, each declaring how its button behaves — external funnel link, in-app credit checkout, or the legacy in-app path kept alive but unlinked so already-sent emails keep working. Connects to Checkout sessions when entry credit offer.
    3. External funnel offers (Payment, CRM funnels). The two higher tiers moved off instant payment links onto funnel pages whose checkout sits on a later step behind a video and an application form. Connects to Fulfillment workflows when funnel checkout product; Automatic manuscript build when funnel purchase.
    4. Checkout sessions (Payment, Stripe). Three session builders — book credits, the subscription package, and the offer path — each carrying purpose and buyer email in metadata. Connects to Checkout webhook when payment confirmed.
    5. Checkout webhook (Trigger, Stripe to FastAPI). The checkout-completed handler, with an idempotency guard near the top returning early when the session is already recorded complete; downstream it grants credits or the package entitlement, flips trial state to purchased, and only then notifies anyone. Connects to CRM purchase event when after credits granted; Server-side conversions when purchase event.
  7. Stage 7, CRM & conversions: Every signup and purchase reported server-side, deduplicated against the browser pixel.
    1. CRM signup event (CRM record, CRM inbound webhook). A server-side signup event fired as a background task carrying entry source, contact fields and the trial deadline, so the signup response is never delayed.
    2. CRM purchase event (CRM record, CRM inbound webhook). Buyer email, name, amount, currency, product label and purpose posted after the payment record is updated, through a shared sender that no-ops when unconfigured and retries transient failures. Connects to Fulfillment workflows when product matched.
    3. Fulfillment workflows (Workflow, CRM). Per-product purchase-tagging workflows that create the opportunity; before publishing new ones the existing published set was reconciled so only genuinely uncovered products got one.
    4. Server-side conversions (Report, Conversions API + browser pixel). A server-side sender mirroring the browser pixel standard events, hashing identifiers, and deriving a deterministic event id so the browser and server copies of one action are collapsed into one.
  8. Stage 8, Tutorial staging: Onboarding that appears when the author reaches the stage it explains — and the bug that hid it.
    1. First-login popup (Message, React + logic module). A one-time getting-started video popup on first landing after signup, driven by a first-login flag and its own pure module. Connects to Stage-aware tutorial when after the first video.
    2. Stage-aware tutorial (Message, React). Four tutorial videos mapped from the six-stage index, auto-opening the first time each part is reached, with the header button always opening the current part manually without reading or writing seen state.
    3. Invisible-tutorial regression (Staff alert, React). Starting a new book mounted the writing screen with no book id in the URL; the header opened the tutorial and wrote the seen flag at open time, a routing rewrite then remounted the page, the header re-read an already-written flag, and the popup never returned. Connects to Stage-aware tutorial when write moved to close-time; Production smoke test when reproduced live.
  9. Stage 9, Flags & verification: What is proven before a deploy, and what can be switched off without one.
    1. Backend test suite (Report, pytest). 18 test modules covering auth, completion, cover generation, CRM senders, conversions, outline update, offers, summary mode, five trial modules, purchase, user schema, username and the automatic build — run before each deploy. Connects to Production smoke test when pre-deploy gate.
    2. Production smoke test (Report, Live production run). A scripted live run across five flows with a pass, fail or blocked checkpoint table, screenshot and log evidence per row, and full teardown of the test users and contacts afterwards.
0/15

Someone clicks a trial ad at 1am, writes a whole book over the next day, and buys the next package.

Read this system as text
  1. Stage 01

    Entry & signup

    Registration, source attribution, and the session the whole product runs on.

    • Product landing page · Source · Next.js — A public page offering sign in, sign up, and — when the trial flag is on — a free-trial entry link carrying a source parameter. → Trial registration page (trial entry link), Register BFF handler (paid register)
    • Trial registration page · Capture · Next.js — A dedicated trial-funnel registration page with its own hero, form card and mobile layout, designed and handed off before it was built. → Entry-source tracker (source param present)
    • Entry-source tracker · Guard · React + session storage — Reads the source query parameter and stores an entry-source marker, itself gated by the trial flag so the marker is cleared when trials are off. → Register BFF handler (entry source attached), Production smoke test (missing marker found live)
    • Register BFF handler · API / service · Next.js route handler — Forwards the whole signup body, entry source included, to the Python service; the browser never calls that service directly. → Signup endpoint (proxied)
    • Signup endpoint · API / service · FastAPI — Creates the user document, persists the entry source, auto-generates a username, and builds the trial fields when the entry is a trial and the flag is on. → Trial state fields (trial entry + flag on), CRM signup event (background task), Auth & recovery surface (auto-login)
    • Auth & recovery surface · Guard · FastAPI + BFF — Login, session, refresh, logout, username availability and the one-time-code forgot/verify/reset chain, paired front and back. → Six-stage progress spine (authenticated), First-login popup (first login)
  2. Stage 02

    Trial & credits

    One expiring free credit, a clock, a drip, and a switch that can withdraw the whole offer.

    • Trial state fields · Data store · MongoDB — Six fields on the user document: a trial credit flag, clock start, hard expiry, a state string, a book-started guard and a completion stamp. → Rolling trial clock (clock set at signup), Expiry checked on access (hard deadline)
    • Rolling trial clock · Wait · FastAPI — A rolling clock from signup, later shortened, with a deferred start when signup lands inside quiet hours so the clock begins at the next allowed local morning. → Countdown banner phases (expiry exposed), Ten-touch drip runner (elapsed hours), Trial lifecycle sweep (elapsed hours)
    • Countdown banner phases · Workflow · Framework-free JS module — A pure module mapping time remaining to four banner phases, each with its own colour and button label, unit-tested without a browser. → Three-way offer branch (expired phase)
    • Expiry checked on access · Guard · FastAPI — Access predicates check the hard deadline at access time, not just the credit flag, with a flag-only fallback for older documents that carry no expiry. → Credit metering gate (past deadline, not free)
    • Credit metering gate · Guard · FastAPI — Writing and completing skip the paid-credit deduction while a trial credit is live; otherwise the normal subscription-then-credits logic runs untouched. → Streamed chapter writing (trial credit active), Three-way offer branch (no credit)
    • One-book trial cap · Guard · FastAPI — A second book created inside a trial is refused with its own dedicated error code. → Three-way offer branch (second book refused)
    • Trial kill switch · Guard · Environment flag, both services — One flag per service, both defaulting off, gating every trial surface — the entry link, badge, banner, chapter-limit copy, header, source tracker, signup field, access predicates and the lifecycle sweep. → Trial state fields (flag off, paid entry only)
    • Trial lifecycle sweep · Scheduled job · Cron to a secured endpoint — A secret-header endpoint sweeping trial users by hours elapsed and driving the state transitions, including a deliberate hard-cut then re-grant window before the final revoke. → Three-way offer branch (terminal state)
    • Ten-touch drip runner · Scheduled job · FastAPI + cron — Ten elapsed-hour touches from welcome to the revenue branch, each carrying a progress flag that makes it skippable once the author has outgrown it. → Ten-touch drip runner (deferred out of quiet hours), Three-way offer branch (revenue touch)
  3. Stage 03

    Six guided stages

    Blank page to finished manuscript on one screen the author never has to leave.

    • Six-stage progress spine · Workflow · React — A granular step string mapped to a six-stage index, with chapter progress interpolated inside the writing band. → Guided writing screen, Stage-aware tutorial (stage index to part)
    • Guided writing screen · Workflow · React + BFF chat handler — The single surface hosting the setup questions, every generation step, the credit-gate modals, the tutorial modal and the countdown banner. → Create book (new book)
    • Create book · API / service · FastAPI — Creates the book document from a minimal payload and invokes no AI at all, so creating a book costs nothing. → One-book trial cap (inside a trial), Short-form jobs (setup answered), Invisible-tutorial regression (no book id in the URL yet)
    • Manuscript export · Scheduled job · FastAPI + PDF renderer — Assembles and exports the finished manuscript; export is also the act that consumes the trial credit, behind a once-guard. → Book completed (once-guard passes)
    • Book completed · Outcome · FastAPI — Marks the book done, flips trial state to completed, and fires the event that drives congratulations, tagging and the upsell. → Three-way offer branch (finished branch), Server-side conversions (completion event)
  4. Stage 04

    AI generation

    Three models, each assigned to the job it is priced for.

    • Short-form jobs · AI · FastAPI + hosted model — Summary refinement and paragraph polish, plus book titles, chapter titles, part titles, chapter summaries, key points and outline generate or update. → Streamed chapter writing (outline approved)
    • Streamed chapter writing · AI · FastAPI + chat model — Long-form chapter generation streamed to the browser, with chapter update and edit paths behind it. → Cover art jobs (chapters written), Manuscript export (author exports)
    • Cover art jobs · AI · FastAPI + image model, media CDN — Front, back and full-spread cover generation plus an iterate path, with a per-book tries counter, a cover history and a select and download path. → Book completed (cover selected)
    • Per-job model assignment · Guard · FastAPI — A documented call-site table putting long-form writing and the default chat helper on a flagship general model, paragraph polish on its mini sibling, and leaving only cover art on the largest image-capable model. → Streamed chapter writing (long-form, flagship model), Short-form jobs (polish, mini model), Cover art jobs (cover art, image model), Backend test suite (full suite re-run)
  5. Stage 05

    BFF, service & data

    The browser never touches the Python service; the rules live where they can be tested.

    • 37 BFF handlers · API / service · Next.js route handlers — Auth, book, chapter, cover and checkout handlers acting as a backend-for-frontend over 14 page routes. → Python service (every browser request)
    • Eight logic modules · Guard · Framework-free modules + node test runner — Trial banner phases, offer selection, tutorial stages, tutorial anchors, the getting-started popup, timezone and phone normalization, and the feature-flag helper — each with a colocated unit test. → Countdown banner phases (phase maths), Offers page (offer table), Stage-aware tutorial (stage to part table)
    • Python service · API / service · FastAPI — The service half of the 100 API endpoints across the two services, spanning admin, AI generation, auth, book, cover, chapter, payment and an automated-build router. → Three collections (reads and writes)
    • Three collections · Data store · MongoDB — Users (auth, credits, trial state, drip touches), books (chapters, covers, settings) and payments (checkout sessions and their status).
    • Admin console · Human · Next.js + FastAPI admin router — User and book listing, search and delete, per-user and by-email credit set and adjust, and manual triggers for the scheduled endpoints. → Trial lifecycle sweep (manual sweep), Three collections (credit fix without a deploy)
    • Automatic manuscript build · Scheduled job · FastAPI background task — After a funnel purchase, builds the buyer whole book into a real account so they log in and find it already written. → Book completed (build succeeds)
  6. Stage 06

    Offers & checkout

    A three-way post-trial branch, checkout sessions, and a webhook that cannot double-fire.

    • Three-way offer branch · Decision · CRM + in-app offers page — At the revenue touch a contact goes one of three ways: finished authors to the publishing package, contacts carrying the done-for-you interest tag to the done-for-you package, and non-finishers to done-for-you with an entry credit fallback. → Offers page (in-app offer), External funnel offers (publishing or done-for-you)
    • Offers page · Payment · Next.js — A pure price and copy table drives three cards, each declaring how its button behaves — external funnel link, in-app credit checkout, or the legacy in-app path kept alive but unlinked so already-sent emails keep working. → Checkout sessions (entry credit offer)
    • External funnel offers · Payment · CRM funnels — The two higher tiers moved off instant payment links onto funnel pages whose checkout sits on a later step behind a video and an application form. → Fulfillment workflows (funnel checkout product), Automatic manuscript build (funnel purchase)
    • Checkout sessions · Payment · Stripe — Three session builders — book credits, the subscription package, and the offer path — each carrying purpose and buyer email in metadata. → Checkout webhook (payment confirmed)
    • Checkout webhook · Trigger · Stripe to FastAPI — The checkout-completed handler, with an idempotency guard near the top returning early when the session is already recorded complete; downstream it grants credits or the package entitlement, flips trial state to purchased, and only then notifies anyone. → CRM purchase event (after credits granted), Server-side conversions (purchase event)
  7. Stage 07

    CRM & conversions

    Every signup and purchase reported server-side, deduplicated against the browser pixel.

    • CRM signup event · CRM record · CRM inbound webhook — A server-side signup event fired as a background task carrying entry source, contact fields and the trial deadline, so the signup response is never delayed.
    • CRM purchase event · CRM record · CRM inbound webhook — Buyer email, name, amount, currency, product label and purpose posted after the payment record is updated, through a shared sender that no-ops when unconfigured and retries transient failures. → Fulfillment workflows (product matched)
    • Fulfillment workflows · Workflow · CRM — Per-product purchase-tagging workflows that create the opportunity; before publishing new ones the existing published set was reconciled so only genuinely uncovered products got one.
    • Server-side conversions · Report · Conversions API + browser pixel — A server-side sender mirroring the browser pixel standard events, hashing identifiers, and deriving a deterministic event id so the browser and server copies of one action are collapsed into one.
  8. Stage 08

    Tutorial staging

    Onboarding that appears when the author reaches the stage it explains — and the bug that hid it.

    • First-login popup · Message · React + logic module — A one-time getting-started video popup on first landing after signup, driven by a first-login flag and its own pure module. → Stage-aware tutorial (after the first video)
    • Stage-aware tutorial · Message · React — Four tutorial videos mapped from the six-stage index, auto-opening the first time each part is reached, with the header button always opening the current part manually without reading or writing seen state.
    • Invisible-tutorial regression · Staff alert · React — Starting a new book mounted the writing screen with no book id in the URL; the header opened the tutorial and wrote the seen flag at open time, a routing rewrite then remounted the page, the header re-read an already-written flag, and the popup never returned. → Stage-aware tutorial (write moved to close-time), Production smoke test (reproduced live)
  9. Stage 09

    Flags & verification

    What is proven before a deploy, and what can be switched off without one.

    • Backend test suite · Report · pytest — 18 test modules covering auth, completion, cover generation, CRM senders, conversions, outline update, offers, summary mode, five trial modules, purchase, user schema, username and the automatic build — run before each deploy. → Production smoke test (pre-deploy gate)
    • Production smoke test · Report · Live production run — A scripted live run across five flows with a pass, fail or blocked checkpoint table, screenshot and log evidence per row, and full teardown of the test users and contacts afterwards.

Platforms · Next.js · FastAPI · MongoDB · Stripe · CRM · Conversions API

What I built

A backend-for-frontend, and rules that could be tested

I structured the web front end as 14 page routes and 37 API handlers acting as a backend-for-frontend. Service URLs and tokens stay server-side by construction rather than by discipline. Together the two services expose 100 API endpoints.

Then I pulled the business rules out of the components into eight framework-free logic modules, each with a colocated unit test: trial banner phases, offer selection, tutorial stages, tutorial anchors, the first-login popup, timezone and phone normalization, and the feature-flag helper. Extracting the rules is what made the rules testable. The front end had no harness, so the rules had to leave the components to get one.

Trial state, and a switch that can withdraw the whole offer

Trial state lives in six fields on the user document: a credit flag, a clock start, a hard expiry, a state string, a book-started guard and a completion stamp. The clock rolls from signup, and defers its start when a signup lands inside quiet hours, so nobody burns trial hours while asleep.

The enforcement decision mattered more than the fields. I moved the deadline check into the access predicates rather than leaving it to the scheduled sweep, because a trial user the sweep never reached kept free access forever. A second book inside a trial is refused with its own error code, since a free credit with no cap is an unlimited product.

Above all of it sits one flag per service, both defaulting off, gating every trial surface: the entry link, badge, banner, chapter-limit copy, header, source tracker, signup field, access predicates and the lifecycle sweep. All the code stays in place and no trial rows are touched. Re-enabling is two environment variables and two redeploys. The accepted cost is written into the design rather than hidden, because in-flight trials freeze too.

One model per job, instead of one model for everything

I wrote a call-site table assigning each AI job the model it is priced for. Long-form chapter writing and the default chat helper went to a flagship general model, paragraph polish to its mini sibling, and only cover art stayed on the largest image-capable model.

Offers, checkout, and a webhook that cannot double-fire

At the revenue touch a contact goes one of three ways: finished authors to the publishing package, contacts carrying the done-for-you interest tag to that package, and non-finishers to done-for-you with an entry-credit fallback. A single post-trial pitch had been treating a finished author and a stalled one identically.

The offers page is driven by a pure price and copy table where each card declares how its button behaves. Encoding button behaviour as data made the branch unit-testable and the migration purely additive. The checkout webhook carries an idempotency guard near the top, returning early when the session is already recorded complete. It is the only trustworthy purchase signal, because a success-page call is lost when the tab closes and can be faked by a client.

Instrumentation, end to end

Signup and purchase events fire server-side into the CRM as background tasks, so a CRM outage can never fail a payment that already succeeded. Alongside them, a server-side conversions sender mirrors the browser pixel's standard events, hashes identifiers, and derives a deterministic event id from a stable hashed key, so the browser and server copies of one action collapse into one.

The tutorial, staged and then repaired

Four tutorial videos map from the six-stage index and auto-open the first time each part is reached. The header button always opens the current part manually, without reading or writing seen state. The regression that hid all of it came down to one write in the wrong place. Moving the seen flag from open-time into the close handler fixed it, with one accepted consequence documented: reloading mid-popup shows it again until dismissed.

Edge cases and failure handling

  • A storage error while reading a seen flag is treated as seen, so the onboarding popup can never hard-loop on a broken browser.
  • The automatic manuscript build stops outright when the buyer has no stored chapter, because shipping a different book than the one they were sold is worse than shipping nothing.
  • That build runs in the long-lived Python process on purpose. An edge worker gets seconds of background execution and a book takes minutes.
  • Ordinary drip touches defer out of quiet hours, final-hour touches drop rather than land late, and the revenue touch is named in code as its own constant so deferral cannot starve it.
  • Each drip touch carries a progress flag that makes it skippable, so the sequence never congratulates an author on a step they already finished.

How I verified it

The backend carries 18 test modules covering auth, completion, cover generation, the CRM senders, conversions, outline update, offers, summary mode, five trial modules, purchase, user schema, username and the automatic build. They run before each deploy.

The model reassignment was verified by a code sweep across the call sites plus the full backend suite. The tutorial fix was reproduced live rather than reasoned about, so the proof was the popup appearing for a real new customer.

The offer migration was gated on a verification phase whose written instruction was to stop and report if no checkout step existed. It caught a funnel page whose visible copy had been cloned from another tier and pitched the wrong offer. Fulfillment workflows were reconciled against the published set before more were added, so no product ended up with two workflows double-tagging its buyers.

Last, I ran a scripted production smoke test across five flows with a pass, fail or blocked checkpoint table, screenshot and log evidence per row, and full teardown of the test users and contacts afterwards. The point of that run was to find out rather than to confirm, and it returned a not-ready verdict naming two blockers and three secondary findings. It separated an app defect from a platform-side billing outage instead of reporting one undifferentiated failure, and it caught the drip runner marking touches as sent during the outage without delivering them.

What changed

The product now runs live in production across both services, the web front end and the Python API, spanning 100 API endpoints. Three AI models are assigned by job rather than one model billed for everything. The regression hiding onboarding from new users was found, fixed, and proven by live reproduction against the real product.

Purchases are reported server-side through the CRM and the conversions API, deduplicated against the browser pixel, so the funnel is legible past the checkout page for the first time. Trial expiry is enforced in the access predicates rather than left to a scheduled sweep. The whole trial offer sits behind a kill switch that withdraws or restores it by configuration, and it has since been withdrawn that way.

Behind the build sits a 35-document spec-and-plan corpus. The design came first and the code followed it, which is why the accepted trade-offs are written down instead of remembered.

Before / after

  • Before
  • ✕ A tutorial that marked itself seen and never showed again
  • ✕ The largest AI model billed for every job, big or small
  • ✕ Purchases untracked past the checkout page
  • ✕ Trial limits that existed as policy, not code
  • After
  • ✓ Onboarding proven visible to exactly the users it was built for
  • ✓ Three models assigned by job — writing, polish, cover art
  • ✓ Pixel + server-side conversions + CRM webhooks on every purchase
  • ✓ Trial expiry enforced in code, with a kill switch

ScopeFigures on this page describe what was built, covered or verified — not a measured lift.

Have something close to this? Book a call. I’ll tell you if I’ve already built it.

One Manila business day to reply. All client work is under NDA; what you see here is redrawn architecture with real metrics.

Book