Use this skill when the OpenART registry selects tool.add_notification_channel.e85166861d03e25f for the current task.
Overview
Notification channels in FetchTheChange follow a strict pattern: a delivery service file, an encrypted credential store, a channel type in the Zod enum, a case in the delivery switch in server/services/notification.ts, and per-monitor UI. The existing Slack and webhook implementations are the reference. This skill encodes the security constraints (encrypted tokens, no plaintext logging) and architectural rules (dedicated service file, tier gating, CSRF exemption for OAuth callbacks).
Workflow
- Read
server/services/notification.ts— understanddeliverToChannels()switch andChannelDeliveryResult. - Read
server/services/slackDelivery.ts— reference for delivery service interface. - Read
server/services/webhookDelivery.ts— reference for delivery service interface. - Read
server/utils/encryption.ts— understandencryptToken()anddecryptToken(). - Read
shared/schema.ts— notenotificationChannelstable andslackConnectionstable patterns. - Read
shared/routes.ts— notechannelTypeSchemaZod enum and channel-related route definitions. - Add the new channel type string to
channelTypeSchemainshared/routes.ts(e.g.z.enum(["email", "webhook", "slack", "discord"])). - If the channel requires stored credentials (OAuth tokens, bot tokens, API keys), add a new table to
shared/schema.tsfollowing theslackConnectionspattern with an encrypted token column. - Create a delivery service at
server/services/{channel}Delivery.tsfollowingslackDelivery.ts:- Export a
deliver()function accepting(monitor, change, channelConfig, token). - Return
{ success: boolean; error?: string }.
- Export a
- Wire the new channel into the
switch (ch.channel)block indeliverToChannels()anddeliverDigestToChannels()inserver/services/notification.ts:- Decrypt stored token with
decryptToken(). - Call the new delivery service.
- Log delivery results via
storage.addDeliveryLog().
- Decrypt stored token with
- If OAuth is required, register install and callback routes in
server/routes.ts:- Store tokens using
encryptToken()— never plaintext. - Add the OAuth callback path to
EXEMPT_PATHSorEXEMPT_PREFIXESinserver/middleware/csrf.ts.
- Store tokens using
- Add a tier gate: new channels must enforce the same Pro/Power check pattern used by Slack and webhook routes — read from
TIER_LIMITSinshared/models/auth.ts. - Add storage methods to
IStorageandDatabaseStorageinserver/storage.tsfor credential CRUD. - Add per-monitor channel selection UI in the monitor notification settings, using shadcn/ui primitives.
- Run
npm run check && npm run test.
Hard constraints
- NEVER store a bot token, OAuth token, or API key in plaintext — always encrypt with
encryptToken()fromserver/utils/encryption.ts - NEVER log a decrypted token — even at debug level; log only safe prefixes or redacted placeholders
- NEVER return a stored credential in a GET response — return a redacted placeholder only (e.g.
"••••connected") - NEVER add a new channel type string without adding it to
channelTypeSchemainshared/routes.ts— validation will reject the value - NEVER wire delivery logic inline in a route handler — create a dedicated service file following
slackDelivery.ts/webhookDelivery.ts - NEVER add an OAuth callback route without a CSRF exemption in
server/middleware/csrf.ts— the callback carries no Origin header - NEVER skip the tier gate — new channels must enforce the same Pro/Power check pattern used by existing Slack and webhook routes, reading from
TIER_LIMITS - NEVER skip the verification gate — run
npm run check && npm run testbefore committing