From notion-pack
Deploys Node.js apps using the Notion API to Vercel, Railway, or Fly.io. Manages production secrets, Notion client singleton for serverless, rate limiting, and health checks.
How this skill is triggered — by the user, by Claude, or both
Slash command
/notion-pack:notion-deploy-integrationThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Ship Node.js apps that talk to the Notion API to Vercel, Railway, or Fly.io. This skill covers environment variable management, the Notion client singleton pattern for serverless, rate limit handling at 3 req/sec, health check endpoints that verify Notion connectivity, and caching strategies to reduce API calls.
Ship Node.js apps that talk to the Notion API to Vercel, Railway, or Fly.io. This skill covers environment variable management, the Notion client singleton pattern for serverless, rate limit handling at 3 req/sec, health check endpoints that verify Notion connectivity, and caching strategies to reduce API calls.
Deep code lives in references/ so this file stays a lean walkthrough. Read a reference when a step needs the full module, then Write or Edit the code into your project's src/.
@notionhq/client installed (npm i @notionhq/client)NOTION_TOKEN (starts with ntn_)vercel, railway, or flyEvery request authenticates with an internal integration token (NOTION_TOKEN, prefix ntn_), created at notion.so/my-integrations and passed as auth to the client. In production the token is stored as an encrypted platform secret and injected at runtime — never committed to source. Store it with vercel env add NOTION_TOKEN production, railway variables set NOTION_TOKEN=..., or fly secrets set NOTION_TOKEN=.... Each database or page must also be explicitly shared with the integration in the Notion UI, or queries return ObjectNotFound.
Build a production entry point with four modules: a Notion client singleton, a rate limiter, a response cache, and a health check. The singleton is the essential piece — serverless containers recycle unpredictably, so a module-level client reuses connections across warm invocations instead of paying cold-start and rate-limit cost per request:
// src/notion-client.ts — singleton for serverless environments
import { Client, LogLevel } from '@notionhq/client';
let client: Client | null = null;
export function getNotionClient(): Client {
if (!client) {
if (!process.env.NOTION_TOKEN) {
throw new Error('NOTION_TOKEN environment variable is not set');
}
client = new Client({
auth: process.env.NOTION_TOKEN,
logLevel: process.env.NODE_ENV === 'production' ? LogLevel.WARN : LogLevel.DEBUG,
timeoutMs: 30_000,
});
}
return client;
}
The rate limiter (token bucket for the 3 req/sec cap), the TTL response cache, and the healthCheck() function are provided in full — read production-ready application modules and copy each into src/.
Pick one platform. All three inject NOTION_TOKEN at runtime from an encrypted secret:
Each path (secret setup, deploy command, and the framework-specific API-route wiring for the singleton, rate limiter, and cache) is in platform deployment paths.
Add structured error logging so Notion-specific failures surface in your monitoring tool (Sentry, Datadog, or platform logs). The full classifyNotionError / logNotionError module — which maps every @notionhq/client error code to a retryability flag and an operator action, plus the API-route wiring — lives in production error monitoring. Copy it to src/notion-error-handler.ts and call logNotionError(error, context) from every catch block that touches the Notion API.
Key metrics to watch:
latencyMs exceeds 2000msThis workflow produces:
NOTION_TOKEN stored as an encrypted platform secret (never in source code)/health endpoint that verifies live Notion API connectivity| Issue | Cause | Solution |
|---|---|---|
NOTION_TOKEN is not set at runtime | Secret not configured for environment | Re-add secret: vercel env add / railway variables set / fly secrets set |
| Cold start timeout (> 10s) | Large dependency tree or slow Notion handshake | Set min_machines_running: 1 (Fly.io) or use Railway always-on |
| 429 Rate Limited in logs | Exceeding 3 req/sec sustained | Increase cache TTL, batch queries, add request queuing |
Health check returns degraded | Token expired or Notion outage | Check status.notion.com; rotate token if 401 |
ObjectNotFound on database query | Database not shared with integration | Open Notion, click Share, add the integration |
| Serverless function creates multiple clients | Not using singleton pattern | Import getNotionClient() from the shared module, not new Client() |
Two complete, copy-ready examples live in full deployment examples:
/health and /api/query routes.deploy.sh [vercel|railway|fly] that builds, sets the secret, deploys, and verifies the health endpoint.notion-webhooks-events skillnotion-sync-databases skillnotion-extract-content skillnpx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin notion-packRuns a 12-section production deployment checklist for Notion API integrations, covering auth, rate limits, pagination, error handling, and OAuth. Produces a pass/fail readiness report.
Guides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.