Wycombe Abbey International School Bangkok

School systems

KEYSTONE App

About this app

Monkey Mail
What it is, and why it exists

A bulk email tool built for the Communications Team at Wycombe Abbey International School Bangkok. It writes to families and staff from the school's own contact records, sends every message individually, and keeps an account of exactly who received what. This page explains how it was built, by whom, in what time, and what it replaced.

01The what

What Monkey Mail actually does

A member of the Communications Team writes a newsletter out of blocks — a banner, text, story cards, buttons, an events list — picks who it goes to, sends a test to themselves, and presses Send. Every recipient gets their own separate email, addressed only to them.

Underneath that, four things are happening that a general-purpose email tool does not do. The audience is a live query against iSAMS, the school's contact database, so a pupil who enrolled this morning is already in the Year 7 parent list this afternoon. The message is compiled into email HTML that survives Outlook, Gmail and iOS Mail rather than into a web page that happens to be emailed. It sends through the school's own Microsoft 365 tenancy, so the message comes from communications@wycombeabbey.ac.th and never leaves the school's systems. And afterwards it reads the bounce reports back out of that mailbox, works out which address failed and why, and tells you which parent to ring.

02Origin

Where the idea came from

Monkey Mail was Rolly Alfonso-Maiquez's idea. He is the school's Head of IT, and it came from working alongside the Marketing and Communications team rather than from a brief handed down to them.

The pain points were ones he had heard about, watched and run into himself: the work of getting a single message reliably to every parent, and to external contacts, and to all staff, using tools that were never designed for a school's contact records or a school's obligations. Knowing which of those frictions were worth building around — and which were just how email is — is the part that could not have been specified from outside.

He also came to it with practice. By September 2026 he had built more than twenty KEYSTONE apps for the school, so the first conversation started from a realistic idea of what a working first version needed to do and what could wait.

And he brought a method. BUILD is a five-step design-thinking framework for turning a school problem into something that works — Begin with a real problem, Understand the need, Iterate with confidence, Launch a useful first version, Develop it with others. This build followed it closely enough to be a fair demonstration of it: it began with one frustration, it was clarified with the people who would use it, it was corrected many times in the space of a day, it launched as a working link that had sent to real families, and it is now being developed with the team that sends from it.

The BUILD model: rolly-folsea-2026.ateneo.workers.dev — Dr Rolly Alfonso-Maiquez, presented at FOLSEA 2026, Jakarta.

03Process

How it actually got built

This was built in the open-ended, conversational way people now call "vibe coding" — describing what was needed in plain language, watching it get built, using it immediately, and correcting course. One person working with Claude Code, in one continuous conversation.

It opened at 10:59 in the morning of 18 September 2026, and not with a request for code. The first message set out what the app was for, who would use it, and — the part that mattered most — how it should be structured: a separate Worker rather than another module bolted onto App-25 Pupil View, because only the Communications Team would use it and Pupil View should not have to carry it. The audience would come from Pupil View's existing iSAMS binding instead of a second connection to the school's records. And the editor had to be, in his own capitals, ÜBER GOOD — good enough to take an exact paste from Word with the images intact. That message ends with two words: "Q&A ME".

The first commit came four and a half hours later, at 15:25. Those four and a half hours were spent deciding things, and every structural decision taken in them is still standing: the separate Worker, the service binding, the single-recipient send. About forty-five minutes after that first commit the first test email arrived in a real inbox. That evening a real campaign went to two people. The following morning it went to fourteen Nursery families, and just after eleven it went to three hundred and forty-four.

Nothing in that sequence was a rewrite. The shape of the app changed constantly — story cards grew multiple buttons, campaign types acquired their own unsubscribe rules, the preview split into phone and desktop, the whole type system was re-derived from a single base size after a long argument about what a newsletter should look like on a phone — but the send path written in the first hour is the send path that delivered all 360 emails.

