
Earlier this year I wrote that coding agents would change the shape of the software development lifecycle: humans would move up to intent, architecture, judgment and governance, and agents would take over more of the execution. It was an argument from observation and from the first few applications I had built this way.
Since then I have run the argument against a harder test. I wanted my own mail system. Not a toy client, but something that receives and sends real email for two domains I run, keeps every message until I personally delete it, survives a bad day, and costs almost nothing to leave running. I built it with a coding agent, mostly Codex initially, and then Claude, over the last week of October. The first commit was on 27 October. Real mail was flowing through it in production a few days later. Verified backups, a tested restore, and automated capture of mail that other systems send on my behalf were in place by the end of the month.
Mere days from an empty folder to a production mail system is the headline, and it is the least interesting part of the story. What I want to write down is what I actually had to do during those few days, because very little of it was writing code.
The old shape of this project
I have been around long enough to know what this project would have looked like fifteen years ago. A mail system touching DNS, an inbound SMTP gateway, object storage, a relational database, a queue, a worker, authentication, a web client and push notifications is a few weeks of work for a small team, with the retention and backup pieces quietly deferred to "phase two" and never built. Most of the calendar time is not typing. It is reading documentation, wiring services together, chasing permissions, and waiting for someone to review a change.
Agents remove most of that mechanical time. The documentation reading, the wiring, the boilerplate and the first draft of every test are now cheap. What they do not remove is the part that was always the real risk: deciding what the system must never do, and proving that it does not do it.
That is where most of the effort went.
Approval boundaries came before architecture
The first useful thing I did was not design the system. It was an audit of what already existed. The agent inventoried my AWS accounts, found the existing receipt rules, the support mailbox with a 14-day expiry that I had forgotten about, and the sending identities that other applications relied on. It wrote all of this down before proposing anything.
Then we agreed on boundaries. Some examples, taken from the project's own documents:
- A proposed monthly budget was labelled "an engineering recommendation, not a user-approved spend" until I approved it.
- A cloud benchmark was run only after I approved its exact scope: one disposable single-region database cluster, in the staging account, tagged so that it could be found and deleted, and deleted afterwards with the deletion verified.
- Readiness checks were written so that they never create, upload or deploy anything. The scripts say so in their own output.
- Changes to other projects' mail-sending code were prepared as reviewable patches and applied locally, but deployment of those projects stayed a separate human decision.
- Production changes went through CloudFormation change sets that I reviewed before they were executed.
None of this is novel. It is the segregation of duties that any sensible organization applies to human access. What is new is that it has to be spelled out in writing, up front, because an agent will otherwise execute an ambiguous instruction perfectly and do exactly the wrong thing with real resources.
I would put it this way: the agent was allowed to be fast everywhere that was reversible. Everywhere that was not reversible, it was allowed to prepare, and I was the one who pressed the button.

Reversibility helps classify work, but it does not create permission. Agree the scope, cost and approval point before execution.
"Never retry what you cannot explain"
The most important design decision in the system is a small one, and it is a governance decision dressed as a technical one.
When the app sends an email, it stores the sent copy and records a "sending" attempt before it calls the mail API. If the API returns an error the service defines as definite, the attempt is marked failed. Anything else, a timeout, a network error, an ambiguous response, is marked uncertain and is never retried automatically. A human looks at it.
This is the opposite of what most retry logic does, and it is the opposite of what an agent will write if you do not tell it otherwise. Agents are optimistic. Left alone, they wrap calls in retries, because retries make tests pass. For email, an optimistic retry is how you send the same message to a customer three times.
The same instinct shaped the rest of the system. Deleting a message permanently writes an independent deletion intent and a tombstone first, and a ledger of deletions persists so that a restore from backup cannot resurrect something I deliberately removed. Backups can only be restored into an empty, isolated destination; the restore tooling refuses to overwrite live data. These are all cases where the safe behaviour is the slower, more explicit behaviour, and where I had to specify it, because nothing in the agent's defaults would have chosen it.
Humans determine what the system must never do. Agents are very good at everything else.
Evidence, not confidence
In the earlier article I argued that when code becomes cheap, verification becomes the expensive and important part. On this project that turned out to be literally true: a large fraction of the agent's effort went into producing evidence rather than features.
The repository has 103 automated checks and eight browser journeys, which is unremarkable. What is less common is the audit trail beside them. Every deployment produced a record of what was verified: that the database schema was initialised, that a signed-out visitor is redirected, that the direct function URL returns 403, that a real message reached "delivered", that a backup was taken and then restored into an isolated target, that the deletion ledger was honoured during that restore. Ninety-one such artifacts accumulated in three days. Each commit that changed the system was followed by a commit that recorded what was deployed and what was checked.
That discipline did not come from the agent's defaults; it came from treating "done" as "here is the evidence" and holding to it. I started requiring evidence because I noticed missing functionality and mistakes. I confronted the agent by stating my observations and they confirmed it later that they did not do what said they did. E.g. a typical conversation would be: Agent: The text in the button no longer wraps. Both web and mobile tests passed. Me: No, it's not fixed. Agent: You are right. I assumed it was working on mobile because the tests passed on web.
The standard you accept on day one is the standard you get, from agents and from people.
The evidence also caught real problems. The first storage design document was superseded within a day and had to be re-labelled as historical, because the production schema evolved away from it. A phrase search that took 31 milliseconds for an inbox view took almost a second on a 50,000-message corpus, and a filter on environment took over two and a half seconds; the agent produced a query diagnosis with the plans and the options before I decided which trade-off to accept. An IAM policy-generation tool refused the project's file extension and had to be run against a snapshot. A test message sent minutes before the DNS record existed was deferred by Gmail and arrived on the retry. None of these are dramatic. All of them would have been invisible without someone insisting on proof.

