Pivot

Dock Blocks, lifecycle

Two surfaces, and our version of the architecture

1 October 2026

What a person looks at to run lifecycle, and what sits behind it. Every row below is real: the journeys are catalogue rows, the audience is win-back's actual pilot, the blocks are the eight that exist. Nothing here is a invented screenshot.

8
journeys, any state
4
live
2
preflight failing
?
people in flight

Part 1

1Our architecture, against theirs

LimeJourney runs Kafka, ClickHouse, Redis, Postgres and Temporal to do this. We do it with a nightly warehouse, files in git, and one durable engine. The difference is not sophistication, it is volume: their diagram is built for event streams we do not have.

SOURCESZoho CRMsystem of recordcontacts, ordersShopifyorders, cartsBrevo, Apollosends, opens, repliesGA4, Ads, Aircallsessions, callsTHE CDP, NIGHTLYMedallion warehousebronze, silver, goldDagster, ~30 assetsperson_map 02:30consent_stateperson_lifecycle 03:00lead_cause, deal_causecalls_attributedDEFINITIONJourney files, in gitentry, nodes, exitsreviewed in a PRcontent as a pointerBlock library8 blocks, 2 shapesMJML, measured tokensEXECUTIONDBOS Transactdurable per personPostgreswaits, branches surviveThe gateeight refusalssuppression, consentfrequency, rampBrevolifecycleMailtrapvolumeEvents returnwebhook persisted first, then fires a rununsub, reply, click stop the sequence for that personComms ledgerone row per sendwhat the next send readsstate at send timerefuse or sendback into goldWHAT WE DO NOT HAVE, DELIBERATELYNo Kafka. one client, nightly batch is right-sizedNo ClickHouse. gold already answers these questionsNo Redis. nothing needs sub-second lookupNo separate orchestrator. DBOS is the engine
Read it left to right. Sources land in the warehouse nightly, journeys are files reviewed in a pull request, DBOS executes per person, the gate refuses or releases, two senders deliver, and events come back to stop sequences and feed the next send.
The one arrow that does not exist yet

State at send time. The warehouse computes identity, consent and stage every night and our reporting already reads it, but no journey consults it at the moment of sending. That is the same sentence as the shared gate having no production caller, and it is the whole prototype.

Part 2

2The orchestration page

What exists, who is running, who is stopped, and what a launch would refuse right now. Control is three verbs: pause, resume, drain.

Journeys4 live, 2 built, 1 paused, 1 draft pauseresumedrain
JourneyStateAudienceIn flightLast sendNext dueGate
Dormant win-backpausedgate1_pilot (150)0never senton green lightblocked: domain
Reconversionlivereturning leadsunknown2026-09-30on triggerok
Product nurturelive22 Brevo listsunknown2026-09-29on triggerok
Welcome SMSlivenew leads, list 68unknown2026-10-01on createok
Shipped noticeliveorders marked shippedn/a2026-10-01on eventtransactional
Distributor receiptbuiltdistributor uploads0neveron uploadok
SMS drip 2 to 4builtnon-converters0never sentheldheld
Trade show re-engagedraftC13, not defined0nevernot scheduledno audience

Why no add-a-path button. Authoring stays a file and a review. A page that lets somebody add a branch by clicking is a page where nobody can answer what changed last Tuesday, and this estate already has four documents nobody can date.

Launch preflightDormant win-back re-run
Check, re-run on refreshNowWhat it read
Audience resolves and is pinnedpass150 rows, gate1_pilot.csv
Suppression read freshpass18,390 addresses, read at check time
Consent is opt-in, not not-opted-outpasschecked per person
Per-person frequencypasspromo 30d, editorial 7d, SMS 14d, 3 in 30 ceiling
Sending domain authenticatedFAILm.dock-blocks.com has no SPF and no DKIM
Domain warmFAILno sending history on this identity
Preflight receipt under 24hpassre-run on every refresh, not remembered
Version-mismatch sensorMISSINGnot built, so stranded people are invisible

Every line is evaluated when the page loads, never remembered. Two fail today and one check does not exist, which is the honest reason win-back has not sent.

Audiences and segments5 defined
AudienceSizeComputedUsed byNote
gate1_pilot150pinned 2026-10-01Dormant win-backstratified, five owners
gate1_rest1,648pinned 2026-10-01Dormant win-back wave 2not yet released
gate2not exportednevernothingno SQL exists in export_audience.py
returning leadslive queryon triggerReconversionone send per 90 days
22 nurture lists7 to 38 eachunknownProduct nurtureone template, varied

Part 3

3The compose page

What is being built, what each message needs, and what happens when the data is not there. The variants column is the one nobody has today.

Messages2 approved, 2 draft, 1 not started preview all variants
MessageStageBlocksAttributesIf absentVariants
winback touch-1approvedplain text, no blocksrecipient_name, owner_nameabort1
winback touch-2aapprovedplain text, no blocksrecipient_nameabort1
abandoned cartdraftmasthead, greeting, body, item_row, cta, signoff, footerfirst_name, items[], cta_urlomit on itemsup to 6
ambassador invitedraftmasthead, greeting, body, cta, signoff, footerfirst_name, referral_urlrefuse2
seasonal roundupnot startedlist shapefirst_name, items[]omitunknown

Variants is the column that earns this page. An attribute-driven message produces a different body per person, so the defect that ships is in the variant nobody rendered. On the Labor Day send six defects passed every automated check and were caught by eye. This column says how many distinct bodies exist, so review is finite and countable rather than a sample.

Plain text is not a failure state

Win-back shows "no blocks" because it is deliberately plain, reading as a personal note from the CEO. A page that renders that as missing would push somebody to fix it.

Part 4

4What these pages will not pretend to know

Copied from the outbound console, which earned trust by saying this out loud. A dashboard without this section is asking to be believed about everything on it.

  • How many people are mid-journey right now. DBOS holds it; nothing surfaces it yet, and the version-mismatch sensor that would separate waiting from stranded is not built.
  • Whether a live journey's copy matches what was approved. The goldens do that for rep sends and nothing does it here.
  • Open and click rates per journey step. Brevo reports per campaign, not per journey node.
  • Whether a person was suppressed by the Mailtrap path, because that path does not consult the shared gate yet.
  • Anything about Apollo. Cold outbound is a different system and this page does not reach into it.