Most messaging apps treat a notification as the default. A message arrives, your phone buzzes, and it's up to you to turn things off one app, one channel, one thread at a time. With Slack, ClickUp, email and text all competing, the result is predictable: either everything interrupts you, or you mute so much that the one message that mattered gets lost.
Here's my argument. Storing a message and interrupting someone with it are two separate decisions, and they shouldn't share a default. Storage is cheap, so keep everything. Interruptions are expensive, so make them earn their way in by passing one explicit rule you can measure and tune.
I built a small app around that idea in one evening. I call it buffer.
When I showed it to my lead bot, this is how I described it: "It's the priority only message system. All message platforms converge to a single place. All messages are stored, truly urgent messages are notifications."
That's the whole product. Messages from every platform come into one inbox. Nothing gets dropped. Only the messages that are truly urgent get to buzz my phone.
The build is deliberately small: one Cloudflare Worker and one database. Messages arrive by webhook, and ClickUp Chat is polled. A unique key on the source and the message's id on that source means the same message can't be stored twice. I can use it from an installable web app, or an AI agent can use it through its built-in MCP server, which is the standard way agents connect to tools. Either way, replies go back to the thread they came from.
The question "should this interrupt me?" goes to a model, and the model's answer is a score. A message is urgent only if that score is 0.8 or higher. The threshold is a setting, not something buried in code, so if buffer interrupts me too often, I raise the number instead of rewriting the classifier.
Getting that number right turned out to matter more than I expected. Early in the build, the gate was reading the wrong score. The model returns a probability for its pick and a separate confidence for how decisive the whole answer is, and those aren't the same thing. A pick with a probability of 0.94 was reading about 0.82, and a pick at 0.48 was reading about 0.05. Gating on the wrong one meant casual chat was being flagged for review and clearly urgent messages were barely clearing the bar to notify me. The fix was to gate on the probability of the pick, which is what the threshold was meant to measure.
That's the general lesson: a threshold only means something if you know exactly what number it's being compared to.
The first version sorted every message into Urgent, Action or FYI, plus a Review bucket for low-confidence calls. It looked thorough. In practice it was four decisions where I only needed one.
About three and a half hours after the first commit, the categories and the Review bucket were gone. The model now answers one question: is this urgent? Urgent threads are pinned and pushed. Everything else lives in a single feed, grouped by thread, which I clear when I've read it.
Fewer buckets means fewer wrong answers to argue with. I don't care whether the model calls a message Action or FYI. I care whether it should have interrupted me.
If urgency is a policy, I need to know whether the policy is working. That means comparing the model's call to mine.
The first version of corrections overwrote the model's confidence with 1 when I fixed a message. That destroyed the evidence. Now a correction keeps the model's own pick and probability and stamps the time it was corrected. The database stores what the feed uses next to what the model originally said, so how often the model agrees with me is something I can measure.
A system that learns from corrections has to remember what it got wrong. If you overwrite the mistake, you can't count it.
If the model call fails, the message is still stored. It lands in the feed as not urgent, and the reason for the failure is saved with it. The rule in the code is simple: never drop a message because the classifier failed.
That's the order of priorities in one line. Keeping everything is the promise. The AI is a filter on top of it, and a filter that breaks should fail toward "you'll see it in the feed," never toward "it's gone."
Minutes after showing the app to my lead bot, I asked for one more piece: "create buffer bot and have it watch the app." It's a read-only bot that checks health, counts and accuracy, never message content.
That fits the same idea. I don't want to babysit a system whose whole job is to protect my attention. A watcher reports when the numbers say something is off, which is the same rule buffer applies to my messages.
It isn't free. A single threshold will sometimes be wrong in both directions: it will interrupt me for something that could have waited, and miss something that couldn't. A model call on every message costs something too, and polling has to stay cheap. Reading every ClickUp channel on each poll was taking about 36 seconds and close to 100 requests a minute, so now it stops after three channels in a row with nothing new, which takes about a second and a handful of requests.
But those costs are visible and tunable. The alternative, where every app decides on its own what deserves your attention, has costs too. You just can't see them, and you can't turn a single dial to fix them.
If your phone decides what's urgent today, try splitting the two decisions. Keep everything, interrupt on one rule, and measure the rule against your own corrections.