When a system fails, the user reads an error message. That sentence is one of the most consequential pieces of writing in the entire product, because it is the moment when the user is most uncertain whether they should trust the software, whether they have done something wrong, and whether the work they have just attempted is salvageable.
Most software treats this sentence as engineering exhaust. The error message is whatever the underlying exception happened to say. "Request failed with status 500." "Invalid input." "Something went wrong." These are not sentences. They are stack-trace fragments dressed in slightly nicer fonts.
I treat error messages as voice work. They are the moment where the brand has to choose between sounding like a system or sounding like a person, and the system answer is almost always the wrong one.
What an error message has to do
A good error message answers three questions in roughly this order. What just happened. What the user can do now. What is happening on the other side, if anything.
The first answer must be specific. Not "something went wrong" but "I could not save your campaign" or "the email service did not accept that recipient." The user already knows something went wrong. They are looking at the error. What they need is the specific shape of the failure.
The second answer must be actionable. Not "try again later" but "your changes are saved as a draft, retry in a moment" or "this email is invalid, edit and try again." A recovery path or a reassurance. Never a dead end.
The third answer is optional but powerful when it applies. If a system is going to retry on its own, say so. If a long operation is in progress in the background, say so. If a notification will follow, say so. The user does not need to know the details. They need to know that the system has not abandoned them.
What I never do
I never expose internal error messages to the user. The user does not need to see "ECONNREFUSED" or "rate limit exceeded on upstream provider." These are operational details that mean something to me and nothing to them. I log the technical detail server-side, where it belongs, and translate the user-facing message into something they can act on.
I never blame the user for system failures. "Your input was invalid" is a different message from "I could not parse that input." The first sounds accusatory. The second sounds honest. When the system has failed, the system should say so. When the user has made an error, the message tells them gently and shows them how to fix it.
I never use error codes as the primary message. A code is useful when the user is talking to support. It is not a substitute for a sentence that explains the failure. If a code shows at all, it is small, secondary, and never the headline.
The discipline at scale
Writing one good error message is straightforward. Maintaining a hundred good error messages across a platform is a discipline. Every new feature adds new failure paths, and each one needs a sentence that fits the rest. The voice has to be consistent. The tone has to be consistent. The structure has to be consistent.
I keep a small set of error patterns and apply them everywhere. A validation failure has one shape. A network failure has another shape. A permission failure has a third. A long operation that has not yet succeeded has a fourth. New errors fit one of these shapes. The shape determines the structure of the message; the specifics fill it in.
The user never reads two errors in this product that sound like they came from different systems. There is one voice and one set of patterns, and every error message is part of the same conversation.
The compound effect
The cumulative effect of taking error messages seriously is hard to measure individually but easy to feel. The user who hits an error and is shown a clear path through it is a user who keeps using the product. The user who hits an error and is shown a stack trace fragment is a user who already does not entirely trust what they are looking at.
Every error message is a small test of the brand's character. I try to pass them all.