The work was not linear and it was not passive. A large share of it was the product owner using the thing, disliking what he saw, and saying so. "REALLY?????????" is a real message in the transcript, about a reading-time estimate that claimed five words took a minute to read. "waste of space!" is another, about a settings panel. "it's like another language ???" is what produced the plain-English notice that now sits above the font controls. The app is noticeably better in the places where the feedback was bluntest.

04Timeline

From empty folder to 344 families

When (Bangkok)What happenedEvidence
18 Sept, 10:59Ground zero — the first message: what to build, and how it should be structuredconversation log
18 Sept, 15:25First commit — auth, audience binding, send queuegit history
18 Sept, ~16:10First test email to a real inboxaudit log
18 Sept, 18:01First real send — 2 recipients, 2 delivered, 4 secondscampaign record
19 Sept, 11:07Nursery send — 14 of 14 delivered in 10 secondscampaign record
19 Sept, 11:31Whole school — 344 of 344 delivered, 0 failures, 16m 54scampaign record
19 Sept, 12:30Latest commit — resend-to-failures and pre-send link checkinggit history

Twenty hours and twenty minutes from the first line of code to the whole parent body; twenty-four hours and fifty minutes from the first message. Not that many hours of work — the clock includes a night's sleep — but that much calendar. All times are Bangkok, which is where every one of them happened.

Hours actually spent

The calendar span flatters the build, because it contains a night. What the conversation log records is 6,138 timestamped events across six sittings, and those sittings can be measured rather than estimated.

Sitting (Bangkok)LengthWhat it was
18 Sept, 10:59 – 11:320h 33mThe opening Q&A — what to build, and how to structure it
18 Sept, 13:43 – 16:443h 01mScaffolding, the audience binding, the send queue, the first test email
18 Sept, 17:31 – 20:403h 09mThe editor and the compiler; the first real send, to two people
18 Sept, 21:25 – 23:021h 37mEvening
19 Sept, 00:16 – 00:550h 39mPast midnight
19 Sept, 06:06 – 13:327h 26mBoth bulk sends, the reporting work, and the writing of this page

About 12 hours of active work, or 16½ hours counting whole sittings end to end, inside a 26½-hour calendar span. The 12-hour figure excludes any gap longer than ten minutes, the ordinary convention for active time; the 16½-hour figure measures each sitting from its first event to its last. Both come from timestamps rather than recollection. An earlier version of this page estimated 25 to 30 hours — that was a guess, and it was roughly double.

The shape of that table says more than the total. One sitting ends at 00:55 and the next begins at 06:06, and two of the six fall squarely outside a working day — which is the concrete version of the point made further down about why none of these hours carry a price.

What exists now

113commits
54source files
20,947lines of code, CSS and tests
228automated tests, all passing
8database migrations
28test sends before the first bulk send
360emails delivered in production
0send failures, ever
6bounces caught and attributed
3people with accounts

The build figures above are regenerated from git every time this page is deployed — last updated 19 September 2026 at 19:14 Bangkok — so they describe the app as it stands rather than as it stood when this was written. The production figures below were read from the live database on 19 September 2026 and are refreshed by hand. None of it is an estimate unless the page says so.

05Progress

Zero to eighty, and never to a hundred

The product owner has a way of describing how a build actually goes, and it is worth setting the record against it, because this build followed it almost to the letter.

"it takes just a little time to get from your idea (zero level) to 80% and you've basically got a working app. then the next 10% takes longer since this is your big edits, big tweaks. then the last 9% is further tweaking. i feel i never really reach 100% since there's always something to tweak so i don't aim for perfection and finality — just to a point that users are happy, functions are working, things are working well."

"once i reach 80/85% — i test with colleagues to figure out how people use them and see what can be improved, then i go back to vibe coding and then test and have others test again."

80% — 18 September, 18:01

Seven hours after the first message, and about four hours of actual work into it, the editor existed, the compiler produced an email that survived Outlook, and a real campaign went to two real people. That is "you've basically got a working app", and it arrived roughly when his model says it does.

Then the part the model insists on: other people

What follows 80% in his description is not more building, it is testing with colleagues — D, develop it with others. That is what the record shows: twenty-eight test sends, tests to colleagues and to demonstration parents, and a review by the Marketing and Communications colleague who would actually be doing the sending, run over LINE. It is also where six of the nine defects on this page were found. Not one of them was found by reading code.

