Send it — unless they said no.

Receipts, codes, alerts, "your export is ready." One call sends it by email, by SMS, or into an in-app inbox — and the opt-out your settings screen renders is the same row we check at send time, so your UI can never say "off" while your code keeps sending.

$15/mo or $150/yr — a year costs two months less.
14-day free trial, cancel any time. One subscription covers every Parsons product, on unlimited projects.

prefs · user u_123try the toggles
Email — everything
scope: channel
Email — marketing
scope: channel + category
In-app inbox
scope: channel
two sends
send { channel: "email", category: "marketing", userId: "u_123" }
This user turned marketing email off.opted_out:marketingskipped
send { channel: "email", templateKey: "receipt" }
Operational mail is unaffected.provider acceptedsent
Most restrictive wins — turn Email — everything off and both sends stop.
three channels, one call· opt-outs checked inside the send· 1,200 requests/min per project· in-app messages uncapped
02 · The layer you keep rebuilding

Every app builds the same four things.

None of them is the reason your product exists, and you will build all four again in your next app. Only one of them can silently email someone who asked you to stop.

a send · templates
a log · a toggle
01
A send
Wire a provider, handle its errors, learn its quirks. Then do it again for the second channel.
02
Templates
Subject and body with variables, plus escaping you hope is right on the day a name contains markup.
03
A log
What went out, to whom, and what the provider said. You will want it six weeks later, not tomorrow.
04 · the bug
A preference toggle
A column your settings screen writes and your send path forgets to read. Nothing errors.
your settings screen
Marketing emailOFF
UPDATE users SET marketing_ok = 0
two tables, one hope
the job you shipped last week
cron.js → sendMarketing(user)
reads a different table

The toggle lives in your database. The send lives somewhere else. The day someone adds a code path that forgets the check, your settings screen says "off" and your app keeps sending — and the customer finds out before you do.

03 · The turn

One row. Three things read it.

Your settings screen writes it. Your backend reads it before every send. Your AI flips it when a customer emails you. There is no second copy to drift out of sync, because there is no second copy.

your settings page
writes it
PUT /v1/notifications/prefs
your backend
reads it, every send
send({ channel:"email",
  category:"marketing" })
your AI, over MCP
flips it in a sentence
set_pref · get_prefs
one row · notification preferences
user_idchannelcategorystate
u_123emailmarketingopted_out

Two layers — channel, and channel + category. The more restrictive one wins. A channel opt-out silences everything on that channel; a category opt-out silences only sends that declare it, so a customer can kill your marketing mail and still get "your order shipped." That is the whole model, and it fits in this paragraph on purpose.

04 · The mechanism

The check is inside the send, not around it.

"We enforce preferences" is a claim. Here is where the enforcement happens: between resolving your template and choosing a transport, before any provider is contacted. There is no way to call the sender and step past it, because the check is not a line in your code that someone could forget to write.

two passes
one gate
01
send()
02
resolve template
03 · the gate
optOutMatch
04
sender identity
05
select transport
06
log row
senttemplateKey: "receipt"
Cleared the gate. Rendered, signed, handed to a transport, and written to the delivery log. sent means the provider accepted it — that is as far as any sender can honestly claim.
skippedopted_out:marketing
Stopped at the gate. Nothing downstream ever ran — no template send, no transport, no provider call. It is still a log row, still an HTTP 200, and it is not an exception you have to catch.
a skipped send is a 200 with status "skipped"·you will find it in the log tomorrow·metering only fires on non-skipped sends
05 · Three channels

Email, SMS, or the bell. Same call.

One argument changes. The template, the preference check, the delivery log and the tools are identical across all three. Pick a channel below and watch what the recipient actually gets.

channel: "email"
"sms" · "inapp"
send {
  channel: "email",
  to: "ana@example.com",
  templateKey: "order_ready",
  vars: { name: "Ana", orderId: "4172" },
  category: "orders"
}
200 emails a day, included in the plan. A skipped send costs you nothing — metering fires only on sends that actually went.
send · list_messages · get_message · inbox_list · inbox_mark_read
what Ana receivesemail
Sunrise Bakery
Your order #4172 is ready
Hi Ana — your order is boxed and waiting at the counter until 6pm today. Thanks for baking with us.
Sunrise Bakery Ltd · 14 Harbour Road, Whitstable CT5 1AB
Sent via Parsons Cloud · unsubscribe
+1 (555) 014-2210 · today 09:12
Sunrise Bakery: your order #4172 is ready for pickup until 6pm.Reply STOP to opt out
notifications · 1 unread
Your order #4172 is ready
Boxed and waiting at the counter until 6pm.
2m
Receipt for order #4160
£14.20 · paid
3d

