@venturekit/notify
@venturekit/notify provides transactional messaging — email (AWS SESv2), WhatsApp (Meta Cloud API), with SMS and push as pluggable placeholders. It is intent-driven: declare a notify intent in vk.config.ts and vk deploy auto-provisions an SES identity + configuration set, a bounce/complaint handler, a Postgres outbox, and an EventBridge cron dispatcher. Application code only calls notify.enqueue(...).
Installation
Section titled “Installation”npm install @venturekit/notify@dev1. Declare the intent
Section titled “1. Declare the intent”import { defineVenture } from '@venturekit/infra';
export default defineVenture({ base, security, databases: [{ id: 'main', type: 'postgres', name: 'app' }], notify: [ { id: 'main', channels: ['email', 'whatsapp'], defaultFrom: 'no-reply@app.com', domain: 'app.com', // optional: SES verifies the whole domain (DKIM) hostedZoneId: 'Z123ABC', // optional: auto-publish DKIM CNAMEs in Route 53 whatsapp: { phoneNumberId: '105954992345678' }, }, ], envs: { dev, prod },});See Infrastructure Intents for the full intent shape.
2. Build the runtime client
Section titled “2. Build the runtime client”import { createNotifyClient, createPostgresNotifyStore, createSesEmailProvider, createMetaWhatsAppProvider, createSecretsTokenResolver,} from '@venturekit/notify';
export const notify = createNotifyClient({ store: createPostgresNotifyStore(), defaultFrom: process.env.VENTURE_NOTIFY_FROM, providers: [ createSesEmailProvider({ region: process.env.AWS_REGION!, configurationSetName: process.env.VENTURE_NOTIFY_CONFIG_SET, }), createMetaWhatsAppProvider({ phoneNumberId: process.env.VENTURE_NOTIFY_WHATSAPP_PHONE_ID!, tokenResolver: createSecretsTokenResolver({ secretArn: process.env.VENTURE_NOTIFY_WHATSAPP_TOKEN_SECRET!, }), }), ], // Map user ids → channel-specific addresses (consumer-owned in Phase 1). addressResolver: async (userId, channel) => { const user = await findUserById(userId); if (channel === 'email') return user?.email ?? null; if (channel === 'whatsapp') return user?.whatsappNumber ?? null; return null; },});
// Templates live in code in Phase 1 — register at module load.notify.registerTemplate<{ name: string }>({ key: 'welcome', channel: 'email', category: 'transactional_account', render: ({ name }) => ({ subject: `Welcome, ${name}`, text: `Hi ${name}, welcome aboard.`, html: `<p>Hi <b>${name}</b>, welcome aboard.</p>`, }),});The auto-provisioned dispatcher imports this module via VENTURE_NOTIFY_CLIENT_MODULE (default ./lib/notify.js).
3. Enqueue a notification
Section titled “3. Enqueue a notification”import { notify } from '@/lib/notify';
await notify.enqueue({ recipientUserId: ctx.user!.id, channel: 'email', templateKey: 'welcome', payload: { name: 'Alice' }, relatedKind: 'account', relatedId: accountId,});enqueue() returns synchronously after the row lands in the outbox. The dispatcher cron drains it on the next tick (≤ 1 minute by default) and calls the provider.
Channels
Section titled “Channels”| Channel | Phase 1 provider | Real impl? |
|---|---|---|
email | AWS SESv2 (lazy-loaded) | Yes |
whatsapp | Meta Cloud API (Graph /messages) | Yes |
sms | noop placeholder | No — register your own provider |
push | noop placeholder | No — register your own provider |
Outbox & status tracking
Section titled “Outbox & status tracking”Persistence is Postgres. Auto-applied migrations (vk migrate / vk deploy) create notifications, notification_preferences, and notification_event_log. Each notification moves through these statuses:
| Status | Meaning | Retried? |
|---|---|---|
pending | Awaiting the next cron tick | Yes |
sent | Provider accepted delivery | No (may flip to bounced) |
failed | Hard error or retries exhausted | Manually via admin retry |
bounced | Bounce/complaint event arrived after sent | Address auto-suppressed |
cancelled | Suppressed at enqueue (opt-out/suppression) | No |
Subpath Exports
Section titled “Subpath Exports”| Subpath | Description |
|---|---|
@venturekit/notify | Client, providers, templates, store, types |
@venturekit/notify/admin | Admin / inbox route helpers (adminListNotifications, adminRetryNotification, …) |
@venturekit/notify/dispatcher | dispatchOnce, makeDispatchHandler (cron Lambda) |
@venturekit/notify/bounce-handler | makeBounceHandler (SNS bounce/complaint Lambda) |
@venturekit/notify/providers/email-ses | SES email provider |
@venturekit/notify/providers/whatsapp-meta | Meta WhatsApp provider |
@venturekit/notify/store/postgres | Postgres outbox store |
@venturekit/notify/migrations | Outbox migration SQL (auto-merged by @venturekit/infra) |
Local Dev
Section titled “Local Dev”Declaring a notify intent with the email channel adds a MailHog service to your local Docker Compose (SMTP on localhost:1025, web inbox at http://localhost:8025).
Dependencies
Section titled “Dependencies”@venturekit/core— required@venturekit/data— optional peer (Postgres outbox store)@venturekit/runtime— optional peer (dispatcher / handler wiring)@aws-sdk/client-sesv2— optional peer (SES email)@aws-sdk/client-secrets-manager— optional peer (WhatsApp token)
Related
Section titled “Related”- Infrastructure Intents — the
notifyintent @venturekit-pro/chat— real-time chat (the other half of the oldcommspackage)- API Reference — full type documentation