90% — 19 September, 11:48

The "big edits, big tweaks": the whole type scale re-derived from a single base size after a long argument about what a newsletter should look like on a phone, the preview defects, the confirmation gate, campaign types carrying their own unsubscribe rules. Then the thing it was built for — 344 of 344 delivered, no failures.

99% — 19 September, and continuing

The "further tweaking", almost all of it prompted by what a real send revealed rather than by anything anyone planned: bounce classification that tells a full mailbox apart from a wrong address, human clicks separated from a link scanner's, a send report that names who to ring, a standing list of problem addresses carried between campaigns, resend-to-failures, and link checking before send.

100% — not reached, and not aimed at

By his own account there is no such point: "there's always something to tweak so i don't aim for perfection and finality — just to a point that users are happy, functions are working, things are working well." By that definition Monkey Mail was finished at 11:48 on 19 September, and has been improved every hour since.

The number that makes the model convincing is the split. The first 80% took about four hours of work. Everything after it has taken about eight more, and is still going. The last fifth of this app has cost twice what the first four fifths did — which is exactly the shape he describes, and the opposite of what "it was built in a day" suggests to most people.

Those percentages are a reading of the record against his own model, not a measurement — there is no scale that tells you an app is 90% done. The times and the hours behind them are measured; the labels are a judgement.

Quoted from rolly-folsea-2026.ateneo.workers.dev/about, where the wording is published in full alongside the question that prompted it.

06Pulse checks

Reality checks, at intervals

Every so often the product owner stops and asks where the build actually is. The rule for answering is that nothing gets estimated if it can be counted — and both times so far, the counted answer has differed from the felt one.

19 September, afternoon — "how can I be super sure they arrived?"

The honest answer, that afternoon, was that the app could not say. sent meant Microsoft had accepted the message and taken responsibility for it. Bounces supplied the negative signal, but silence from a receiving server is weak evidence: a message quietly dropped, or filed straight into a junk folder, looks exactly like one read over breakfast.

Checking that answer properly turned up two things. The first was a defect. Verifying the report against the database rather than reading it back showed the engagement figures were wrong — the report claimed 253 clicks by people and no machine activity, when the database held 45 human clicks and 210 by a link scanner, correctly classified all along. Every click query was missing the filter its sibling open queries had, and it had been naming a scanner in Hong Kong as the first person to read the newsletter, nought seconds after send.

The second was that the question had become answerable since the last time anyone checked. Microsoft had released a message trace API on Graph, generally available since January 2026, supporting an application permission — which is exactly how this app authenticates. It was built that afternoon. The first real run appeared to fail: the browser gave up waiting, because the code was issuing one database write per recipient and a whole-school send meant 344 of them in sequence. The data had landed perfectly well. Only the patience had run out.

Exchange statusRecipientsWhat it means
delivered338Reached the recipient's mail server
failed5Exchange could not deliver it — the same five as the bounces
pending1Still being retried; Exchange keeps trying for two days
filed as spam0Nothing was accepted and then quietly junked

Read from Microsoft's own message trace at 16:42 Bangkok on 19 September 2026 — the authoritative record of delivery to each recipient's mail server, independent of anything this app recorded.

The five failures matched the five bounces exactly. Two records kept by different systems, for different reasons, agreeing to the address — which is a far stronger statement than either could make alone. And the nought filed as spam is a number that could not previously be reported at all, honestly or otherwise.

The pattern is worth naming: a question the app could not answer became a feature, and the act of checking the old answer found a defect on the way. Neither would have happened if the question had been answered from memory.

19 September, evening — "almost 40 hours, and 95%?"

Two parts to the question, and the record disagreed with both.

On the hours. It felt like forty. It was not.

MeasureHours
Calendar span, first message to now31.9
Whole sittings, end to end, across nine of them17.4
Active work, gaps over ten minutes excluded13.1