A test result is one part of the evidence. Review what was checked, which revision it describes and how the product behaves on the intended devices.
What I did, in order
If I compress these dev days into the roles I actually played, the list is short and almost entirely non-technical.
- Decide what the system is for and what it must never do. Keep everything until I delete it. Never send twice. Never let a restore undo a deletion. Never change another project's mail without a separate decision.
- Approve the scope of anything that costs money or cannot be undone. Benchmarks, clusters, DNS changes, change sets, real sends.
- Set the standard of evidence. A claim of "done" is not accepted without a check I can read.
- Make the judgment calls the agent surfaced. Which search trade-off to accept. Whether a two-and-a-half-second filter is acceptable for a personal mailbox (it is, for now, and it is written down as an accepted risk).
- Use the product. The first phone layout passed its checks and still needed a release titled "make the phone layout readable." The person using the application remains the final test.
That is the loop I described in the abstract earlier this year: intent, architecture, governance, execution, verification, observation, improvement. It ran roughly once a day. Check out the full article here: https://delairi.com/ideas/ai-native-sdlc/

This loop reflects the roles in the mail-system case study. Assign responsibility explicitly, then feed observed behavior into the next decision.
What this means if you lead a team
The temptation when you see a system like this built in days is to conclude that the bottleneck has moved to "how fast can we type instructions." It has not. The bottleneck has moved to how quickly a person can make a sound decision about an irreversible action, and how good the evidence in front of them is when they make it.
That has a few practical consequences.
The people who are most valuable in this model are the ones who already know what a system must never do: experienced engineers, architects and product owners who have been burned before. Their value goes up, not down, because an agent can now act on their judgment immediately.
Approval boundaries need to exist before the work starts, in writing, and they need to be enforced by the environment, not by hoping the agent remembers. Read-only readiness checks, disposable scopes, change sets with a human on the execute button.
"Done" has to be redefined as "evidenced." Otherwise a team will accept an agent's confidence the way it would never accept a junior developer's, because the agent's confidence is so fluent.
And someone still has to use the thing. The checks passed on a phone layout that then needed a readability release.
The limits of this example
This was one person, two domains, and a system whose worst failure mode is my own inbox. The budget, the risk appetite and the approval path were all mine. A team with customers, compliance obligations and shared infrastructure would need the same boundaries with more ceremony around them, and would find that the ceremony, not the coding, is what their calendar is made of.
I also do not want to overstate the agent's independence. It proposed; I disposed. It wrote most of the code and most of the tests, and it drafted every runbook, but every design decision that mattered went through me, usually more than once. The system is mine in the sense that the decisions are mine.
What I am confident of is the shape. A year ago I was describing it. This month I ran it, and the thing that made it work was not the agents' capabilities. It was deciding, before any code existed, what it was not allowed to do alone.
The project is published as a reference implementation at github.com/mdelairi/ses-inbox, with the runbooks, the accepted-risks list and the audit records included, because the decisions are the part worth reading.


