NOTE

Building a reader without delivering the book

Access, not files, and the decisions behind a small controlled-access publishing system.

18 Jun 2026

Back to notes

This project started with a request that sounded simple.

Could an author give readers digital access to a book without giving them a file they could download and pass around?

The honest answer was: not completely.

Anything a person can read on a screen can eventually be copied, photographed, transcribed, scraped, or otherwise extracted by someone with enough time and motivation.

But there is still a meaningful difference between that and handing every reader a clean PDF.

That difference became the product.

First, I had to remove the PDF

The book was already being sold through other channels.

There was no need for another shop, another checkout flow, or another ambitious publishing marketplace waiting for its first publisher.

The actual need was much smaller:

  • give approved readers access,
  • make the book comfortable to read online,
  • let the author manage that access,
  • and avoid distributing the original file.

A password-protected PDF would have been the obvious answer.

It would also have solved the wrong problem.

Once the reader receives the file, the system has very little control over what happens next. The password can be shared, the protection can be removed, and the same clean copy can travel indefinitely.

So the central decision became:

Access, not files.

The original document would remain private. The application would store the book as structured chapters and deliver only the chapter being read. Every protected content request would be checked on the server, and the reader would display a personalised watermark.

This was not hard DRM.

It was a more honest combination of access control, traceability, and inconvenience for casual redistribution.

Less dramatic, perhaps.

Also more real.

The first access model was too clever

The initial idea used unique redemption codes.

Each physical copy could include a code. The reader would create an account, redeem it, and receive the book in a personal library.

It was tidy on a diagram.

It also introduced code batches, redemption states, lost codes, already-used codes, public registration, and a small claims system hiding inside what was supposed to be a reader.

During the design work, we realised that the person managing access already knew the reader's email address.

That changed the model.

Instead of manufacturing a parallel currency made of book codes, the administrator could grant access directly by email. If the reader did not yet have an account, the system would invite them to create a password. When they signed in, the book would already be waiting in their library.

The flow became:

email
→ invitation
→ account activation
→ personal library

A few boxes disappeared from the architecture.

This was progress.

The best product decision in the early project was not adding a better redemption system.

It was deleting the need for one.

Then the real Word document arrived

Up to that point, the content model looked straightforward.

A book has parts. Parts have chapters. Chapters contain HTML.

Then I tested it with the actual Word document.

The real document became dozens of chapters, with dozens of footnotes and special symbols that had to survive conversion and review.

It also contained wide tables, borders, alignment rules, front matter, embedded assets, and years of perfectly reasonable formatting decisions made for a printed page.

Browsers, unfortunately, are not printed pages.

I used Mammoth as the semantic DOCX-to-HTML foundation. It does a useful thing: it prefers the document's structure over a pixel-by-pixel imitation of Microsoft Word.

That gave me clean headings, paragraphs, lists, and tables.

It did not give me a finished book.

Some footnotes ended up in the wrong place. Certain symbols did not survive conversion. Table borders and cell alignment needed information from the underlying Open XML. Unsupported assets had to become explicit warnings rather than quietly disappearing.

So "import a DOCX" became a proper processing pipeline:

private source file
→ preprocessing
→ semantic HTML conversion
→ document-specific repair
→ chapter detection
→ footnote placement
→ sanitisation
→ validation report
→ draft content

The import did not publish anything automatically.

It produced draft chapters and a report for review.

That distinction mattered. A converter can tell you that it completed successfully. It cannot tell you that a legal or technical book still means the same thing after conversion.

The real document was not an edge case to handle after the architecture.

It was the thing that revealed what the architecture actually needed to be.

A reader is not just rendered HTML

Once the import worked, the next temptation was to declare the reader finished.

There was a table of contents. There were chapters. The text appeared on screen.

Technically, this was reading.

It was not yet a good reader.

The book had to work on a phone, where a wide table can casually make the entire page wider than the screen and take the navigation with it.

Footnotes needed to be available without forcing the reader to lose their position. Long books needed progress tracking. Desktop navigation and mobile navigation needed different behaviour. Tables needed their own horizontal scrolling and, in difficult cases, a full-screen view.

The personalised watermark also had to remain visible without making the book unpleasant to read.

