All we saw was a sea of errors
It started like any other day of cleaning up old content.
We were redoing the join experience for Webex Meetings, auditing all the pieces.
And we saw it.
A sea of errors, errors that had accumulated over years. Maybe centuries. (Bit of an exaggeration, but it had been a while).
So I teamed up with an engineer to collect them all. It took a little while. And the Jira was not small.
Final tally: 65+ errors. One flow. Not ideal.
But I got to reviewing.
And right away, something was wrong. The messages were repeating. But not the same. Just close enough to be confusing.
This wasn’t all of them, but you get the idea (ps. you can check out all of them below)
Some were close, most were not. Content construction was all over the place.
And let’s be honest, none of them were written by someone who actually writes or looks at content much. Most likely a PM or an engineer. (No offense… okay, maybe a little.)
So now I needed to figure out how many duplicate variations there were.
Which meant escaping the sad world of Jira and moving into something that could actually help me visualize it.
Once in Miro, I set up four criteria for the audit:
What’s the core message?
What’s the content structure of the messages?
Is it in our voice and tone? (spoiler from future me: nope)
Can it be consolidated with another? (also me: a lot of them can)
After a little bit of work, I finally had my baseline.
The big boy: 20 instances of a “can’t join this meeting/webinar/thing” message. All basically saying the same thing, but written differently every time.
Clearly, we only needed one consolidated “you can’t join this thing” message.
Next, a little conspiracy theorist-style string connecting (I already used the Always Sunny Meme in a different story) until I had everything mapped out.
After mapping, the number dropped from 65+ error to 18.
Yes, 18!
Next up, rewriting the messages themselves.
Luckily, we’d already dipped our toes into error messages during the early rework of the join experience.
They turned out… alright. But only after a ton of compromises.
The biggest reason? We didn’t have a clear stance on how we wanted to handle errors.
I smell a content pattern coming… It’s at the end if you want to see it.
So part of the job became building that stance from scratch.
One hill I fought hard to defend but lost was sticking with industry-standard content structures—header, description, buttons.
But PMs wanted everything shorter. Usually, I love shorter. But this was crossing a line. And I wasn’t for it.
Three “aha” moments for me along the way:
Errors live in the middle of conversations. They’re always responses to something, not standalone pronouncements. We can use that to our advantage in the language.
Don’t state the obvious. Writing “Can’t do X” is like telling someone “the door is locked” right after watching them rattle the doorknob. Pretty useless info.
Use buttons as part of the conversation. So don’t have information like “Try joining from browser blah blah blah” followed by a button that repeats it: “Join from browser.” Instead, use the button as part of the message.
So I got to writing.
Got feedback from the team, tweaked again, and kept refining.
A few highlights worth showing off, like the consolidated “can’t join” catch-all message.
A little on why…
Keeps the flow moving:“Let’s get you in another way” feels like part of the chat, not a giant red stop sign.
Avoids the obvious: No need to waste space telling users what they already know (connection issue or can’t do that = duh).
Simple and reassuring: Short, calm phrasing that takes the sting out of the error. Making it almost not feel like an error at all.
Okay, last one I want to call out, mainly because I’m proud of how much simpler I made it compared to the original.
But first, some setup (otherwise it won’t make sense). For free accounts, if someone tries to make a call in space, they can’t. They’d need to upgrade.
Great, now that you’ve got the context, here’s the message before:
Took me three days (multiple rounds of bugging PMs and engineers) just to wrap my head around what this thing was trying to say.
So here’s mine.
I think it makes more sense, but I will let you decide.
Here are my reasons why:
Keeps momentum: “Start calling with a paid plan” feels like guidance, not a dead end.
Simple & scannable: Short headline, short body, one clear action.
Reassuring clarity: Explains the “why” (everyone’s on a free plan) without drowning users in fine print.
Action aligned: The CTA (“Upgrade account”) is the exact next step. No guesswork.
By cutting 65 errors down to 18, I not only reduced user frustration through clearer writing but also gave engineers a saner, more maintainable codebase with less noise and faster debugging.
Once we had all the errors for this flow. I wanted to make sure the approach to error messages scaled to the org.
So I created an Error message content pattern that lives in our guidelines and our AI generation tool.