SMS is not turnkey and we will not pretend it is: it needs a number bound for SMS on your project and completed A2P registration. Those are real one-time and monthly fees, itemised in pricing. Once they are done, delivery is a real carrier send with STOP handling and a daily segment cap applied for you.

06 · Deliverability

Your domain. Our signing. One rule about when we won't try again.

Attach a domain to your project and your mail goes out under it, DKIM-signed on our own send path, with a second transport behind it. No domain yet? You still send under your own sender name — our wordmark is never on your mail, only one footer line.

two transports
three outcomes
recipient
ana@gmail.com
send()→ parsons-mta · DKIM, your domain→ sent
We hold a verified signing key for the exact domain your project resolved to, so the mail leaves under your domain, not a shared one.
recipient
ana@icloud.com
send()→ routed to CF Email Sending→ sent
Apple's filter rejects our MTA's current IP, so icloud / me / mac addresses are deliberately routed to the path Apple accepts — verified live 2026-08-20. You do not configure this and you do not think about it.
recipient
a hard-bounced address
send()→ REFUSED · suppressed→ retry on the standby transport
A transport fault falls through to the standby. A refusal never does. Re-sending a hard-bounced address on a second path is precisely how you teach a receiving domain that you do not process bounces — so we don't, and we tell you instead.
the email a person actually receivesrendered
1Sunrise Bakery
Your order #4172 is ready
Hi Ana — your order is boxed and waiting at the counter until 6pm today.
2Sunrise Bakery Ltd · 14 Harbour Road, Whitstable CT5 1AB
4Sent via Parsons Cloud · 3unsubscribe

Sender identity is scoped to the project, so several apps in one project share one identity. If you need two identities, make two projects — they are free.

Your name on it. Ours in the footnote.

Four fields, four tool arguments, all on the $15 Base plan. A project that never sets a sender name gets no wordmark at all — not ours. One footer line stays, at footer size, on every send.

1Your wordmark. The From display name and the mark at the top of the shell — set_sender_name.
2Your legal entity and postal address, which CAN-SPAM requires and which is yours to state — set_sender_identity({ mailingAddress }).
3An unsubscribe destination you control. Set it and we add a List-Unsubscribe header; leave it and we don't — silently unsubscribing someone from their own receipts is a worse bug than not having the header.
4The only place we appear — one footer line, at footer size. There is no branding tier to buy your way out of.
07 · Templates

Variables that can't inject. A missing one that can't break the send.

Write the message once per channel with {{var}} placeholders and send it by key. Two failure classes are closed for you, and both are demonstrable right here — type anything you like into the field.

escaped on the way in
forgiving on the way out
create_template · channel "email" · key "order_ready"
Hi {{name}} — your order
{{orderId}} is ready for pickup.
what we store for {{name}}: Ana
status: senta missing optional variable renders empty — it never fails a send

Try <b>hi</b> or <script>alert(1)</script>. Every substituted value in an email body is HTML-escaped, so a customer whose display name contains markup renders as literal text. A variable is content. It is never markup, and it cannot become markup by being unusual.

Now drop the second variable. The body renders with a gap and the send still goes. Transactional mail should not be lost to a blank field — a receipt with a missing order number is worth more than no receipt at all.

create_template · list_templates · get_template · delete_template (requires confirm: true)

Templates are per project and per channel, so the same key can carry a long email body and a one-segment SMS. Change the wording once and every send that uses the key changes with it.

08 · The log

Four statuses. None of them lie to you.

The console holds a filterable delivery log — the same rows your code reads back over list_messages. It is not a paid tier and not an upgrade — the same log for every subscriber. The filters below are the console's real filters; use them.

when · channel · to
status · detail
channel
status
WhenChannelToStatusDetail
09:12:04emailana@example.comsentorder_ready
09:12:04inappu_123deliveredorder_ready
08:47:51emailana@example.comskippedopted_out:marketing
08:31:19sms+15550142210sent1 segment
08:02:38emailtypo@exmaple.comfailedsuppressed · provider-managed
07:55:02emailsam@example.comsentreceipt
07:41:20emailsam@example.comskippedopted_out
07:12:47inappu_881deliveredexport_ready
sent
Handed off
The provider accepted it. Not "delivered" — we will not claim what we cannot see.
delivered
In the store
In-app only, and it means the row exists in your project's inbox. Not a guess.
failed
Did not go out
Over HTTP this returns 502, so a plain if (!res.ok) catches a non-delivery.
skipped
They said no
Deliberate, logged, and a 200. You can tell "they opted out" from "it broke".
// the 502 rule — a non-delivery fails your own error check, not silently
const r = await fetch("https://notifications.parsons.ai/v1/notifications/send", opts)
if (!r.ok) { /* 502 — it did not go out. Decide what to do. */ }

