notion-reliability-patterns

'Graceful degradation when Notion is down: offline cache, retry with

Allowed Tools

ReadWriteEdit

Provided by Plugin

notion-pack

Claude Code skill pack for Notion (30 skills)

saas packs v1.38.0
View Plugin

Installation

This skill is included in the notion-pack plugin:

/plugin install notion-pack@claude-code-plugins-plus

Click to copy

Instructions

Notion Reliability Patterns

Overview

Production reliability patterns for Notion integrations: retry with exponential backoff, a circuit breaker to prevent cascade failures, and graceful degradation (offline cache, health checks, fallback content) so users see stale data instead of errors when the API is unreachable. All patterns use Client from @notionhq/client and handle Notion-specific error codes.

The three layers compose in order — retry sits inside the circuit breaker, which sits inside the cache/fallback layer. Read the skeletons below to follow the workflow, then open references/implementation.md for the complete copy-paste code.

Prerequisites

  • @notionhq/client v2.x installed (npm install @notionhq/client)
  • lru-cache for in-memory caching (npm install lru-cache)
  • Python: notion-client installed (pip install notion-client)
  • NOTION_TOKEN environment variable set
  • Understanding of circuit breaker and retry patterns

Authentication

All patterns authenticate with a Notion internal integration token read from the NOTIONTOKEN environment variable — new Client({ auth: process.env.NOTIONTOKEN }) (TS) or Client(auth=os.environ["NOTION_TOKEN"]) (Python). Never hardcode the token; the health check calls notion.users.me() to confirm the token is valid and the API reachable.

Instructions

Build the three layers in order. Each skeleton shows the shape; the full implementation lives in references/implementation.md.

Step 1: Retry with Exponential Backoff

Classify errors as transient (429, 500, 502, 503, timeouts, network) vs permanent (400/401/404), retry only the transient ones with exponential backoff + jitter, and honor the Retry-After header on rate limits.


async function retryWithBackoff<T>(fn: () => Promise<T>, opts = {}): Promise<T> {
  // maxRetries=4, baseDelayMs=1000. Loop; on transient error wait
  // baseDelayMs * 2^attempt (+ jitter), else rethrow. See implementation.md.
}

Full code (TypeScript + Python, including isTransientError and rate-limit handling): references/implementation.md Step 1.

Step 2: Circuit Breaker to Prevent Cascade Failures

During a sustained outage, stop hammering the API and fail fast. The breaker moves closed → open after N consecutive transient failures, waits a reset timeout, then tests recovery in half-open before returning to closed.


class NotionCircuitBreaker {
  // failureThreshold=5, resetTimeoutMs=30_000, halfOpenSuccesses=2
  async execute<T>(fn: () => Promise<T>): Promise<T> { /* ...see implementation.md */ }
  getState() { /* { state, failures, lastFailure } for health checks */ }
}

Full class + CircuitOpenError: references/implementation.md Step 2.

Step 3: Graceful Degradation — Cache, Health Checks, Fallback

Wrap calls so a success refreshes an LRU cache and a failure serves the last-good cached value; expose a health check and static fallback content for the cache-cold case.


async function resilientQuery<T>(cacheKey, fn, fallbackContext?) {
  // circuit.execute(() => retryWithBackoff(fn)) → cache on success,
  // serve cache on failure, static fallback if cache is empty.
}

Full code — queryWithFallback, notionHealthCheck, getFallbackContent, resilientQuery, and the Python equivalents: references/implementation.md Step 3.

Output

  • Retry with exponential backoff handling 429, 500, 502, 503 errors
  • Circuit breaker preventing cascade failures (5 failures = circuit opens)
  • Offline cache serving stale data when API is unavailable
  • Health check endpoint returning healthy/degraded/down status
  • Fallback content for zero-downtime user experience
  • Combined resilient query pattern composing all layers

Error Handling

Issue Cause Solution
Circuit stays open Threshold too low for occasional errors Increase failureThreshold to 10
Stale cached data Long TTL during extended outage Add freshness indicator in UI, reduce TTL
CircuitOpenError in logs API is down, circuit protecting Expected behavior, check status.notion.com
Retries not helping Error is permanent (400/401/404) isTransientError filters these out
Health check shows degraded Notion API latency > 2s Normal during peak load, monitor trend
Memory growing Large cache Set max on LRU cache, reduce TTL

Examples

Operational wiring — a pollable health endpoint and Prometheus alert rules — lives in references/examples.md:

  • System Health Dashboard — expose notionHealthCheck() as GET /api/health/notion, returning 200 for healthy/degraded and 503 for down.
  • Monitoring Alert Rules — Prometheus alerts that page when the circuit opens (notioncircuitstate == 2) or when >50% of requests are served from cache.

Resources

Next Steps

For governance and policy enforcement, see notion-policy-guardrails.

For scaling beyond single-token limits, see notion-load-scale.

Ready to use notion-pack?