Thirteen hours. It feels like forty because it crossed two days and a midnight, which is exactly why the question was worth asking rather than answering from memory.

On the 95%. The sending engine is there or close to it: 360 emails, no failures, two real sends, and Microsoft's own message trace independently confirming 338 delivered and nothing filed as spam. Bounces classified, households reachable, links checked, engagement honest about machines.

The product is not, and the gap turned out not to be code at all. The audit log answers the question nobody had asked — who has actually used this?

RoleWhat the audit log shows
Admin (the product owner)Everything — three campaigns, three sends, twenty-eight tests
Sender — the colleague whose job this isSigned in once. Nothing since.
EditorHas never signed in

Both real sends were pressed by the person who built it, not the person whose job it is. The colleague who will do the sending reviewed and edited the September newsletter, over a chat app — but has never created a campaign in Monkey Mail, never run a test send, never seen the confirmation dialog, and never pressed Send.

The BUILD model above is explicit about what follows 80%: test with colleagues and watch how they use it. That happened for the newsletter's content and not for the app. And every defect listed on this page was found by a person looking at real output — not one by reading code. So the largest untested surface left is not a missing feature. It is a trained user operating the thing unaided.

Which puts the honest figures at roughly 99% for the engine and 90% for the product, with most of the remainder discoverable only by watching someone else send the October newsletter start to finish while the person who built it sits on their hands and writes down every hesitation. That is the next action, and it will produce more improvement in an hour than a day of code review would.

Pulse checks get added here as they happen, with whatever they turned up, including the parts that were uncomfortable. A check that only ever confirms the current belief is not a check.

07Assessment

What the product owner actually contributed

"Vibe coding" invites the assumption that someone described a wish and a machine granted it. That is not what the record shows. The code was written by Claude; almost everything that made the code correct came from the product owner.

Product owner and lead designer is the accurate description of the role Rolly Alfonso-Maiquez held here, and both halves matter. As lead designer he decided what the thing should look like and how it should behave — the block model, the phone-first preview, the type scale, the wording of the warnings. As product owner he did the job that in a larger team nobody else is allowed to do: he set the scope, ranked what got built next, gathered and weighed feedback from the colleagues who would use it, and accepted or rejected the work. Someone has to decide that a thing is good enough to send to 344 families. That decision was his, and he made it only after watching fourteen arrive.

He set the non-negotiables before any code existed

Two rules were stated at the outset, in capitals, and never relaxed: never send as CC, because families must not see each other's addresses; and never send to anyone without being told to. Both are now enforced in code and guarded by tests. The send function takes exactly one address and has no code path that can attach a second. Those rules did not emerge from the build — they constrained it from the first hour, which is why they hold.

He insisted on a staged send, and it paid for itself immediately

Given a working app and 344 waiting families, the decision was to send to fourteen Nursery families first and look at the result. That was his call, not a suggestion he accepted. It surfaced a case nobody had modelled: two parents with one child in Nursery and another in a different year group, who would have received the same newsletter twice. The app now detects the overlap before every send and offers to skip the people who already have it. A single-shot send to 344 would have found that bug by annoying two families.

He found most of the real defects — by looking, not by reading code

Nine defects survived into something a parent could have received, or were only findable in real data. Six of the nine were caught by the product owner, looking at output.

DefectWhy it matteredFound by
"Desktop" preview showed the phone layoutThe desktop preview had never once been correct"do i actually get a preview of laptop view?"
Dozens of phantom dividers in the previewApp styling was bleeding into the email preview"why bazillions of horiz dividers?"
Preview restyled itself on hoverSame cause, different symptom"before i hover and after i hover. WHY?"
Editor misreported its own font sizesA 15px block rendered larger than a 16px one"TEXT 1 = 16px and STORY CARD = 15px but looks larger?????"
Every timestamp was seven hours outUTC shown unlabelled as if it were local time"it's 10:01 AM in Bangkok"
42 of 44 clicks were a link scannerEngagement figures were largely fictional"clicked data is good — but from where and when?"
Subject never reached the email bannerAffected every campaign ever createdinvestigation prompted by a question
Preheader text was never actually sentSame root cause, found in the same passinvestigation prompted by a question
An empty gold button shipped in the newsletterStandalone buttons were never validatedfound while repairing the live campaign

