When you send a message, the app has at least two jobs: keep the message and arrange to tell the recipient it’s there.
A system can succeed at the first and forget the second. Your words are safely stored, but the person you wrote to has no reason to look for them.
That small gap influenced how we built our messaging and calling system. We kept the message and its notification job in the same database, where we could save them together.
Keep the message with its to-do
The notification job is a record of work to be done. It tells a background worker to attempt the notification later.
Our database can save that job and the message in one transaction. Both records are saved, or neither is. It can’t finish with a message successfully stored and its notification job accidentally left out.
The notification itself can still fail. This protects the step before delivery: remembering that delivery needs to be attempted.
For the technical version, we use Phoenix for the application, PostgreSQL for the database, and Oban to manage background jobs in that same database. The useful property is the shared transaction. The list of tools matters less than what it lets us keep together.
A separate service would add another handoff
We could put those jobs in a separate queue service. Plenty of systems have good reasons to do that.
But then we’d need a reliable way to coordinate saving a message in one place and recording its notification work in another. What happens if the first succeeds and the second is unavailable? How do we recover without losing work or repeating it carelessly?
Those questions have answers. They also create work. In this system, we hadn’t gained enough from a separate queue to justify taking that on.
Keeping things together has costs too
The application’s parts share a release and can’t all grow independently. The database carries both product records and background jobs. Keeping the system compact doesn’t remove the need for backups, recovery planning, or attention to capacity.
We already use a separate relay for calls that can’t connect directly. And the files people upload live on a persistent storage volume, a choice we’d revisit if storage needs or deployment requirements changed.
Large group calls, recording, pressure on the database, or work that needed to run independently could all change the arrangement.
For now, saving the message and its to-do together solves a real problem. Before adding another service, we want an equally specific answer to what it would improve.