@personize/signal
v0.1.0
Published
Smart notification engine powered by Personize SDK — context-aware customer engagement with AI decision-making, workspace collaboration, and governed delivery.
Downloads
45
Maintainers
Readme
@personize/signal
Smart notification engine built on the Personize SDK. Decides IF, WHAT, WHEN, and HOW to notify each person using unified memory and governance guardrails.
Delivery channels and event sources are modular — the community adds these. The core intelligence is Personize.
Quick Start
npm install @personize/signal @personize/sdkimport { Personize } from '@personize/sdk';
import { Signal, ConsoleChannel, ManualSource } from '@personize/signal';
const client = new Personize({ secretKey: process.env.PERSONIZE_KEY! });
const manual = new ManualSource();
const signal = new Signal({
client,
channels: [new ConsoleChannel()],
sources: [manual],
});
await signal.start();
// Push an event — Signal decides whether to notify
manual.emit({
id: 'evt_001',
type: 'user.signup',
email: '[email protected]',
data: { plan: 'pro', source: 'website' },
timestamp: new Date().toISOString(),
});Or trigger synchronously and get the AI decision:
const result = await signal.trigger({
id: 'evt_002',
type: 'usage.drop',
email: '[email protected]',
data: { metric: 'daily_logins', dropPercent: 40 },
timestamp: new Date().toISOString(),
});
console.log(result.action); // 'SEND' | 'DEFER' | 'SKIP'
console.log(result.score); // 0-100
console.log(result.reasoning); // AI explanationHow It Works
Event In Pre-Check Context Assembly AI Decision
| | | |
Source ──> [ dedup + daily cap ] ──> [ 4 parallel SDK calls ] ──> [ prompt() ]
| (no SDK calls) | smartContext (rules) | |
| Fast skip if: | smartDigest (entity) | SEND ──> channel.send()
| - Same event < 6h ago | smartRecall (context) | DEFER ──> memorize + digest
| - Over daily cap (5/day) | smartRecall (sent log) | SKIP ──> log, done
| |
└────────────────────────── EngineResult ───────────────────────-┘Engine Flow (9 Steps)
- Receive —
SignalEvententers via Source orsignal.trigger() - Pre-check — dedup + daily cap (no SDK calls, instant)
- Context assembly — 4 parallel SDK calls: governance rules, entity digest, semantic context, recent notifications sent
- AI decision —
prompt()scores 0-100: SEND (>60), DEFER (40-60), SKIP (<40) - Decision routing — branch on SEND/DEFER/SKIP
- Deliver —
channel.send()(if SEND) - Workspace update — optional task/update creation on the entity record
- Feedback — memorize what was sent back to the entity record
- Return —
EngineResultwith action, score, reasoning, delivery status, duration
Built-in Channels
| Channel | Use Case | Config |
|---------|----------|--------|
| ConsoleChannel | Development & testing | None |
| SesChannel | AWS SES email | { sourceEmail, region? } |
| SendGridChannel | SendGrid email | { apiKey, fromEmail, fromName? } |
| SlackChannel | Slack webhooks | { webhookUrl } |
| InAppChannel | Callback-based (bridge to your UI) | handler: (recipient, payload) => ... |
Built-in Sources
| Source | Use Case | Config |
|--------|----------|--------|
| ManualSource | Programmatic events from your code | None — call .emit(event) |
| WebhookSource | HTTP webhooks into Signal | { path?, secret?, parser? } |
Workspace Utilities
Signal includes convenience methods for the Personize workspace pattern — collaborative workstations per entity record:
// Add tasks, notes, updates, issues to any entity record
await signal.workspace.addTask('[email protected]', {
title: 'Follow up on trial expiry',
priority: 'high',
assignee: 'sales',
});
await signal.workspace.addNote('[email protected]', {
content: 'Showed strong interest in enterprise features during demo',
tags: ['sales', 'enterprise'],
});
await signal.workspace.addUpdate('[email protected]',
'Onboarding completed — activated 3 integrations');
// Read back workspace data
const tasks = await signal.workspace.getTasks('[email protected]');
const digest = await signal.workspace.getDigest('[email protected]');When engine.workspaceUpdates is enabled, the engine automatically:
- On SEND — creates an update: "Notification sent: <subject>"
- On DEFER — creates a task: "Review deferred notification for <entity>"
Digest Pipeline
Deferred notifications (score 40-60) are stored in Personize memory with signal:deferred tags. The digest builder compiles them into periodic personalized summaries:
// Run digest for one user
const result = await signal.digest.buildForUser('[email protected]');
// Run batch digest for all users (use with scheduler)
const batch = await signal.digest.runBatch([
'[email protected]',
'[email protected]',
]);
// Schedule automatic daily digests
signal.schedule('daily-digest', '0 9 * * 1-5', async () => {
const users = await getActiveUsers(); // your function
await signal.digest.runBatch(users);
});Multi-Team Patterns
Multiple teams contribute to the same entity records. Each team tags their events:
// Product team
manual.emit({
type: 'usage.milestone',
email: '[email protected]',
data: { milestone: '100_api_calls' },
metadata: { team: 'product' },
// ...
});
// Sales team
manual.emit({
type: 'deal.stage_changed',
email: '[email protected]',
data: { stage: 'proposal', rep: 'mike' },
metadata: { team: 'sales' },
// ...
});When Signal evaluates any event, smartDigest returns the combined context from ALL teams. The AI decides based on the full cross-team picture — product usage + sales activity + marketing engagement.
Cost Controls
Signal is designed to be cost-efficient with the Personize API:
const signal = new Signal({
client,
channels: [...],
engine: {
dailyCap: 5, // max notifications per email per day
deduplicationWindowMs: 6 * 60 * 60 * 1000, // skip same event type within 6h
maxEvaluationsPerMinute: 20, // rate limit for batch processing
concurrency: 5, // max parallel evaluations
memorize: true, // feedback loop (default)
workspaceUpdates: false, // workspace tasks/updates (default: off)
},
});Pre-check skips events before any SDK calls:
- Same event type for same email within dedup window → instant SKIP
- Email over daily cap → instant SKIP
Per evaluation: typically 4-5 SDK calls (smartContext + smartDigest + 2x smartRecall + prompt). Pre-check reduces this to 0 for obvious skips.
Adding a Channel
See templates/CHANNEL_TEMPLATE.md. One interface, one file, one test.
import type { Channel, Recipient, DeliveryPayload, DeliveryResult } from '@personize/signal';
export class TwilioChannel implements Channel {
name = 'twilio';
async send(recipient: Recipient, payload: DeliveryPayload): Promise<DeliveryResult> {
// Your delivery logic
return { success: true, channel: this.name, timestamp: new Date().toISOString() };
}
}Adding a Source
See templates/SOURCE_TEMPLATE.md. One interface, one file, one test.
import { EventEmitter } from 'events';
import type { Source, SignalEvent } from '@personize/signal';
export class StripeSource implements Source {
name = 'stripe';
async start(emitter: EventEmitter): Promise<void> {
// Listen for Stripe webhooks, emit SignalEvents
}
async stop(): Promise<void> {
// Cleanup
}
}Configuration Reference
SignalConfig
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| client | Personize | Yes | — | Authenticated SDK client |
| channels | Channel[] | Yes | — | At least one delivery channel |
| sources | Source[] | No | [] | Event sources |
| scheduler | Scheduler | No | CronScheduler | Scheduler implementation |
| engine | EngineConfig | No | See below | Engine configuration |
EngineConfig
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| memorize | boolean | true | Record sent notifications in memory |
| concurrency | number | 5 | Max parallel evaluations |
| dailyCap | number | 5 | Max notifications per email per day |
| deduplicationWindowMs | number | 21600000 (6h) | Dedup window in ms |
| maxEvaluationsPerMinute | number | 20 | Rate limit for batch operations |
| workspaceUpdates | boolean | false | Create workspace entries on SEND/DEFER |
Examples
See the examples/ directory:
- quickstart/ — Minimal setup: one source, one channel
- saas-onboarding/ — Signup-to-nurture-to-convert sequence
- sales-alerts/ — Usage drop alerts to sales team
- weekly-digest/ — Deferred items compiled into weekly digest
- multi-team/ — Product + Sales + Marketing on same records
Architecture
┌──────────────────────────────────────────────────────────┐
│ Your Application │
│ ┌─────────┐ ┌───────────┐ ┌────────────┐ │
│ │ Sources │ │ Signal │ │ Channels │ │
│ │ ──────── │ │ Engine │ │ ──────── │ │
│ │ Manual │─>│ (9 steps) │─>│ SES │ │
│ │ Webhook │ │ │ │ SendGrid │ │
│ │ HubSpot* │ │ Pre-check │ │ Slack │ │
│ │ Stripe* │ │ Context │ │ InApp │ │
│ └─────────┘ │ Decision │ │ Twilio* │ │
│ │ Deliver │ └────────────┘ │
│ │ Feedback │ │
│ └─────┬─────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ Personize │ * = community-contributed │
│ │ SDK │ │
│ │ ───────── │ │
│ │ Memory │ │
│ │ Governance│ │
│ │ AI │ │
│ └───────────┘ │
└──────────────────────────────────────────────────────────┘Core (Personize SDK — not pluggable): Memory, Intelligence, Governance, Workspaces
Edges (modular — community contributes): Channels, Sources, Schedulers
License
Apache-2.0 — see LICENSE.