Scroll the table sideways to see every column.

Four further defects were introduced by Claude during the build and corrected the same day, three of them after the product owner pushed back on a confident claim that turned out to be wrong. One of those was the assertion that email clicks cannot be faked. They can. Forty-two of them were.

He tested like someone who had something to lose

Twenty-eight test sends before the first bulk send. Tests to himself, to colleagues, and to demonstration parents. Approval ran through a LINE chat with the Marketing and Communications colleague who does the sending, who reviewed and edited the September newsletter before it went out. Before pressing Send on 344 he asked whether the window had to stay open, whether the laptop had to stay awake, whether both parents would each get their own copy, and whether the comma-separated list on the confirmation screen meant the addresses would be visible to each other. Every one of those is the right question. The last one is the question that would have caught a CC bug if there had been one.

He corrected the machine when the machine was wrong

Told that user roles had never been configured, he replied "huh?" and produced the table showing all three roles configured since 18 September. He was right. Shown sample data for a report, he spotted instantly that it had borrowed a real family's name and invented a spouse and a phone number to go with it, and said so at once. Fixing that exposed a real bug underneath: the household lookup was grouping contacts by failed address rather than by family. A fabricated example had been hiding a real defect.

The honest summary. The technical judgement in this build is Claude's. The product judgement, the risk judgement and the quality bar are his. He knew what the output had to look like to a parent on a phone, he refused to accept anything that looked wrong even when told the code was correct, and he would not let 344 emails go until he had watched fourteen arrive. That is the difference between a demo and a system that has sent to every family in the school without a single failure.

08Insight

What the defect record actually shows

There is a pattern in the nine defects and it is worth naming, because it predicts where the next one will be.

Almost every defect lived at a seam between two components that were each individually correct. The editor knew the subject line; the compiler knew how to draw a banner; the subject never travelled between them. The preview knew it was 600 pixels wide; the email knew it switched to phone layout below 620; nobody had compared the two numbers. The app had a stylesheet; the email had a stylesheet; injecting one into the other drew dividers nobody asked for. The tracker knew how to rewrite links; the archive URL was a token that could not be resolved at click time.

Meanwhile the part everyone was frightened of — the send path, the code that could email the wrong thing to 344 families — has never produced a production defect. It got the tests, the review, the staged rollout and the paranoia, and it has a perfect record. The bugs went where the attention wasn't.

The practical lesson for anything built this way: test the joins, not the parts. A component you can describe in one sentence is usually right. A sentence that needs the word "and" to connect two components is where to look.

09Cost

What a commissioned build would have cost

If the school had gone to a development firm and asked for an email platform that does what Monkey Mail does — the live iSAMS audience binding, the block editor, the email compiler, individual personalised sending through the school's own mail system, bounce classification and the reporting — this is roughly the shape of that project.

PhaseWhat it coversRoles involvedTypical time
1. Discovery & requirementsInterviewing Communications and Marketing, defining audiences, campaign types and rolesPM, Tech Lead, Designer≈2 weeks
2. Architecture & contact integrationData model, Entra sign-in, the live iSAMS audience binding, consent and suppression modelTech Lead, 2 Backend, DevOps≈4 weeks
3. The email compilerBlocks to HTML that survives Outlook, Gmail and iOS Mail; phone layout; brand templates2 Frontend, Designer, QA≈5 weeks
4. The editorBlock editing, rich text, images, live phone and desktop preview2 Frontend, Designer≈5 weeks
5. Sending infrastructureMicrosoft Graph sending, pacing, resumption, per-recipient personalisation, suppression enforcement2 Backend, DevOps≈4 weeks
6. Deliverability & reportingBounce ingestion and classification, open and click tracking, machine filtering, send reportsBackend, Data, QA≈4 weeks
7. QA, security & data protectionAccess-control audit, PDPA review, deliverability testing across mail clientsQA, Security, DPO, all≈3 weeks
8. Deployment & trainingRollout and training the Communications TeamPM, Tech Lead≈1 week

