Writing an RFC people actually read

Most RFCs do not get read. They get skimmed by the people who have to approve them, ignored by everyone else, and then somebody quotes them out of context six months later when they are annoyed about the outcome. The technical content is usually fine. The structure is where they fail, and the structure is the easy part to fix.

I am not going to pretend I have a system. What I have is a few habits I picked up after writing too many RFCs that nobody read, and watching other people’s RFCs that everybody read. They are not in any particular order, but the first one matters more than the others.

The TL;DR has to stand alone

Permalink to “The TL;DR has to stand alone”

The first paragraph should answer three questions for someone who will read no further: what are we changing, why now, and what are we asking the reader to do.

## TL;DR

We are moving the item ingestion pipeline from synchronous HTTP to a Kafka topic.
This is happening now because the synchronous version is dropping requests under
peak load and we have a customer onboarding next month who will exceed it.
We are asking for review of the message schema (section 3) and sign-off from
the data team on the new retention policy (section 5).

If a stranger reads only that and walks away knowing what is happening and what they have to do, the TL;DR is doing its job. If they have to scroll to figure out what you are even asking, it isn’t.

The thing to delete is everything that sounds like a movie trailer. “As we scale our platform to meet the demands of an evolving market” is words that read fine and contain nothing. Cut them. You have maybe fifteen seconds before the reader decides whether to keep going.

Alternatives before the proposal

Permalink to “Alternatives before the proposal”

Most RFCs put alternatives at the bottom, somewhere after the proposed design. By the time the reader gets there they are already invested in your design, and reading the alternatives feels like reading the reasons the author thinks they are right. That is not what alternatives are for.

So I put them first.

## Alternatives considered

### Option A: keep the synchronous HTTP pipeline, add a queue in front
Pros: minimal change, no new infrastructure.
Cons: the queue would need to durably persist requests, which means another stateful service.
Why we didn't pick it: we already pay for Kafka, adding a second queueing system increases ops surface for no gain.

### Option B: move to Kafka (proposed)
[short description]

### Option C: a different streaming system (Pub/Sub, Kinesis)
Pros: managed, less ops.
Cons: would mean running two streaming systems in parallel during the migration.
Why we didn't pick it: cost of migration far exceeds the operational savings.

This shape is harder to write because it forces you to actually think about the alternatives instead of presenting them as straw men. That is the point. If every alternative in your RFC is obviously bad, the author has not thought hard enough, and a good reviewer will notice.

Say what you are not deciding

Permalink to “Say what you are not deciding”

Every RFC has a halo of related questions that the reader will assume the RFC is also deciding. State explicitly that it isn’t, otherwise the comments will go there anyway.

## What this RFC is not deciding

- The exact partitioning scheme. We will pick this in a follow-up after measuring traffic patterns.
- Whether to deprecate the existing HTTP endpoint. It will keep running for at least 6 months for clients we don't control.
- The schema registry choice. See decision log entry 2026-03-12 for the current direction; this RFC follows it.

This little section saves more arguments than anything else in the document. Without it, every reviewer brings their pet adjacent concern and the comment thread becomes about whatever the loudest person was worried about that morning. With it you can say “out of scope, see above” and move on.

What I cut

Permalink to “What I cut”

Most of the standard RFC sections, honestly.

Background is usually a history lesson the readers already lived through. If they need it, link a doc, do not recite it. Goals and non-goals are the TL;DR and the previous section under different names. Detailed design with every field belongs in the code, not in the RFC; the RFC describes the shape, the schema lives in a .proto or .py file linked from it. Open questions mean the RFC is not ready yet, so either decide them or move them into “what we are not deciding.”

A good RFC for a moderately complex change is 2-3 pages. Anything longer and the reader is being punished for the author not editing enough. If the change really does need ten pages, split it. One RFC for the architecture decision, separate docs for the implementation details.

I used to think the thing that gets an RFC approved was technical depth. It is not. It is whether the reviewers can tell, in about two minutes, that you considered the alternatives, you know what you are not solving, and you are asking for a specific decision. Everything else is decoration.