Dock Blocks · Rep Digest

How the digest should work, end to end

Today the data is rendered to HTML, then scraped back out of that HTML, then rendered again. Every defect found this week lives in that round trip. This is what replaces it.

Drafted 2026-09-25 Internal · not client facing Supersedes nothing yet: this is the proposal
The target

One contract, one renderer, one release

Data crosses a typed boundary once. Anything missing or mistyped stops there, loudly, instead of arriving as an empty value that reads like a quiet day.

01 · Njui Source systems Zoho and Apollo, queried directly. No intermediate page.
02 · Njui Emit typed data One document per rep per day. Named fields, declared types, explicit nulls.
03 · The boundary Validate, or refuse Schema check. A missing field or wrong type fails the build. Nothing renders on a guess.
04 · Us Render Matt's approved design, filled from fields. No parsing, no regex, no markup assumptions.
05 · Us Release and send Held until released. Brevo delivers only what a human let through.
dockblocks-data-ops the contract this repo
The change

What actually moves

The rendering stays ours, so Matt's approved design is untouched. What disappears is the layer in between.

Today round trip

01Njui queries Zoho and Apollo and renders HTML pages.
02We fetch those pages and scrape the numbers back out: 291 lines, six parsers, 89 regular expressions.
03A pattern that stops matching returns None, which renders as absent, which reads as a quiet day.
04We render again into the approved design.
05Checks pass with known gaps by design, so green never meant complete.
06The send fires unless something stops it in time.

Proposed typed contract

01Njui queries Zoho and Apollo and emits data, not a page.
02We read named fields. Nothing is parsed out of markup, so markup can change freely.
03A missing or mistyped field fails at the boundary and names itself. It cannot render as empty.
04We render the same approved design from those fields.
05A gap is a declared absence in the contract, visible before the build, not a surprise in the output.
06The send waits for release. Missing the window sends nothing.
Why

Every one of these came from the round trip

Found this week, all the same shape: a pattern stopped matching, returned nothing, and nothing was indistinguishable from nothing to report.

What brokeHowUnnoticed
Abandonment guard Upstream changed "aging 259d" to "open since April 2025". The pattern matched nothing, so the guard could not execute. 18 days
New potentials section Same dead field, second site. The error was caught and discarded, so every row fell through and the section never labelled. 18 days
Caller time Pattern expected the phone link before the name. Real markup puts the name first, so it never matched for a named caller. unknown
Company goal check Assertion matched HTML tags against text that had its tags stripped. It could never pass, and it blocked the send. 1 day
Size guard Examined whichever local folder sorted newest. Reported clean on data twelve days old. unknown
The common cause

None of these failed loudly. A regular expression that stops matching does not raise an error; it returns nothing, and nothing looks exactly like a quiet day. A typed field cannot do that: it is present with a declared type, or the build stops and says which field is missing.

Sending

Held by default, released on purpose

Today the send proceeds unless something interrupts it, so every failure is a stop that arrived too late. Inverting that makes lateness harmless.

Built

The day's emails are rendered and checked. Nothing is addressed to anyone yet. This can happen as early as the data allows.

Released

A person releases the day. Until that happens the mail does not go. No release, no send, and the miss is silent rather than damaging.

Sent

Brevo delivers. The release is recorded with who and when, so what went out is answerable afterwards.

The difference that matters

A pause has to arrive before the send. A release has to arrive before the send too, but if it is late, nothing has gone. The failure mode moves from "mail went out wrong" to "mail did not go", which is recoverable within the hour and visible immediately.

Open

Decisions this needs

These shape the work and none of them are engineering calls.

Who renders

Njui emits data and we render, keeping Matt's approved design in our hands. Or Njui renders the final email and we only send, which removes our renderer entirely and moves design changes to his repo.

Who releases

You, every morning. Or the reps release their own. Or an automatic release on a clean build with a human release required only when a check fails.

What a gap means

Today a known gap does not block a send. Under a contract, a field can be declared absent on purpose. Does a declared absence still send, and does an undeclared one stop everything.

Cutover

Field by field, with both paths live and compared until they agree. Or a single switch on an agreed date. The first is slower and provably safe; the second is faster and trusts the schema.