@fayz-ai/plugin-notifications
v0.2.6
Published
Fayz SDK — plugin-notifications plugin
Readme
@fayz-ai/plugin-notifications
The engine that turns a fact into a message: template, queue, retry, deduplication and the recipient's choice of channel.
Status: beta — the engine is complete and graded by the canonical chain (schema, enqueue, dedupe, opt-out, drain). Pre-1.0: no admin surface yet, and the APIs may change before 1.0.
Install
npm install @fayz-ai/plugin-notificationsimport { createNotificationsPlugin } from '@fayz-ai/plugin-notifications'
export const plugins = [createNotificationsPlugin()]createNotificationsPlugin mounts no screen. Everything it does is reachable from
SQL and from the three event subscriptions it declares — which is deliberate: a
form that cannot send is what this engine exists to stop shipping.
Why it exists
The SDK had both ends and not the middle. It had the event — the durable log
and its consumer — and it had the channel: plugin-conversations owns the
number, the delivery and the opt-out. public.notifications is a bell inside the
app that never leaves it. So "tell the customer their order is ready" had nowhere
to live.
Dunning, appointment confirmation, order-ready, the post-service survey and a marketing campaign are the same engine with a different template. This is that engine.
What it ships
| Table | What it holds |
|---|---|
| plg_notifications_templates | what a message says, per key, channel and locale |
| plg_notifications_preferences | how a person wants to be reached |
| plg_notifications_queue | what is waiting, what went, and what was refused and why |
| plg_notifications_deliveries | what the provider said |
| plg_notifications_channels | which function delivers a channel, per tenant |
The three invariants
- Nothing leaves without a template.
notifications_enqueuerefuses a key it cannot resolve, before writing anything. - Opt-out is an absolute veto, and a blocked message is a ROW with a reason on it — an opt-out that produces nothing at all is indistinguishable from an engine that quietly stopped working.
- Redelivering an event does not send twice.
dedupe_keyis unique per tenant, so a retried delivery resolves to the message that already exists.
Using it
-- write the template once
INSERT INTO public.plg_notifications_templates (tenant_id, key, channel, body, is_default)
VALUES (:tenant, 'order.completed', 'whatsapp', 'Oi {{customer_name}}, seu pedido {{order_number}} está pronto!', true);
-- everything else is a reaction: the order completes, the engine enqueues
SELECT public.consume_event_log(200); -- scheduled by pg_cron (PRD 10)
SELECT public.notifications_drain(100); -- its own job: the provider is outsideThe drain is a separate schedule from consume_event_log() on purpose: the
provider is outside and the queue is ours, so a provider timing out costs a retry
here and never holds up the event log.
