Guides/How to Build Notifications with AI Agents/Prompting an Agent to Build Notifications

Chapter 4

Prompting an Agent to Build Notifications

Most bad output from an agent isn't a model problem. It's a missing-information problem. You asked for a welcome email and didn't say which channels are live, who the recipient is, or what data you have, so the agent picked something plausible.

how to build product notifications with AI

Last updated: September 2026

Most bad output from an agent isn't a model problem. It's a missing-information problem. You asked for a welcome email and didn't say which channels are live, who the recipient is, or what data you have, so the agent picked something plausible.

Close that gap and the output gets better immediately. Then check it anyway.

What makes a good notification prompt?

Five things. Recipient, channels, trigger, data, constraints.

You can derive this from the skill itself, which tells the agent to stop and ask when a request is underspecified: "Channel? Transactional or lifecycle? New code or existing? Which language?" Those are the questions a complete prompt has already answered.

  • Recipient. A user_id, not an email address. It's how preferences, routing, and cross-channel delivery find each other.
  • Channels. Which ones are actually connected, and what the fallback order is.
  • Trigger. What event causes this, and from where in your code.
  • Data. The fields available at send time, and their shape.
  • Constraints. Transactional or lifecycle. Does a human publish. Is there a length limit.

A vague prompt and a complete one

Here's the difference, concretely.

The vague version:

Add a welcome email when someone signs up.

What you get back is reasonable and mostly unusable. An email-only template, hardcoded copy, a send to an inline email address, no user_id, no preference topic, published straight to live because nothing said not to. It works in a demo and breaks the first time someone updates their email address.

The complete version:

Add a welcome notification for new signups. Send to user_id. Email is live via SendGrid and we have the in-app inbox; no SMS yet. Trigger it from handleSignup in src/auth/signup.ts. Available data is first_name, plan_name, and workspace_url. Keep the subject under 50 characters. Create it as a draft, don't publish, and show me the rendered output before we ship it.

Same model, same skill, completely different result. You get a template that renders on both live channels, a send keyed to user_id, the trigger wired into the right function, and a draft waiting for review.

The second prompt is four sentences longer.

How do I stop an agent guessing about Courier?

Guessing is what happens when an agent needs a fact it doesn't have. Give it the facts, in order of impact:

1. Install the skill. It carries the verified call shapes and the rules that are easy to get wrong. One command, covered in Chapter 2.

2. Give it the docs. Courier publishes machine-readable documentation specifically so agents can read it:

  • https://www.courier.com/llms.txt is the briefing: what Courier is, when to use it, how agents should approach it
  • https://www.courier.com/docs/llms.txt is the documentation index
  • https://www.courier.com/docs/llms-full.txt is every page, concatenated

If you installed the skill as a Claude Code plugin, the docs MCP server comes with it and your agent can look things up without being told to.

3. Write down what's true about your setup. The AGENTS.md from Chapter 2. Which environment, which channels are live, what the review rule is.

An agent with all three stops asking you the same question every session and stops inventing answers when you're not looking.

Make the agent verify its own work

An agent will tell you it sent the message. It's reporting that the API call succeeded, which is not the same claim. A 200 with a requestId means Courier accepted the job. Delivery is a separate question with a separate answer.

So ask for the answer:

Show me the status of every message sent to user_test today, and the current step of the onboarding journey run.

That's from Courier's own agent quickstart, and the reason it's the right prompt is in the docs' framing of it: the answer comes from Courier, not from the agent's memory.

Three checks worth building into your habits:

Did it render correctly? GET /messages/{message_id}/output returns exactly what Courier handed the provider: resolved variables, brand styling, the real subject line, per channel. Not a description of the output. The output.

courier messages content --message-id <MESSAGE_ID>

Did it actually go? The history endpoint is the honest one. A trailing SENT event is the real confirmation.

courier messages history --message-id <MESSAGE_ID>

If it failed, why? There's a ladder, and it goes in this order:

courier messages list --recipient user_test # find it
courier messages history --message-id <ID> # what happened
courier messages content --message-id <ID> # what it tried to send

Build these into the prompt itself. "Send it, then show me the delivery history and the rendered output" is one sentence longer and removes the entire category of an agent confidently reporting success on a message that never arrived.

What not to ask an agent to do

Some of these come from Courier's own "what not to do" list, and some are ours. All of them are mistakes an agent makes more often than a person does.

  • Don't let it use PUT /profiles/{user_id} for updates. PUT replaces the whole profile, and any field you leave out is deleted. POST merges. An agent updating a phone number with PUT will quietly drop the email address.
  • Don't skip idempotency keys on transactional sends. Retries without one produce duplicate messages. Pass 'Idempotency-Key' on the send.
  • Don't use method: "all" for OTPs, password resets, or billing. Use "single" with fallback channels, or the user gets the same code three ways.
  • Don't send to a user_id with no stored profile if the channel needs contact info. Courier won't know where to deliver it.
  • Don't let it commit an API key. Environment variables or a secrets manager, always.
  • Don't test with test@example.com. Reserved domains are blocked and come back UNDELIVERABLE, which looks like a bug and isn't.

Where a human still has to sign off

Two moments, and they're both about blast radius.

Publishing. Templates and journeys are created as drafts by default. Keep it that way. An agent that can publish is an agent that can push untested copy to everyone, and reviewing a draft costs thirty seconds.

Anything that fans out. A single test send is low risk. A journey that triggers for every user in a segment is not. The review step belongs between "the agent built it" and "it runs for real."

Scope your keys so the blast radius is small by default: one key per agent, per environment, so revoking one doesn't take down everything else.

Does the model you pick matter?

For this work, yes, in two places.

Structured output is where it shows up first. An agent that reliably produces valid JSON against a schema saves you a debugging loop. Most current models are fine at this; the older and smaller ones drift.

The second place is inside your journeys, where an AI node runs a model as a step in a live flow. That's a different decision with different tradeoffs, and Chapter 6 covers which model to pick for which job.

Frequently asked questions

What should I include in a prompt to get a usable template?

Recipient type, which channels are live, the trigger, the data fields available at send time, and any constraints like subject length or whether a human publishes. Four sentences covering those five things is the difference between a demo and something you'd merge.

How do I know the agent actually sent something?

Ask it to show you the delivery history, not to tell you it worked. courier messages history --message-id <ID> returns the real event sequence, and a trailing SENT is the confirmation. A 200 on the send call only means Courier accepted the job.

Can I let an agent send to real users?

Once you trust the flow, yes, and plenty of production systems work this way. Get there in stages: Test environment first, then Production with a human publishing anything that fans out. Keep a separate API key per agent so you can revoke one without revoking everything.

How do I stop an agent inventing Courier features?

Install the skill, point it at llms.txt, and put your own setup in an AGENTS.md. Agents invent things when they need a fact they don't have. Most of the time the fix is supplying the fact rather than writing a better prompt.