One attempt, one log row. There is no retry queue — if a send fails, you decide whether to retry, and you can see the row that says so. And sent is the honest ceiling: post-send delivery events are not built, so nothing on this page will ever tell you a message was read.

09 · Suppression

Some addresses you can rescue. Some you can't.

An address that hard-bounces or reports spam gets suppressed, and every later send to it fails. What matters is knowing which kind you are looking at — because for one of them a retry is a plan, and for the other it is a way to damage your own sending reputation.

clear the ones we added
never pretend about the rest
Manual · clearable
added by you or by us · clear_suppression
bounced-test@example.comadded 12 Aug
old-address@sunrisebakery.comadded 03 Aug
Clear one and the next send to that address is attempted normally.
Provider-managed · permanent
real bounce and complaint history · not ours to undo
typo@exmaple.comhard bounce
complaint@example.netspam report
Click why? and check_suppression tells you the verdict instead of pretending a retry helps.

Every suppression tool is scoped to addresses your project has actually sent to — so nobody can use this surface to probe somebody else's list. And a practical warning worth more than any feature: test with real addresses you control. A made-up address burns permanently on its first bounce, and that is the receiving world's rule, not ours.

list_suppressions · check_suppression · clear_suppression (requires confirm: true)
10 · For your deployed app

A sk_live_ key, five endpoints, no SDK.

Your running app sends over plain HTTP. There is no client library to install, no version to keep up with, and nothing to import — a fetch and a bearer token is the whole integration.

the project comes
from the key
curl -X POST \
  https://notifications.parsons.ai/v1/notifications/send \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"channel":"email",
       "to":"ana@example.com",
       "templateKey":"order_ready",
       "vars":{"name":"Ana","orderId":"4172"},
       "category":"orders"}'
EndpointWhat it is for
POST /sendOne notification, any channel. 200 on sent or skipped, 502 on failed.
GET /messagesThe delivery log, filterable by channel and status.
GET /prefsRender your own settings screen from platform state.
PUT /prefsWrite the toggle to the same row the sender reads.
GET /inboxBuild your notification bell against the in-app inbox.
Authorization: Bearer sk_live_… → projectId resolved
"projectId": "proj_…"
There is no such field. The project comes from the key and never from the body, so a request cannot send as somebody else's project. The key is verified on every call, and the project it resolves to is the only project that call can touch.
sustained, per project1,200 requests / minute
burst 120
20 requests a second, held — the bar stops at the ceiling because that is what a ceiling does.
The console's own API runs its own 240/240 budget, so a busy dashboard never eats your app's send capacity.

GET and PUT /prefs are what your own settings screen should call. That is how the row stays single: your React toggle and our sender are reading the same state, not two copies of it.

The key is the door for your code, wherever it runs — your own servers, a container, or an app you deployed on Parsons. Our own platform services reach the same sender over an internal binding instead, which is how the scheduler's watchdog pages its owner. Either way it is the same send path and the same gate.

11 · Drive it from your AI

Eighteen tools. Not a chat box bolted to a dashboard.

Every operation on this page is a named tool your own Claude or ChatGPT can call. The support question you would normally answer by grepping production logs becomes a question you just ask.

you
Ana says she's still getting marketing email from us.
get_prefs { userId: "u_123" }
→ email · marketing · opted_out · set 4 days ago
list_messages { channel: "email", limit: 20 }
→ 3 sends to ana@example.com since then — all skipped · opted_out:marketing
your AI
She's opted out and nothing has gone. The three marketing sends since the toggle are all logged as skipped — the mail she's seeing is older than the opt-out. Her receipts are still sending normally.
whoamidocssendcreate_templatelist_templatesget_templatedelete_templateset_sender_nameset_sender_identitylist_messagesget_messageset_prefget_prefsinbox_listinbox_mark_readlist_suppressionscheck_suppressionclear_suppression
one connection · minted for you at sign-in GET YOURS → Works in Claude, ChatGPT, Cursor — anything that speaks MCP. The connection is pinned to one project, so the project is never an argument an AI can get wrong. Tool calls are uncapped and unbilled.

Every admin action — a send, a template change, a sender identity edit, a preference flip, a suppression clear — is written to an audit log, whether a human, your app or an AI did it.

12 · Scope