Scroll the table sideways to see every column.

Run in sequence that is 28 weeks. With the usual overlap between phases, a team of about eight would ship a comparable version one in roughly six to seven months. Every one of those phases happened here too — compressed into two days and folded into one conversation instead of eight scheduled handoffs.

Head-to-head — build it this way, commission it, or buy it

Three ways the school could have arrived at a bulk email tool. The third column is Mailchimp Standard, the tier that realistically matches this team's needs, included because a cost comparison that leaves out the option most schools actually take is not a comparison at all.

MetricThis buildCommissioned teamMailchimp Standard
Team size1 person, working with Claude Code≈8 people across 7 rolesNone — you buy it
Calendar time to first real send≈20 hours≈6–7 monthsA few days
Developer time≈25–30 hours≈4,700 person-hours≈20–40 hours to set up, then list maintenance for ever
Tooling cost to build it≈$2–3 of subscription time — see the breakdown belowIncluded in the labour figure$0
Cost of the developer's timeDeliberately not priced — see the note below≈$580,000 in fully-loaded labourNone
Cost to run, per year≈$0–60 of Cloudflare; sending is included in the school's Microsoft 365Same hosting, plus a support retainer≈$480 at list price, rising past 500 contacts
Five years of running cost≈$300 of hosting≈$300 of hosting, plus five years of support retainer≈$2,400 and upward as the roll grows
Cost to change somethingSame day, in conversationA change request and a quoteA roadmap request, or not at all
Audience stays correct by itselfYes — a live query against iSAMSYes, if specified up frontNo — a list someone maintains
Operational email with no unsubscribeYes, by campaign typeYes, if specified up frontNot permitted
Where parent addresses liveOnly in the school's own systemsOnly in the school's own systemsUploaded to Mailchimp

Scroll the table sideways to see every column.

Why this page puts no price on the developer's time. Monkey Mail was built by the school's Head of IT, and the hours split into two kinds. The ones inside the working day are work, and they are accounted for as work. The rest — evenings and weekends — are something else: continuing learning, and the satisfaction of helping his own team get more accurate and more efficient at something they do every week.

Putting a rate on either would mean publishing a salary, which is nobody's business. Putting a rate on the second would also simply be wrong: hours given because someone wanted to keep learning are not hours the school bought.

So the comparison above is made in hours, not currency: roughly twenty-five to thirty of them against a modelled four thousand seven hundred. That is the honest unit, and it shows the difference perfectly well on its own. The commissioned-team column keeps a dollar figure because it prices a hypothetical team at published market rates — it is nobody's actual pay, least of all his.

Read that table honestly and Mailchimp wins the top half of it. It is far cheaper than commissioning a build, enormously faster than either alternative, needs no developer, and ≈$480 a year is not a number that troubles a school budget. Anyone arguing this project on cost alone is arguing a weak case.

The decision is made in the bottom four rows, and none of them are about money. Mailchimp is a fast, cheap way to get a bulk email tool. It is not a way to get this one — a tool where the audience cannot drift because nobody maintains it, where a fee notice cannot carry an unsubscribe link, and where no parent's address ever leaves the school. The build was not undertaken to save $480. It was undertaken because the $480 product cannot do those three things, and they are the things a school needs most.

None of these figures are invoices. This build was never billed by the hour, and the commissioned-team column is a modelled benchmark — a hypothetical staffing plan priced with real published salary data, not a quote from an actual firm. The person-hours and phase durations are this page's own assumption. The labour cost uses a blended fully-loaded rate of about $124 per hour, derived from 2026 Bay Area role averages loaded by the 1.4× multiplier commonly cited from US Small Business Administration guidance. The "this build" column is an honest approximation of unlogged activity, and its hours are the only figure in it worth leaning on. Both columns exist to make the comparison legible, not to the dollar.

What the tooling actually cost

