Every change we ship to katura1999.com — features, fixes, security patches, the lot. Pulled straight from our private GitHub repository so you can see exactly what was built and when.
By the numbers
Lines of code
943,828
Web platform — TypeScript, React, Prisma, CSS
iOS
79,607
Swift + SwiftUI lines
Android
3,117
Kotlin + Jetpack Compose lines
All platforms
1,026,552
Web + iOS + Android combined (51.0× the King James Bible)
Characters written
40.17M
40,168,559 total characters
Updates pushed
2,265
exact commit count on main
Current version
v1.22.65
build 2265 · d89cabc
Database models
420
across 47 schema files — most SaaS platforms have 20–50
API endpoints
1,052
individually routed — Stripe's public API has ~400
Translated strings
90,960
every string, in 24 languages
System permutations
10^316
2^1,052 endpoint combinations — more than atoms in the observable universe (10^80)
Project age
9mo 16d
since Dec 14, 2025
Pre-AI dev hours
34.2K hrs
1,026,552 lines ÷ 30 LOC/hr — equivalent to 16.5 years (senior engineer, no AI)
With-AI dev hours
8.6K hrs
4× AI productivity multiplier (2024–2026 studies) — equivalent to 1,069 days
Equivalent firm cost
$8,963,446
live ticker · Katura rate: $60 USD/hr
Hours estimated from source line count at 30 LOC/hr (industry benchmark for production-quality TypeScript/React without AI assistance), with a 4× multiplier for AI-assisted development per published 2024–2026 enterprise studies. Equivalent Firm Cost uses a $250/hr loaded billable rate reflecting a premium engineering firm building enterprise-grade SaaS — and ticks up live, because the project is still being actively built.
Commit history
2,265 updates pushed
Showing page 3 of 40 · 101–150 of 2,000 fetched
Tuesday, August 25, 2026
21 updates pushed
Featureprospecting5:05 PM · ZRosserMcIntosh
stamp which generation wrote each lead, and grade the book against today's standard
The machine's standard has moved several times — the claim ledger arrived,
then the free-value gate, then the voice spec, then the per-arm link rules
— and each time everything already in the pipeline stayed exactly as it
was. A deck written before the ledger existed is still a DRAFT, still
sendable, and looks identical in the console to one written this morning.
The console's only staleness signals were two ad-hoc predicates ("no claim
ledger", "fact-check failed"), which caught two of the ways an artifact
goes stale and none of the others: a deck that gives away nothing usable,
an email whose words disagree with what is actually attached, an arm-A
email that picked up a link.
This answers the question two ways on purpose, because they are two
different questions.
WHICH GENERATION WROTE IT — a stamp recorded at write time, on every path
that produces an email (first touch, follow-up, rewrite), along with which
adaptive guidance was steering. Unstamped rows get a generation inferred
from what their deck satisfies, and `generationStamped` is reported next
to the number rather than hiding the difference: inference can only ever
place a lower bound, because it cannot distinguish "written before the
free-value gate existed" from "written after it and failed it".
WOULD IT PASS TODAY — every current check re-run against the stored
artifact, including the actual first-touch validator over the actual body.
This is what decides whether something needs reprocessing, and it is
deliberately NOT derived from the stamp. A version number is a promise
someone remembered to keep; the validator is a fact. An email stamped
current that nonetheless fails today's rules is a real defect, and a
stamp-based check would have hidden it — so there is a test for exactly
that case.
The panel leads with the only genuinely urgent bucket: drafted or approved
emails, sitting in the send queue, failing a check we enforce today. A
SENT email is never counted — it is the outreach record, there is nothing
left to fix, and counting it would make the urgent number permanently
non-zero and therefore ignorable.
Each defect is offered with the single action that clears it, ranked so a
prospect needing several gets told the one that subsumes the rest:
rewriting the email of a pre-ledger deck is wasted work, because the
reassessment replaces it anyway.
── The bulk buttons ──
"Regen all emails" told the operator that APPROVED drafts would be reset
to DRAFT. They were not: the guard returned skipped_approved, and the
diagnose why outbound isn't working, with a sample-size gate in front
"Why aren't we getting results?" is the right question and the dangerous
one. Forty emails go out, three replies come back unevenly across the
arms, and the temptation is to rewrite the pitch, re-price the offer and
re-target the list at once — on evidence that cannot distinguish a real
effect from a coin flip. Every change made that way also destroys the
experiment that was about to answer the question.
So the diagnosis runs in two halves, in this order.
DETERMINISTIC (experiment-stats.ts, 19 tests). Wilson intervals rather
than the normal approximation, because at these counts the textbook
interval is simply wrong: 1 reply in 40 gives a negative lower bound, and
0 in 40 gives [0, 0] — "proven never to work", from forty emails. Pooled
two-proportion tests. And the number that makes "wait" actionable rather
than annoying: how many more sends per arm, and how many days that is at
the current rate.
It also reports what a test of this size COULD have detected. A bare
"p = 0.42, not significant" reads as "the arms are the same"; at n = 40
it means the test could not have seen a 15-point difference if one
existed.
INTERPRETIVE (Claude Opus 5, adaptive thinking, structured output). Given
those numbers plus the funnel in absolute counts, per-step outcomes, deck
fact-check health, the cost ladder and the ACTUAL COPY of recent sends:
where is the funnel really losing people. Absolute defects — a 30% bounce
rate, decks that never shipped, approved emails that never sent — are
fair game at any sample size, because those are not experimental results.
Under NO_DATA or TOO_EARLY the model may not name a winning arm or
propose a strategy pivot; its recommendations may only increase the
information available. That is enforced three times: in the prompt, by
nulling any guidance it returns anyway, and again at the activate
endpoint for older rows.
Guidance is a reviewed artifact, not an auto-applied one. Proposals sit
Featuresignatures4:43 PM · ZRosserMcIntosh
rebuild the signing domain and put signed copies behind an account
Three changes to sign.katura1999.com and what happens after someone signs.
ELEGANCE. The landing page introduced a fine-jewelry house with an eight-
card SaaS feature grid, each card led by an emoji. Emoji are someone
else's icon set rendered at whatever size the OS decides; a house that
sets stones by hand does not introduce itself with a gem emoji. The grid
is now a typographic index — rules, numerals, and the display face the
rest of the brand uses.
LANGUAGE. The page was English for everyone, and not because nobody had
translated it: the proxy rewrites the signing subdomain and returns
before intlMiddleware runs, so getLocale() there always answered "en".
Rather than reorder a rewrite every signing link in the wild depends on,
lib/signatures/locale.ts negotiates directly — saved choice, then the
device's Accept-Language (quality-ranked, region tags folded to the base
language), then the edge's country header. Device before country on
purpose: a Brazilian in Paris on a pt-BR phone wants Portuguese, and a
configured language says more about what someone can READ than the
country their IP resolves to. All 24 locales carry the namespace.
The switcher deliberately shows language names, not flags. A flag is a
country, not a language — Portuguese is not Brazil, Arabic is not Saudi
Arabia — and flag emoji were half of what the page needed rid of.
RETENTION. The completion email attached the executed PDF. That leaves a
legal document sitting in an inbox forever: forwardable, searchable by
anyone who later gets into that mailbox, impossible to withdraw. It now
links to /account/documents, reachable only by signing in as the address
the document was sent to.
Matching is on EMAIL rather than user id because the QR flow mints
documents for people with no account at the moment they sign — asking a
stranger at a counter to register before agreeing to be filmed would kill
the flow. Registering later with the same address claims everything
Featuresignatures4:43 PM · ZRosserMcIntosh
self-serve signing from a QR code
PLACEHOLDERS accept both {name} and {{name}}. Refusing single braces made the template editor a transcription exercise — every field had to be re-typed with doubled braces before a pasted Word document would render.
The FORM IS DERIVED from the wording, never configured beside it: every placeholder, minus the operator's business constants, minus the ones the system fills. Add {instagram_handle} to the body and the form asks for it; delete it and it disappears. The form cannot drift from the document in either direction.
A BLANK BUSINESS CONSTANT does not fall through to the signer's form — the key exists, so it reads as provided, and it prints as empty space in the signed copy. Publishing is refused while any is blank.
{signature_datetime} cannot be known at mint time. Rather than refuse the placeholder or ship a visible blank, creation leaves a marked span and markSigned fills it in place, idempotently.
CPF is checksum-validated, not length-checked. On a Brazilian image release the number IS the identification; one that identifies nobody is worth nothing at the moment you need it.
record the built workspace tree and the two API limits hit
Renaming the owner-created default space returns a permission error — the token is a workspace member, not the owner. Creating new spaces works.
PUT /space accepts a statuses array, returns 200 with no error, and silently ignores it (verified by re-fetch). Statuses are UI-only. Until all six exist, two-way sync would collapse BACKLOG/REVIEW/BLOCKED, so Phase 3 stays blocked.
manual per-category export of minutes into ClickUp
src/lib/integrations/clickup.ts — minimal API client. Notes worth keeping: personal tokens use a BARE Authorization header (only OAuth uses "Bearer"), the markdown field is `markdown_content` not `markdown_description`, due dates are epoch MILLISECONDS, and priority is 1..4 with 1 = Urgent.
Task creation is sequential, not parallel, and stops on the first hard failure. The rate limit is 100 req/min per token, and a half-applied export is worse than a refused one — duplicates in a shared workspace are a social problem, not just a data one. Stopping early gives an accurate report of what landed.
Due dates resolve relative to the MEETING, not now(). Minutes are often exported days later, so "Friday" means the Friday after the call. Unparseable values stay null and the task is tagged needs-date rather than being given an invented deadline. Same for unowned items: tagged needs-owner.
Only actionItems and followUps carry owner/due/priority. Decisions, open questions and risks are statements rather than assignments, so those fields are left empty instead of fabricated.
Duplicate protection is honest about its absence. There is no ClickupTaskLink table yet, so this cannot prove an item was already exported. Each task carries a katura-<meetingId> tag and a backlink to the minutes, and the confirm dialog states plainly that re-exporting a category creates duplicates. An unprovable silent dedup would be worse than a visible warning.
Scoped to OWNER/MANAGER — this writes into a shared external workspace.
stop the auditor rejecting the email's own sender voice
New ClaimKind APPROVED_SENDER_CONTEXT for first-person statements about Zachary or Katura. Excluded from the factual-claim gate for the same reason HYPOTHESIS and RECOMMENDATION already are: it asserts nothing about the prospect, so absent prospect evidence is not a defect. Judged against founderStatement by the auditor and by detectForbiddenLeaps, which still runs on every claim regardless of kind.
isSenderContextSentence() — deliberately narrow. Requires first person AND a Katura/Zachary anchor AND no second-person reference. A loose match here would let unevidenced claims about a real business ride along in first person, which is a worse failure than the one being fixed. Tests pin both directions.
email-body-* restatements now carry the deck's evidence ids, so the auditor has the citations it was already told to judge them against.
founderStatement now names the role, closing the writer/auditor disagreement.
v3-integrity and generate-gates pin the invariant that was missing: both FIRST_TOUCH_EMAIL_RULES and founderStatement must name the role, and no factual email claim may carry zero evidence.
conversion integrity audit and ClickUp integration plan
The external consultant's audit is largely stale against the repo. The storefront is built; the recovery and measurement layer behind it is not.
The return-policy contradiction it flags was already resolved on 2026-08-21, and the canonical policy is stricter than the consultant's suggested wording (lab-grown and custom pieces are final sale).
Only 1 of 132 eligible carts has a consented user. Consent is not captured at cart time, so cart recovery has almost no addressable audience. That, not the plumbing, is the real constraint.
User.adminLocale does not exist as a column; admin/layout.tsx raw-SELECTs it inside a try/catch and has always fallen through to null.
Every guard now FAILS CLOSED. If we cannot prove we are under the frequency cap, we assume we are over it. If the record lookup throws, the cart is skipped rather than re-sent blind.
Record-before-send ordering. The reminder row is written FIRST; if that write fails the send is suppressed. A lost reminder is recoverable, a reminder loop against a real customer is not.
New CartAbandonment table, including the recovery-attribution columns (recovered/recoveredAt/recoveredOrderId/recoveredValue) the original code referenced but had nowhere to store.
tenantId carries DEFAULT 'tenant_katura'. The currently-deployed cron INSERTs without a tenantId; with a NOT NULL and no default that INSERT would fail, be swallowed by the old .catch(), and the send would proceed with no record written — reopening the same loop by a different route. Keep the default until the hardened cron is confirmed live everywhere.
Added ?dryRun=1, which evaluates every gate against real carts and reports who WOULD be emailed without sending or writing anything.
Corrected the docstring, which claimed a 30-minute schedule that never matched the 4-hour registration in vercel.json.
The credit branch now runs FIRST, before anything order-shaped, and returns.
An issuance failure now THROWS instead of being logged and swallowed, so Stripe retries. A charge with no credit issued is the worst silent failure available here.
gift-cards: make the Private Credit Event actually able to take money
Stripe Payment Element rather than a bare card field, so Apple Pay, Google Pay and Link appear automatically. For an email opened on iPhones at night, Face ID versus a 16-digit number IS the conversion rate.
/gift-cards/house-credit/confirmed handles the Stripe return, and deliberately does not restate a balance — the credit is issued by the webhook and this page can render a beat earlier.
The ledger is ABOVE THE FOLD. The email creates desire; the page closes.
Ladder anchors at $500-$5,000 (never $100 first — that teaches the reader that small is normal), each rung carrying a reason rather than a 'most popular' badge: a future gift / a future Katura creation / a significant commission.
Defaults to 'for me'. Recipient details are an optional tick — asking who it is for during an impulse placement is admin work at the worst moment.
Checkout is inline; no redirect to a second page.
Scarcity stated as a fact: a finite House allocation, closing 28 August, whichever comes first. No countdown timer.
$10,000+ routes to 'Speak with Lee' rather than a bigger Stripe button.
findingSchema gains a REQUIRED plainEnglish field — two sentences saying what the technical fact means for their shop, with an everyday analogy only where it earns its place
Writer prompt carries worked examples ('a sitemap is the index at the back of a book — Google has to wander the shelves without one')
checkPlainEnglish is deterministic, because a prompt alone does not hold a rule here: it rejects condescension ('as you may not know', 'put simply', 'most business owners don't realize'), blame ('you should have', 'leaving money on the table'), and jargon that was never actually translated — repeating 'sitemap.xml returned 404' is not an explanation
src/lib/prospecting/brevo-segment.ts + POST /api/admin/prospecting/brevo-sync
scripts/infra/sync-b2b-brevo.ts (standalone; lib/prisma is server-only). Needs pgbouncer=true — Supabase's pooled connection is pgBouncer in transaction mode and Prisma otherwise dies on 'prepared statement s0 already exists'.
Own spec, a SIBLING of the first-touch rules rather than a mode of them: 20-80 words, 1-5 sentences, no greeting, no re-introduction, no bump language, no link, no dollar figure, and NO ask at all on touch 4
Own validator (validateFollowUp) wired into send.ts — before this, 75% of a four-touch sequence would have shipped through no validator whatsoever
Writer sources only from the already-fact-checked deck and must hand over a finding the earlier touches did NOT use; no canned fallback, because an identical filler note to every prospect is worse than sending nothing
Deliverability rules enforced in code: RFC-5322 threading (In-Reply-To + References, so Gmail groups it), greeting suppressed after touch 1, weekday 9-11am in the RECIPIENT's timezone, separate daily lane (FOLLOWUP_DAILY_CAP) drawn first so a sequence can never quietly eat top-of-funnel
Master switch PROSPECTING_FOLLOWUPS_ENABLED, defaulting OFF and deliberately inverted from this repo's usual convention
stampOutreach matched on recipient and stamped the newest THREE sent rows, so one delivery webhook stamped up to three emails and delivery rate could exceed 100%. Now matches brevoMessageId, falling back to one row.
The events panel counted test-mode rehearsals despite the page promising it excluded them. ProspectingEvent has no testMode column and no email relation, so they're now excluded by id.
A/B experiment (free walkthrough vs paid deep diagnostic) + reply detection
writer: variantBEmailRules — sincere growth context, paid deep-diagnostic close, price WITHOUT digits ('a tenth of what we'll charge once we stop taking on new stores this way' — forward tense only, FTC 16 CFR 233), unique link exactly once, free deck never diminished, ONE ask
validator: variant-aware — B requires the exact URL once and no other URL, and must not also carry the free-walkthrough ask; A now rejects ANY URL; both arms reject regular-price claims (normally charge/usually costs/ valued at/a $X value)
auditor + APPROVED_WHY_KATURA.deepDiagnosticStatement: the offer is approved sender positioning, never UNSUPPORTED
fallback: buildSafeFirstTouch closes on the offer in B mode
send.ts revalidates with the same options at send time
console: A/B chip on rows; 11 new coherence tests (278/278 pass)
KPI tiles (prospects/verified emails, real sends, delivered, bounced+spam, replies, queued next 7d), 21-day sent-vs-scheduled timeline, pipeline funnel, 30-day engagement events, AI spend by operation with cost/deck, deliverability health panel
Every number from immutable facts (send rows, webhook stamps, ProspectingEvent, the usage ledger); testMode sends excluded everywhere
Amber banner when sends exist but zero delivery events have ever arrived — the register-the-Brevo-webhook tell
mode: 'online' finds US-based founder-led DTC jewelry brands selling from their own site (marketplace-only sellers, dropshippers, big DTC excluded); optional niche query; results land in the fixed 'Online' territory
Online brands keep the same evidence standard — the rules + JSON shape are extracted to one shared EVIDENCE_AND_SHAPE_RULES const so the two modes can never drift apart
Unknown HQ state accepted (many DTC sites publish none); wrong-country states and website-less candidates discarded
dynamic star ratings, autopilot field explanations, founder voice
0–5 half-step stars derived live from the evidence score (mail-only capped at 3 stars); hover shows tier, meaning, and the next half-star step
Autopilot start form: every field (duration / target decks / AI budget / per-discovery-call batch size / min score) now explains itself in place
Writer rules: intro carries the true founder fact (stepping up into a lead role in the family business, Katura, since 1999) + a plain one-line answer to why the diagnosis is free; band 130–195 words
Validator: band 120–215; new SUPPLICATION filter — warmth never begs (bans 'any chance you'd', 'take a chance on me', 'sorry to bother you'…)
APPROVED_WHY_KATURA.founderStatement: auditor allowlist for the new sender-context claims
Auditor prompt blesses founder framing as sender context, never UNSUPPORTED
Fallback body warmed to match (and still names Katura — test-caught)
+9 coherence tests pinning the layer agreements; 267/267 pass
make the Brevo webhook actually able to receive Brevo
Accept a URL/header token (constant-time compared) as the shared secret; HMAC path kept for providers that do sign. Missing secret still 503s.
Dispatch the TRANSACTIONAL event vocabulary (request, blocked, invalid_email, deferred, unsubscribed, unique_opened, error) — these fell through to default and were silently dropped
blocked/invalid_email now suppress like hard bounces; soft_bounce/deferred/ request/delivered land as ProspectingEvent timeline rows
per-prospect deck engagement — track the hand-raise, not the open
An ATTACHED PDF cannot phone home — every modern viewer blocks remote content inside documents. There is no such thing as a PDF open pixel.
An EMAIL open pixel exists but stays off deliberately: a 1x1 beacon is a Promotions-tab signal (we fought for Primary), and opens are junk data anyway — Apple Mail prefetches every image (false opens), Gmail proxies the rest. Opens flatter; replies and clicks are true.
pdf-data: footer link -> /api/proposal-view/{proposalId}/go. PDFs render at send time, so every future send carries this with no regeneration. (The 17 already sent keep the old aggregate UTM link — visible in pilot page analytics, not per-prospect. Cannot be retrofitted.)
go route: proposal-id tokens record a DECK_LINK_CLICKED event against the prospect, then 302 to the pilot page with the same UTM tags.
Console: list payload carries deckClickedAt/deckClicks; rows show a "🔥 read the deck" chip with the full story in its tooltip — the cue for a fast personal follow-up, which is the highest-converting moment in the whole funnel.
auto-approve due-pinned drafts too — a rewritten email resets to DRAFT, and if its slot is already due it was pinned out of the plan, so approving only planned rows stranded it at a past slot forever
Reciprocity: unconditional free value — "the fixes are yours either way".
Interest-based CTA over meeting demand (Gong response data: roughly 2x replies): offer the twenty minutes, never demand them — "reply and I'll walk you through it. Twenty minutes, no pitch."
Autonomy / "but you are free" (Carpenter meta-analysis, 42 studies): one explicit release — "no pressure either way".
Concrete stakes in neutral operational language, no alarm, no blame.
Peer respect as the governing register: the reader has run their store longer than most agencies have existed; assume they know their business.
living schedule — standing re-rank, auto-approve, readable rows
Owner decisions (2026-08-22 evening), all three: 1. THE SCHEDULE RE-RANKS ITSELF. Every cron tick recomputes the whole plan over ALL unsent sendable emails: weekday slots from the top of the score ranking, weekend slots from the bottom. Run discovery on a Wednesday and find a lead that outscores the board -> it takes the next weekday slot and everyone shifts down one. Discovery stays manual; the ordering reacts on its own. Published times stay stable across ticks — the minute-in-hour is a hash of the email id, not a fresh random draw, so times only move when the RANKING moves. Refused sends are pinned out of the re-plan for ~20h so their backoff holds instead of hot-looping. 2. AUTO-APPROVE (PROSPECTING_AUTO_APPROVE, default on): every scheduled, fact-check-passed draft is approved automatically — by the cron for new arrivals, and backfilled now for the existing board (45 approved on top of the prior 17). Every send-time gate still runs at send. Set the env to "false" to restore per-email human sign-off. 3. READABLE ROWS. The single-line row let the action cluster crush the name to zero width — the board read as anonymous chips. Rows are now two-line cards: line 1 is the NAME (truncating, never wrapping) + score + status + Open; line 2 is meta (location
reviews
email
campaign
evidence
AI cost) with the send-time badge and actions on the right. 249/249 tests, scoped tsc clean, eslint clean.
"your homepage capture showed 0 visible prices" — the capture pattern only covered the/our/a as determiners, so "your"/"this"/"that" slipped by.
"0 price(s) in product-page HTML" — the parenthetical plural is pure machine text; nobody types "price(s)" in a one-to-one email. New MACHINE_PLURAL check.
The owner's cadence, now automatic: Mon-Fri: one send per hour, 8 AM-8 PM (12/day), BEST leads first — the highest score gets Monday 8 AM. Sat-Sun: one send per hour, 10 AM-10 PM (12/day), WORST leads first, starting from the very bottom on Saturday. All hours are the RECIPIENT'S own wall clock (STATE_TZ; Eastern fallback), and the minute within the hour is randomized so the pattern never looks mechanical. assignSendSlots (schedule.ts) is a pure function over one ranking consumed from both ends
weekday slots from the top, weekend slots from the bottom
with Canadian provinces excluded up front (CASL; a slot would be a lie the send gate exposes later). 10 deterministic tests pin the windows, the orderings, the timezone math (incl. UK/BST), and the CASL skip. /api/cron/outreach-send (every 5 min): 1. assigns slots to newly sendable emails, APPENDING after the last planned slot so published times never shuffle; 2. sends due emails
ONLY those a human has APPROVED. A slot is a plan, not consent: a DRAFT that reaches its time just waits, shown as "missed
approve to send". Gate refusals are recorded and pushed +24h so the cron never hot-loops on a blocked email. Every send still passes the full gate chain (fact-check, suppression, CASL, twin guard, daily cap, attachment integrity, TEST_MODE reroute). Every lead row now shows its plan: "sends Mon, Aug 24, 9:46 AM (their time)", red when the slot passed un-approved. scheduledFor was an existing unused column
no migration needed. DAILY_SEND_CAP default 10 -> 12 to match the cadence (the scheduler is the pace; the cap is the backstop). Initial assignment run against production: 69 leads scheduled Sat Aug 22 -> Wed Aug 26, zero skipped. Saturday opened with the three lowest scores; Monday 8 AM opens with the 90s. 236/236 tests (10 new), scoped tsc clean.
repair near-misses instead of discarding researched emails
repairSubject: takes the first real clause, caps it, falls back to a plain note line only if nothing usable survives.
repairSelfDescription: splits the welded appositive into two sentences.
Retries go 2 -> 3, each carrying the specific failures back to the model. Anything still failing is a real content problem and still gets the fallback.
`jewell?ery` matches "jewellery" and "jewelery" but NOT the American "jewelry" — so every one of these rules was half-blind on US prospects, which are most of the board. Now `jewell?e?ry`.
GARBLED_SELF_DESCRIPTION matched the CORRECT predicate form ("We're a jewelry house ourselves, and we built this for our own store first") as well as the broken appositive one ("at Katura, a jewellery house ourselves, which is why"). It would have rejected 52 perfectly good emails on the next generation. Narrowed to require the comma before the article, and the correct form is now asserted ACCEPTED and never rewritten.
build the cold email to land in Primary, not Promotions
From name is now the PERSON ("Zachary Rosser"), matching how the body signs itself. "Zack — Katura" reads as a brand to the recipient and to the tab classifier alike.
List-Unsubscribe + List-Unsubscribe-Post (one-click), which Gmail's and Yahoo's bulk-sender rules expect — its ABSENCE is the violation.
Brevo's open-tracking pixel and link rewriting switched off (X-Mailin-Track/-Track-Links). A 1x1 beacon and rewritten redirect links are two of the loudest "this is marketing" signals available, and neither is worth anything on one-to-one mail whose replies we read by hand. (Brevo needed a `headers` field to carry any of this; it now has one.)
Subject-only failures are now REPAIRED rather than fatal. The model pasted a 16-word page title into Farrah's subject and the whole researched body was discarded for it; repairSubject takes the first real clause, caps it, and falls back to a plain note line.