Nine things we don't do.

Scope is a feature, and a page that sends you elsewhere is a page you can believe. If one of these is the thing you actually need, we would rather you found out now than after you paid.

none of these
are "coming soon"
Push notifications
Mobile and web push are not built. If push is the requirement, OneSignal or Knock is the honest answer.
Slack, WhatsApp, Teams
Three channels: email, SMS, in-app. No chat-platform connectors.
Digests and batching
Every send is one send. There is no "5 new comments" rollup and no quiet-hours window.
Scheduling and send-later
Send now. If you need "at 9am on Tuesday", Parsons Scheduler fires and calls your app, which sends.
Delivery webhooks
Nothing calls you back. Poll GET /messages or read list_messages.
A retry queue
Delivery is synchronous best-effort. One attempt, one log row, and the 502 tells you.
A drop-in inbox component
You get inbox_list, inbox_mark_read and GET /inbox and you build the bell. Fewer batteries than Knock's React feed — and no framework to be locked into on Vue, Svelte or native.
Campaigns, segments, analytics
No audience builder, no open rates, no journey canvas. This is app-to-user messaging, deliberately.
A reply inbox
These messages don't expect an answer. If you need a conversation and a mailbox, that is Parsons Email.

One more, stated plainly because it affects your bill: plan allowances are metered, not enforced. We show you the counts and we do not cut you off mid-month. Do not read that as a promise you cannot exceed your plan — read it as a promise your receipts won't stop at 4pm on the 28th.

13 · Honestly, against the field

Every rival meters the message. We meter the account.

Because they charge per message, everything adjacent to the message has to be sold back to you: the in-app bell is billed, log retention is a tier, your own branding is an upgrade, and a second project is a second workspace. Three rows here go against us. They land first.

three losses
at full weight
 ParsonsKnockNovu (cloud)Courier
Push notificationsnot builtyesyesyes
Multi-step workflow buildernot builtyesyesyes
Drop-in inbox UI componentAPI onlyyes, React-firstyesyes
Entry paid price$15/mo
or $150/yr
free tier, then $250/mofree tier, then $30/mofree tier, then $0.005/send
What the meter countsthe accountmessages, guide users, AI creditsworkflow runs, across all environmentssends
In-app messagesuncappedmeteredcounted as workflow runscounted as sends
Delivery log retentionnot tiered30 days; 90 on Enterprise24 hours on free; 7 days on Pro; 90 days on Teamnot published
Your branding on your mailincludedtheir branding until Starter; per-customer branding is Enterprise——
A second projectfree, unlimitedplan- and environment-scoped2 environments free, 10 on Team—
Driven by named AI tools18 MCP toolsdashboard-firstdashboard-firstdashboard-first

Competitor rows read from each vendor's own published pricing and docs pages on 2026-08-22. Prices change; check theirs before you decide.

If you need push or a workflow canvas, buy Knock — it is a good product and it does those things. What we will not do is let you find out in month three. And one more honest row that will not fit in the table: if notifications are all you will ever send, and you send few of them, a per-send vendor can bill you less than $15 a month. We are not the cheapest way to send two hundred emails. The rest of the table is structural — none of them can bundle a whole platform of products into one $15 account, because notifications is the only product they have.

14 · Pricing

$15 buys the account, not the notification layer.

Monthly or yearly — a year costs exactly two months less than twelve months bought one at a time. There is no per-seat price, no enterprise tier and no free tier. One subscription covers every Parsons product on every project you own. Going past an included allowance is welcome: it draws from prepaid credits at roughly cost-and-a-half, so the bill starts when real usage does.

14-day free trial
cancel any time
Parsons Base
$15 / month
or $150 / year — two months free ($180 paid monthly)
emails per day200
in-app messagesuncapped
the included allowance on this product
  • 200 emails a day, sent under your own sender name and your own domain if you have one
  • In-app messages are uncapped — no plan limit, because they never leave your project's store
  • Templates, the delivery log, the preference API, suppression tools
  • All 18 MCP tools and the sk_live_ HTTP API — tool calls are uncapped and unbilled
  • Every other Parsons product, on unlimited projects

14-day free trial on your first subscription · pick monthly or yearly at checkout, switch later · cancel any time · checkout collects a payment method, because it opens the subscription that starts on day 15.