This is where the product became less about "protecting content" and more about respecting the person who was allowed to read it.

A browser-based reader cannot honestly promise that screenshots are impossible.

It can avoid delivering a ready-made source file, make casual sharing less attractive, record meaningful activity, and attach access to an identifiable account.

Security was a constraint on the reading experience.

It was not allowed to become the reading experience.

The reader needed a publishing workflow

A private reader still needs someone to prepare what appears inside it.

That led to a small administration system for importing a document, reviewing the detected structure, previewing chapters, correcting the generated HTML, adding publication metadata, and deciding what was ready to publish.

This was deliberately not a general-purpose CMS.

There is no page builder.

There are no plugins.

Nobody can install a newsletter widget into chapter twelve.

The administration area exists for the actual work around the book: content, access, users, publication state, and diagnostics.

The public side stayed equally restrained. It can present the author and the available publications, but it does not expose the protected content and it does not pretend to be an ecommerce platform.

Again, the useful part was knowing what not to build.

A second distribution flow tested the model

Later, another requirement appeared.

Seminar attendees needed to request access by scanning a QR code rather than being added individually by an administrator.

The easy response would have been to create a separate registration flow for that situation.

Instead, the new request page became another entry point into the same access system.

A reader submits an email address. The existing invitation and entitlement flow decides what should happen next. The new feature does not get its own user model, password system, or special kind of library.

That was a useful test of the earlier decisions.

The access model had started with one book and one distribution process, but it was general enough to support another real workflow without duplicating the sensitive parts.

Not because it had been designed as a universal publishing platform.

Mostly because it had been simplified before it was extended.

The stack was deliberately conventional

The application is a modular monolith with an ASP.NET Core backend, PostgreSQL, and a React and TypeScript frontend.

The content is delivered as sanitised HTML. Authentication and invitations use ASP.NET Core Identity. The application is packaged with Docker and deployed as a small web system rather than a collection of independently scalable book microservices.

There is no message broker.

There is no distributed chapter consensus algorithm.

The first audience did not require either.

The interesting engineering work was in the product model, document conversion, access boundaries, reader behaviour, and operational reliability.

A more fashionable architecture would not have made the footnotes appear in the correct chapter.

AI wrote much of the code

The same basic AI-assisted workflow I used to build this site was used here too.

ChatGPT helped with product discovery, requirements, architecture, threat modelling, task design, and reviewing what came back.

Google Stitch was used to explore visual directions for the public site, the library, the reader, and the administration screens.

Claude Code worked inside the repository, edited files, ran commands, implemented tickets, and produced completion reports.

I did not ask it to "build a secure ebook platform".

That would have been an excellent way to receive a large amount of plausible software and spend the following weeks discovering what it thought those words meant.

The work was split into small pieces:

  • define the access model,
  • scaffold the application,
  • implement invitations,
  • build the library,
  • import one real document,
  • fix the conversion gaps,
  • build the reader,
  • harden the protected routes,
  • add the administration workflow,
  • and verify each stage before moving on.

The recurring loop was:

plan
→ review
→ implement
→ test
→ inspect
→ revise

The build passing was necessary.

It was never treated as proof that the product was correct.

A converted document can pass every automated test and still contain a broken symbol, a misleading table, or a footnote attached to the wrong chapter.

Someone still has to look.

The difficult part was deciding what the system was

The codebase contains plenty of normal application work: authentication, database migrations, APIs, responsive layouts, email delivery, imports, tests, containers, and deployment.

None of those was the main problem.

The hard part was turning "a digital book that cannot be downloaded" into a set of honest, achievable constraints.

It meant rejecting absolute protection.

It meant removing the downloadable format rather than decorating it with a password.

It meant simplifying the access model before automating it.

It meant testing the architecture against the real book, not a polite sample document with three headings and one paragraph.

And it meant accepting that good protection for a legitimate reader should be mostly invisible.

The result is now a small publishing and access system, with the core reader already running in production.

Readers receive a personal library rather than a file. The source document remains private. The author can prepare content, control access, and reuse the same workflow for new distribution scenarios.

It is not impossible to copy.

Nothing readable can honestly make that promise.

But it no longer begins by giving everyone the original thing we were trying to protect.

That turned out to be a useful improvement.