The build ran on Claude Code, on a Team Premium seat. Anthropic prices a Team standard seat at $20 per user per month billed annually, or $25 billed monthly; a Premium seat is $100 annually or $125 monthly, and carries five times the usage of a standard seat. The seat costs five times as much and gives five times the room.

That seat is not a Monkey Mail expense. It is a standing subscription the school already pays for, shared across more than twenty KEYSTONE apps, and it would have cost the same this month if Monkey Mail had never been built. Charging the whole $100 to this one app would be dishonest. What can be attributed is the share of it this build actually used.

BasisShare of one monthAt $100/mo annualAt $125/mo monthly
Usage allowance consumed≈1.8%≈$1.85≈$2.30
Elapsed time (under 30 hours of a 730-hour month)≈4.1%≈$4.10≈$5.15

Scroll the table sideways to see every column.

Two ways of prorating the same seat. The usage line is the meaningful one — a subscription is consumed by how much of its allowance you draw, not by how long the laptop is open — and the time line is included because it is the figure people reach for first. They land in much the same place, which is reassuring rather than surprising. Call it two to three dollars of subscription time.

The usage figure rests on one measured number and one estimate, and it is worth being exact about which is which. The second day of the build — both bulk sends, roughly half the commits, the reporting work and this page — was read directly from the account and came to 4% of that week's allowance. The first day fell inside a weekly window that has since reset and can no longer be read, so it is assumed to be of the same order, giving roughly 8% of one week, or about 1.8% of a month.

The number that matters is not the two dollars. It is the 4%. Building an entire email platform, sending it to every family in the school, and writing this page consumed a single-digit percentage of one week's allowance on one seat. Capacity was never the constraint on this project, and no amount of extra subscription would have made it faster. The constraint was how quickly one product owner could look at something, decide it was wrong, and say why.

Role-rate sources: Indeed — Software Engineer, Levels.fyi — Software Engineer, Indeed — Product Manager, Built In — Engineering Manager, Indeed — UX Designer, Glassdoor — Software QA Engineer, Indeed — DevOps Engineer, and the 1.4× fully-loaded-cost multiplier commonly cited from SBA guidance. Hosting: Cloudflare Workers pricing. Claude Team seat pricing and the 5× Premium usage multiplier: claude.com/pricing, checked 19 September 2026; the 4% weekly-allowance figure was read from the account the same day. All figures pulled September 2026; salary data moves, so treat them as directional.

10The log

This page is part of the work

It was written in the last sitting on the table above, on the afternoon of the day the newsletter went to every family — which makes it the only part of this build documented while the thing it describes was still warm.

That was deliberate. Most of what is on this page could not have been reconstructed a month later: which question found which defect, what the confusion actually was before it was resolved, how long a sitting ran, the fact that one ended at 00:55 and the next began at 06:06. Written in arrears it would have become the tidy version, in which everything was foreseen and nothing was wrong for long. The defect table in particular only exists because it was written down while it was still slightly embarrassing.

So this is a log as much as an explanation, and it is kept rather than finished. The build figures above — commits, files, lines, tests — are regenerated from the repository every single time this page is deployed, so they describe the app as it stands today and not as it stood when the first version of this page was written. The production figures are refreshed by hand and carry the date they were read. Anything estimated says that it is estimated.

Which is the same idea as the section above, applied to the page rather than the app. If the app is never at 100%, an account of it cannot be finished either. This one is expected to keep changing, and every deployment updates it.

11Stack

What it runs on

A single Cloudflare Worker, with SQLite for the campaign records, object storage for images, and a cron trigger that drains the send queue a batch at a time and sweeps the mailbox for bounces every five minutes. Sending is Microsoft Graph against the school's own tenancy. The audience comes from App-25 Pupil View over a direct service binding — an in-process call with no URL, unreachable from the internet, which cannot write to Pupil View's data. Monkey Mail holds no copy of the pupil database and has no iSAMS credentials of its own.

No IP address is ever stored. Open and click tracking records the country, city and connecting network that Cloudflare reports, and nothing that identifies a device. That was a deliberate choice, made before the tracking was built.

Enter Monkey Mail →