SMS is real, and it is not included — the whole bill, itemised
US local number$3.99 / mo
US toll-free number$4.99 / mo
A2P brand registration$49.99 once
A2P campaign vetting$18.99 once
A2P campaign — low volume$3.99 / mo
A2P campaign — standard use cases$11.99 / mo
Outbound SMS segment$0.02
Inbound SMSnot billed
Segments draw from a prepaid wallet you top up, bounded by your project's spend cap — never a surprise invoice. Numbers and A2P registration are carrier costs with a fixed markup, not a margin we hide. If you only need email and in-app, none of this applies to you.
Unlimited projects, free.separate data · separate members · separate spend caps
Sunrise Bakery
sender: Sunrise Bakery
cap $25/mo
Coastal Detailing
sender: Coastal Detailing
cap $10/mo
Personal
sender: Mike Parsons
cap $20/mo
Harbour Market Stall
sender: Harbour Stall
cap $10/mo
what you pay, total $15/mo ↑ fourth project added · the number did not move

A project is a boundary, not a line item — its own sender identity, its own preference rows, its own delivery log, its own members and its own spend cap. Rivals in this category gate a second environment or a second workspace behind an upgrade. Add as many as you like.

01 · what the $15 replaces
Novu and Knock sell notifications and nothing else, and their entry paid plans are $30 and $250 a month. The same $15 here is the whole account — notifications, email, auth, deploy, database, storage, scheduler, domains and the rest, on one bill. Most buyers are already paying more than $15 for exactly one of those.
02 · what we never add to it
No per-seat charge, so a second teammate is $0. No per-workspace or per-environment charge, so a second project is $0. No branding tier and no log-retention tier — both are in the price. The number on your card is the number on this page, however far the account grows.
03 · where the meter is
Consumption that costs us real money is priced separately and never hidden. Usage you burn — AI turns, voice minutes, SMS segments — comes out of a prepaid wallet under your project's spend cap. Things you deliberately buy — a phone number, a domain — are quoted at a fixed markup and added to your bill as their own line. The spend cap is the proof: a plan meaning to surprise you would not ship one.
NotificationsEmailAuthMCP HubAI GatewayDeployDatabaseStorageSchedulerDomainsPaymentsCRMTrackerPhoneMoneyJournalFitnessMusicCloud
One subscription. All of them, on every project. 14-day free trial · cancel any time.
15 · Questions

The ones people actually ask.

Every answer is open. An accordion in a section about honesty is a small dishonesty.

nothing hidden
behind a click
Does this replace Resend or Postmark?
For app-to-user messaging, yes — and it adds the preference layer, the in-app channel and the delivery log you would otherwise build. For newsletters and campaigns, no: that is a different product and we do not have it.
Can my own settings screen read these preferences?
Yes — GET and PUT /v1/notifications/prefs with your sk_live_ key. It is the same row the sender checks, which is the entire point.
Does a skipped send cost me an email?
No. Metering fires only on sends that were not skipped, so an opted-out user costs you nothing.
What happens at email number 201?
It sends. Going past the included allowance simply draws from your prepaid credits at roughly cost-and-a-half — we show you the counts, and the bill starts when real usage does.
Is SMS really live?
Yes — a real carrier send with STOP handling and a daily segment cap. It needs a number bound for SMS on your project and completed A2P registration first, and every one of those fees is printed in the pricing section above.
Can I send from my own domain?
Yes, once the domain is attached and verified — DKIM-signed on our own send path, with a second transport behind it. Without a domain you still send under your own sender name.
Do you put your branding on my email?
No. A project with no sender name gets no wordmark at all — not ours. The only mention of us is one Sent via Parsons Cloud line in the footer.
Do I get push notifications?
No. Mobile and web push are not built and are not in V1. The in-app inbox is a stored row you render yourself, not a device push.
Is there a free tier?
No. A 14-day free trial on your first subscription, then $15/mo — or $150 for the year. Checkout collects a card because it opens the subscription — we would rather say that here than surprise you at the form.
Is there an annual plan?
Yes. $150/yr — exactly two months less than the same plan paid monthly ($180). Pick the interval at checkout; the plan, the allowances and the 14-day trial are identical either way.
What if I need a second project?
Make as many as you like. They are free, and each gets its own sender identity, preference rows, delivery log, members and spend cap.
Can I retry a failed send automatically?
Not here. One attempt, one log row, and a 502 so your own error handling sees it. You decide what a retry means for that message.
How do I know it delivered?
For in-app, delivered means the row is in the store. For email and SMS the honest ceiling is sent — the provider accepted it. We do not have post-send delivery events and will not invent them.

Send it — unless they said no.

send { channel: "email", templateKey: "order_ready" } → sent

14-day free trial, then $15/mo — or $150 for the year — covering every Parsons product, on unlimited projects.

$15/mo or $150/yr · two months free yearly · cancel any time