Every notification workflow I've seen works the same way: code triggers, template fills. User signs up? Fire the welcome sequence. Order ships? Send the tracking email. Status changes to X? Template Y.
The triggers are deterministic. The content is a template with some variables swapped in. Maybe you A/B test the subject line. Maybe you personalise with {{first_name}}. But fundamentally, you wrote the email months ago and now it just... fires.
This works fine for transactional stuff. But what if you're building something where the context actually matters? Where every situation is different enough that a template feels robotic?
I've been building this for Ajust, an Australian consumer advocacy platform. Their agent helps people negotiate bills, cancel subscriptions, and lodge complaints. It contacts businesses however it can (email, live chat, web forms, phone) and handles the back-and-forth on behalf of the user.
The challenge: every case unfolds differently. A billing dispute with your phone company plays out nothing like a subscription cancellation with a gym. The business might respond in an hour or a week. They might send a helpful reply or a useless auto-acknowledgement. Users need notifications they can trust: that they'll hear about things that matter, and that silence means nothing's changed.
The Template Problem
Traditional notification systems couple two things together:
- When to send: hardcoded triggers in your application code
- What to say: templates with variable interpolation
This coupling makes sense when your notification types are fixed and predictable. You know exactly what "order shipped" means, so you can write that email once.
But when every case is different, that's where the agent shines. The "what happened" varies wildly. A template that says "We've received a response from {{company}}" tells the user almost nothing.
What they actually want to know: Was it good news or bad news? What did the company say? What happens next? Do I need to do anything?
You can't template that. The content needs to be generated from context.
The Insight
Here's what I realised: if the agent is already reasoning about the situation, it should also generate the notification content. Not fill in a template. Actually write the email, based on what just happened.
The timing follows instructions, but the content is generated fresh each time. No templates. Every notification is a judgment call.
What This Looks Like
Two things are happening here:
- Timing follows instructions. The "should I notify?" check isn't
if (email.received && !isAutoReply()). It's natural language that the agent interprets. - Content is generated. There's no template. The agent writes the actual email based on what just happened and the full context of the case.
The Instructions
"But wait," I hear you saying, "if the instructions are in natural language, isn't the agent just deciding arbitrarily?"
No. There's a crucial difference between giving an agent a vague tool and giving it explicit heuristics.
Vague
Send a notification when appropriate.
Explicit
Send a notification when: (1) an action requires more information from the user, or (2) something substantive happens that changes the state of their case. Ignore automated replies unless they contain critical information like a reference number.
The second version constrains behaviour. The agent still interprets "substantive" (that's what makes it agentic rather than fully deterministic) but it has a framework. It knows what categories of events matter. It knows to filter out noise.
This is the same reason you write explicit acceptance criteria instead of telling your team "do what feels right." Constraints enable good judgment.
Why "Substantive" Works
You might think vague words like "substantive" are a liability. They're actually the point.
Try writing a deterministic rule for "should we notify the user about this inbound email?" You'll end up with something like:
if (
email.from !== 'noreply@' &&
!email.subject.includes('Auto-Reply') &&
!email.body.match(/thank you for contacting/i) &&
(email.body.includes('case number') ||
email.body.length > 500 ||
email.hasAttachment)
) {
sendNotification();
}
This is a mess. And it'll still miss edge cases. What about the auto-reply that happens to contain a case number? What about the short email that says "Your refund has been processed"? What about the long email that's just a legal disclaimer?
The word "substantive" encodes human judgment that would take hundreds of conditional branches to approximate. And it generalises. When a new type of email shows up, "substantive" still works. Your regex doesn't.
Generated, Not Templated
This is where it gets interesting. Once the main agent decides to notify, it doesn't slot values into a template. It passes a summary to a specialised email agent that writes the notification from scratch.
I use a separate agent for this because of bounded context. The main agent is orchestrating a complex workflow with dozens of tools. Asking it to also write polished messages adds too much noise. The email agent receives a curated prompt, just the summary and situational context, and focuses entirely on communication.
Let me show you what this looks like in practice.
Agent needs more info
The agent tries to log into a customer portal on behalf of the user. It hits a 2FA wall.
What a template would say:
Hi Sarah,
We need additional information to proceed with your case. Please log in to continue.
What the agent actually writes:
Hi Sarah,
I tried to access your Telstra account but the system is asking for a verification code sent to your phone. Could you share that code when you get a chance?
The generated version tells the user exactly what's happening and exactly what they need to do. No "log in to continue" mystery box.
Business responds
The company replies acknowledging the complaint and says they'll review it within 3-5 business days.
What a template would say:
Hi Sarah,
We've received a response from Telstra. Log in to view the details.
What the agent actually writes:
Hi Sarah,
Good news: Telstra has acknowledged your complaint and assigned case number TLS-2024-78234. They said a specialist will review within 3-5 business days.
I've scheduled a follow-up for next Monday if we haven't heard back by then.
The agent synthesised the inbound email, extracted what matters, assessed the sentiment ("good news"), and scheduled an appropriate follow-up. This isn't a forwarded message. It's a briefing.
Auto-reply
The business sends: "Thank you for contacting us. Your message has been received and will be reviewed shortly."
A traditional system would probably fire a "New response received" email here. The user clicks through, sees it's nothing, and learns to ignore your notifications. Our agent recognises there's nothing substantive and stays quiet.
This is the notification that didn't happen. And that's just as important as the ones that did.
Scheduling
Sometimes the right time to notify isn't now. It's later.
tool({
description: 'Schedule a notification to be sent later',
inputSchema: z.object({
datetime: z.string().describe('ISO 8601 datetime'),
summary: z.string().describe('What to tell the user'),
}),
});
The agent decides: I'll follow up Monday morning if we haven't heard back.
Notice what's happening here. The scheduling is deterministic. It will fire at that exact time. But the decision to schedule was a judgment call. The agent looked at the context, decided a follow-up made sense, and picked an appropriate delay.
Scheduling also gets complicated fast. You need to consider out of office hours, weekends, and timezones. A follow-up at 3am isn't helpful. Neither is a notification that lands on Saturday when the user won't see it until Monday. The agent needs to reason about when the user is likely to be available, not just when the business logic says to fire.
Closing the Loop
"Sent" doesn't mean "delivered." "Delivered" doesn't mean "read." When your notification system can feed delivery status back to the agent, it can report problems and adapt its behaviour.
Two types of feedback matter:
- Bounced: Tell the user their email address isn't working
- Delivered but unopened: Try a different channel for time-sensitive messages
Bounce handling
The agent sends an update to the user's email. Two hours later, the mail server returns a hard bounce. The address doesn't exist.
The agent flags the problem in-app and, if the user has a phone number on file, sends an SMS:
Your email address seems to be bouncing. Update it in your account settings so I can keep you posted on your Telstra case.
Escalating channels
The agent emails about a time-sensitive settlement offer. Twenty-four hours pass. The email shows as delivered but unopened.
The agent escalates to SMS:
Telstra offered to settle your complaint. Check your email before the offer expires tomorrow.
This isn't spam. It's a judgment call. The agent knows the deadline matters, knows the user hasn't seen the email, and picks a channel more likely to cut through. For routine updates, it would just wait.
Why Users Like This
After a few interactions, users develop a mental model: "I get notified when something meaningful happens."
They stop checking the app compulsively because they trust that silence means nothing's changed. They stop ignoring emails because they know each one contains something worth reading.
This is what deterministic template systems can't achieve. They're predictable but not meaningful.
The Tradeoffs
I'm not going to pretend this is free. You're trading:
Auditability. You can't point to a line of code and say "this is why the notification fired." The agent interpreted the instructions. If you need to debug, you're reading reasoning traces, not stepping through conditionals.
Testing complexity. "Did the agent correctly interpret substantive?" is fuzzier than expect(email.sent).toBe(true). You end up writing more eval-style tests with example cases.
Prompt sensitivity. Poorly written instructions lead to inconsistent behaviour. If your instructions say "notify when something important happens," you've given the agent nothing. The specificity of your instructions directly determines the consistency of your notifications.
When to Use This
Agentic notifications make sense when:
- The events you're responding to vary in importance, and that importance is hard to codify
- Users need to trust that notifications are worth reading
- The content of notifications needs to be contextual, not templated
- You're already running an agent that can interpret natural language instructions
They don't make sense when:
- You have a small, fixed set of notification types
- The trigger conditions are genuinely simple
- You need perfect auditability for compliance reasons
- You're not running an agent anyway
Don't over-engineer. If if (case.resolved) sendEmail() works for your use case, do that. The agent adds value when there's judgment involved. "Case closed" is binary, and the state change is the message. If neither "should I notify?" nor "what should I say?" requires interpretation, you're adding complexity for no benefit.
The Takeaway
Users don't hate notifications. They hate noise. The fix isn't fewer emails or better copy. It's splitting the problem: let the instructions govern when you reach out, and let the agent decide what's worth saying. When timing is predictable and content is contextual, users stop ignoring you and start trusting you.
