| Single note size |
25 MB
Best practices for handling Evernote data.
ReadWriteEditGrep
Evernote Data Handling
Overview
Best practices for handling Evernote data including ENML content processing, attachment management, local database sync, and ENEX export/import.
Prerequisites
- Understanding of Evernote data model (Notes, Notebooks, Tags, Resources)
- Database for local storage (SQLite, PostgreSQL, etc.)
- File storage for attachments
Instructions
Step 1: Data Schema Design
Design a local database schema that mirrors Evernote's data model. Key tables: notes (guid, title, content, notebookGuid, created, updated, USN), notebooks (guid, name, stack), tags (guid, name), resources (guid, noteGuid, mime, hash, size). Track Update Sequence Numbers (USN) for incremental sync.
CREATE TABLE notes (
guid TEXT PRIMARY KEY,
title TEXT NOT NULL,
content TEXT,
notebook_guid TEXT REFERENCES notebooks(guid),
created BIGINT,
updated BIGINT,
usn INTEGER DEFAULT 0
);
Step 2: ENML Content Processing
Parse ENML to extract plain text (strip tags), convert to HTML (replace with , resolve to ![]() / tags), or convert to Markdown. Validate ENML before sending to the API by checking for required declarations and forbidden elements.
function enmlToPlainText(enml) {
return enml
.replace(/<\?xml[^>]*\?>/g, '')
.replace(/<!DOCTYPE[^>]*>/g, '')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
Step 3: Resource (Attachment) Handling
Download resources via noteStore.getResource(guid, withData, ...). Store binary data locally with the MD5 hash as filename. Track MIME types for proper content-type serving. Compute hashes for integrity verification.
Step 4: Sync Data Manager
Implement incremental sync using getSyncState() to get the current server USN, then getSyncChunk() to fetch changes since your last sync. Process chunks in order: notebooks first, then tags, then notes, then resources.
const syncState = await noteStore.getSyncState();
if (syncState.updateCount > lastSyncUSN) {
const chunk = await noteStore.getSyncChunk(lastSyncUSN, 100, true);
// Process chunk.notebooks, chunk.tags, chunk.notes, chunk.resources
}
Step 5: Data Export
Export notes to ENEX (Evernote's XML export format), JSON, or Markdown. ENEX preserves the full note structure including resources and is compatible with Evernote import.
For the complete data schema, sync manager, ENML processor, and export implementations, see Implementation Guide.
Output
Debug Evernote API issues with diagnostic tools and techniques.
ReadWriteEditBash(npm:*)Bash(node:*)Grep
Evernote Debug Bundle
Current State
!node --version 2>/dev/null || echo 'N/A'
!python3 --version 2>/dev/null || echo 'N/A'
Overview
Comprehensive debugging toolkit for Evernote API integrations, including request/response logging, ENML validation with auto-fix, token inspection, and diagnostic CLI utilities.
Prerequisites
- Evernote SDK installed
- Node.js environment
- Understanding of common Evernote errors (see
evernote-common-errors)
Instructions
Step 1: Debug Logger
Create a logger that captures API method names, arguments (with token redaction), response times, and error details. Write to both console and file for post-mortem analysis.
class EvernoteDebugLogger {
constructor(logFile = 'evernote-debug.log') {
this.logFile = logFile;
this.requests = [];
}
logRequest(method, args, response, duration, error) {
const entry = {
timestamp: new Date().toISOString(),
method,
duration: `${duration}ms`,
success: !error,
error: error?.message || error?.errorCode
};
this.requests.push(entry);
fs.appendFileSync(this.logFile, JSON.stringify(entry) + '\n');
}
}
Step 2: Instrumented Client Wrapper
Wrap the NoteStore with a Proxy that automatically logs every API call, measures response time, and catches errors. This adds zero-config debugging to any existing integration.
function instrumentNoteStore(noteStore, logger) {
return new Proxy(noteStore, {
get(target, prop) {
if (typeof target[prop] !== 'function') return target[prop];
return async (...args) => {
const start = Date.now();
try {
const result = await target[prop](...args);
logger.logRequest(prop, args, result, Date.now() - start);
return result;
} catch (error) {
logger.logRequest(prop, args, null, Date.now() - start, error);
throw error;
}
};
}
});
}
Step 3: ENML Validator
Validate ENML content against the DTD rules: check for XML declaration, DOCTYPE, root, forbidden elements, and unclosed tags. Optionally auto-fix common issues (add missing headers, close tags, strip forbidden elements).
Step 4: Token Inspector
Check token validity by calling userStore.getUser(). Report token owner, expiration date (edam_expires), account type, and remaining upload quota.
Step 5: Diagnostic CLI
Create a CLI script with commands: diagnose (run all checks), validate-enml (validate ENML content), inspect-token (show token info), test-api (verify API connectivity).
For the full debug logger, instrumen
Deploy Evernote integrations to production environments.
ReadWriteEditBash(npm:*)Grep
Evernote Deploy Integration
Overview
Deploy Evernote integrations to production environments including Docker containers, AWS ECS/Lambda, Google Cloud Run, and Kubernetes, with proper secrets management and health checks.
Prerequisites
- CI/CD pipeline configured (see
evernote-ci-integration)
- Production API credentials approved by Evernote
- Cloud platform account (AWS, GCP, or Azure)
- Docker installed for containerized deployments
Instructions
Step 1: Docker Deployment
Create a multi-stage Dockerfile that builds the app and produces a minimal production image. Set NODE_ENV=production and configure the Evernote SDK for production endpoints.
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
ENV NODE_ENV=production EVERNOTE_SANDBOX=false
EXPOSE 3000
CMD ["node", "dist/index.js"]
Step 2: Google Cloud Run Deployment
Deploy the Docker image to Cloud Run with secrets mounted from Secret Manager. Cloud Run scales to zero when idle, making it cost-effective for webhook receivers.
gcloud run deploy evernote-app \
--image gcr.io/PROJECT/evernote-app:latest \
--set-secrets EVERNOTE_CONSUMER_KEY=evernote-key:latest \
--set-secrets EVERNOTE_CONSUMER_SECRET=evernote-secret:latest \
--allow-unauthenticated \
--region us-central1
Step 3: AWS Lambda (Serverless)
Package the webhook handler as a Lambda function behind API Gateway. Use AWS Secrets Manager for credentials. Lambda is ideal for event-driven Evernote integrations (webhook processing, scheduled sync).
Step 4: Kubernetes Deployment
Create a Deployment with ConfigMap for non-secret settings and Kubernetes Secrets for API credentials. Include liveness and readiness probes that verify Evernote API connectivity.
Step 5: Deployment Verification
After deployment, verify: health check endpoint returns connected, a test note can be created and retrieved, webhook endpoint is reachable, and monitoring is reporting metrics.
For the full Dockerfile, Cloud Run config, Lambda handler, Kubernetes manifests, and deployment verification scripts, see Implementation Guide.
Output
- Multi-stage Dockerfile for production builds
- Google Cloud Run deployment with Secret Manager integration
- AWS Lambda handler for serverless webhook processing
- Kubernetes Deployment, Service, and Secret manifests
- Deployment verification checklist and script
Error Handling
| Error |
Cause
Implement enterprise RBAC for Evernote integrations.
ReadWriteEditGrep
Evernote Enterprise RBAC
Overview
Implement role-based access control for Evernote integrations, including Evernote Business account handling, shared notebook permissions, multi-tenant architecture, and authorization middleware.
Prerequisites
- Understanding of Evernote Business accounts and shared notebooks
- Multi-tenant application architecture
- Authentication/authorization infrastructure
Instructions
Step 1: Evernote Permission Model
Evernote has built-in sharing permissions for notebooks: READNOTEBOOK, MODIFYNOTEBOOKPLUSACTIVITY, READNOTEBOOKPLUSACTIVITY, GROUP, FULLACCESS. Map these to your application's role system.
const EvernotePermissions = {
READ: 'READ_NOTEBOOK',
WRITE: 'MODIFY_NOTEBOOK_PLUS_ACTIVITY',
FULL: 'FULL_ACCESS'
};
const AppRoles = {
viewer: [EvernotePermissions.READ],
editor: [EvernotePermissions.READ, EvernotePermissions.WRITE],
admin: [EvernotePermissions.FULL]
};
Step 2: RBAC Service
Build a service that checks whether a user has the required permission for an operation. Query shared notebook privileges via noteStore.listSharedNotebooks() and getSharedNotebookByAuth().
class RBACService {
async canAccess(userToken, notebookGuid, requiredPermission) {
const noteStore = this.getAuthenticatedNoteStore(userToken);
const sharedNotebooks = await noteStore.listSharedNotebooks();
const shared = sharedNotebooks.find(sn => sn.notebookGuid === notebookGuid);
if (!shared) return false;
return this.hasPermission(shared.privilege, requiredPermission);
}
}
Step 3: Authorization Middleware
Create Express middleware that validates the user's Evernote token and checks permissions before allowing access to protected routes.
Step 4: Evernote Business Integration
For Evernote Business accounts, use authenticateToBusiness() to get a business token. Business notebooks are shared across the organization. Use getBusinessNotebooks() to list them.
Step 5: Multi-Tenant Support
Isolate tenant data by scoping all Evernote operations to the tenant's access token. Never mix tokens between tenants. Store tenant-to-token mappings with encryption at rest.
For the full RBAC service, middleware, Business account integration, and multi-tenant architecture, see Implementation Guide.
Output
- Evernote permission model mapped to application roles
RBACService class with permission checking
- Express authorization middleware for protected routes
- Evernote Business account integration
Create a minimal working Evernote example.
ReadWriteEdit
Evernote Hello World
Overview
Create your first Evernote note using the Cloud API, demonstrating ENML format and NoteStore operations.
Prerequisites
- Completed
evernote-install-auth setup
- Valid access token (OAuth or Developer Token for sandbox)
- Development environment ready
Instructions
Step 1: Create Entry File
Initialize an authenticated Evernote client. Use a Developer Token for sandbox or an OAuth access token for production.
// hello-evernote.js
const Evernote = require('evernote');
const client = new Evernote.Client({
token: process.env.EVERNOTE_ACCESS_TOKEN,
sandbox: true // false for production
});
Step 2: Understand ENML Format
Evernote uses ENML (Evernote Markup Language), a restricted XHTML subset. Every note must include the XML declaration, DOCTYPE, and root element. Forbidden elements include , , . Only inline styles are allowed (no class or id attributes).
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
<en-note>
<h1>Note Title</h1>
<p>Content goes here</p>
<en-todo checked="false"/> A task item
</en-note>
Step 3: Create Your First Note
Build ENML content and call noteStore.createNote(). The returned object contains the guid, title, and created timestamp.
async function createHelloWorldNote() {
const noteStore = client.getNoteStore();
const content = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
<en-note>
<h1>Hello from Claude Code!</h1>
<p>Created at: ${new Date().toISOString()}</p>
</en-note>`;
const note = new Evernote.Types.Note();
note.title = 'Hello World - Evernote API';
note.content = content;
const createdNote = await noteStore.createNote(note);
console.log('Note GUID:', createdNote.guid);
return createdNote;
}
Step 4: List Notebooks and Retrieve Notes
Use listNotebooks() to enumerate notebooks and getNote() with boolean flags to control what data is returned (content, resources, recognition, alternate data).
const noteStore = client.getNoteStore();
// List all notebooks
const notebooks = await noteStore.listNotebooks();
notebooks.forEach(nb => console.log(`- ${nb.name} (${nb.guid})`));
// Retrieve a note with content
const note = await noteStore.getNote(noteGuid, tr
Manage incident response for Evernote integration issues.
ReadWriteEditBash(curl:*)Grep
Evernote Incident Runbook
Overview
Step-by-step procedures for responding to Evernote integration incidents including API outages, rate limit escalations, authentication failures, data sync issues, and quota exhaustion.
Prerequisites
- Access to monitoring dashboards and production logs
- Production Evernote API credentials
- Communication channels for escalation (Slack, PagerDuty)
Instructions
Incident Classification
| Severity |
Symptoms |
Response Time |
| P1 - Critical |
All Evernote API calls failing, data loss risk |
15 minutes |
| P2 - High |
Persistent rate limits, auth failures for multiple users |
1 hour |
| P3 - Medium |
Intermittent errors, degraded sync performance |
4 hours |
| P4 - Low |
Single user issues, non-critical feature affected |
Next business day |
Step 1: Triage
Check Evernote's status page first. If Evernote is down, activate the circuit breaker and wait.
# Check Evernote service status
curl -sf https://status.evernote.com/api/v2/status.json | jq '.status'
# Check your API connectivity
curl -sf -H "Authorization: Bearer $EVERNOTE_TOKEN" \
https://www.evernote.com/shard/s1/notestore | head -20
# Check error rate in logs (last 15 min)
grep -c 'EDAMSystemException' /var/log/evernote-app.log
Step 2: Rate Limit Escalation
If rate limits are persistent: reduce API call frequency, increase delays between batch operations, and contact Evernote developer support for a rate limit increase.
Step 3: Authentication Failure
For auth failures: verify tokens are not expired (edam_expires), check that production credentials match the production endpoint (sandbox: false), and test with a fresh Developer Token to isolate the issue.
Step 4: Sync Failure Recovery
For sync issues: compare local USN with server USN via getSyncState(). If gap is too large, reset to full sync from USN 0. Verify data integrity after re-sync.
Step 5: Mitigation Strategies
- Circuit breaker: Disable Evernote API calls after N consecutive failures. Retry after cooldown period.
- Graceful degradation: Serve cached data when API is unavailable. Queue writes for retry.
- Failover: Switch to polling-based sync if webhooks stop arriving.
Post-Incident
- Document root cause and timeline
- Update runbook with new failure modes discovered
- Adjust alert thresholds if false positive or missed detection
- Review and improve circuit breaker settings
For the complete diagnostic scripts, mi
Install and configure Evernote SDK and OAuth authentication.
ReadWriteEditBash(npm:*)Bash(pip:*)Grep
Evernote Install & Auth
Overview
Set up the Evernote SDK and configure OAuth 1.0a authentication for accessing the Evernote Cloud API. Covers API key provisioning, SDK installation, OAuth flow implementation, and connection verification.
Prerequisites
- Node.js 18+ or Python 3.10+
- Package manager (npm, pnpm, or pip)
- Evernote developer account
- API key from Evernote developer portal (requires approval, allow 5 business days)
Instructions
Step 1: Request an API Key
- Navigate to the Evernote developer portal
- Submit the API key request form
- Wait for manual approval (up to 5 business days)
- Receive
consumerKey and consumerSecret credentials
Step 2: Install the SDK
set -euo pipefail
# Node.js
npm install evernote
# Python
pip install evernote
Step 3: Configure Environment Variables
cat << 'EOF' >> .env
EVERNOTE_CONSUMER_KEY=your-consumer-key
EVERNOTE_CONSUMER_SECRET=your-consumer-secret
EVERNOTE_SANDBOX=true
EOF
Step 4: Initialize the OAuth Client
const Evernote = require('evernote');
const client = new Evernote.Client({
consumerKey: process.env.EVERNOTE_CONSUMER_KEY,
consumerSecret: process.env.EVERNOTE_CONSUMER_SECRET,
sandbox: process.env.EVERNOTE_SANDBOX === 'true',
china: false
});
Step 5: Implement the OAuth Flow
Set up request token acquisition, user authorization redirect, and callback handling. Alternatively, use a developer token for sandbox testing to skip the OAuth flow entirely.
Step 6: Verify the Connection
Create an authenticated client, access getUserStore(), and call getUser() to confirm authentication succeeds.
For the complete OAuth callback implementation, developer token setup, Python client initialization, and token expiration handling, see OAuth flow reference.
Output
- Installed SDK package in node_modules or site-packages
- Environment variables configured for authentication
- Working OAuth flow implementation
- Successful connection verification
Error Handling
| Error |
Cause |
Resolution |
| Invalid consumer key |
Wrong or unapproved key |
Verify key in the developer portal |
| OAuth signature mismatch |
Incorrect consumer secret |
Check secret matches the portal value |
| Token expired |
Access token older than 1 year |
Re-authenticate the user via OAuth |
| Rate limit reached |
Too many API calls
Set up efficient local development workflow for Evernote integrations.
ReadWriteEditBash(npm:*)Bash(node:*)Grep
Evernote Local Dev Loop
Overview
Configure an efficient local development environment for Evernote API integration with sandbox testing, hot reload, ENML helpers, and a local Express server for OAuth testing.
Prerequisites
- Completed
evernote-install-auth setup
- Node.js 18+ or Python 3.10+
- Evernote sandbox account at https://sandbox.evernote.com
Instructions
Step 1: Project Structure
Organize your project with clear separation of concerns:
evernote-app/
src/
services/ # NoteService, SearchService, etc.
utils/ # ENML helpers, query builder
middleware/ # Auth, rate limiting
test/ # Unit and integration tests
scripts/ # Dev utilities (test-connection, seed-data)
.env.development # Sandbox credentials
.env.production # Production credentials (gitignored)
Step 2: Environment Configuration
Create .env.development with sandbox credentials. Use a Developer Token for quick iteration (skip OAuth during development). Add .env* to .gitignore.
# .env.development
EVERNOTE_CONSUMER_KEY=your-sandbox-key
EVERNOTE_CONSUMER_SECRET=your-sandbox-secret
EVERNOTE_DEV_TOKEN=your-developer-token
EVERNOTE_SANDBOX=true
NODE_ENV=development
PORT=3000
Step 3: Evernote Client Wrapper
Create a client factory that switches between Developer Token (for scripts and tests) and OAuth (for the web app) based on environment configuration.
function createClient() {
if (process.env.EVERNOTE_DEV_TOKEN) {
return new Evernote.Client({
token: process.env.EVERNOTE_DEV_TOKEN,
sandbox: true
});
}
return new Evernote.Client({
consumerKey: process.env.EVERNOTE_CONSUMER_KEY,
consumerSecret: process.env.EVERNOTE_CONSUMER_SECRET,
sandbox: process.env.EVERNOTE_SANDBOX === 'true'
});
}
Step 4: ENML Utility Helpers
Build helper functions: wrapInENML(html), textToENML(text), htmlToENML(html) (strip forbidden elements), and validateENML(content). These prevent BADDATAFORMAT errors during development.
Step 5: Express Server with OAuth
Set up a local Express server with session management for OAuth flow testing. Include routes for /auth/start (get request token), /auth/callback (exchange for access token), and /dashboard (authenticated operations).
Step 6: Quick Test Script
Create a scripts/test-connection.js that verifies SDK setup by calling userStore.getUser() and noteStore.listNotebooks(). Run with node scripts/test-connection.js.
For the full project setup, Express server, ENML utiliti
Deep dive into Evernote data migration strategies.
ReadWriteEditBash(npm:*)Grep
Evernote Migration Deep Dive
Current State
!npm list 2>/dev/null | head -5
Overview
Comprehensive guide for migrating data to and from Evernote, including ENEX export/import, bulk API operations, format conversions (ENML to Markdown, HTML to ENML), and data integrity verification.
Prerequisites
- Understanding of Evernote data model (Notes, Notebooks, Tags, Resources)
- Source/target system access credentials
- Sufficient API quota for migration volume
- Backup strategy in place before starting
Instructions
Step 1: Migration Planning
Assess the migration scope: count notes, notebooks, tags, and total resource size. Estimate API call count and quota consumption. Plan for rate limits (add delays between operations).
async function assessMigration(noteStore) {
const notebooks = await noteStore.listNotebooks();
const tags = await noteStore.listTags();
let totalNotes = 0;
for (const nb of notebooks) {
const filter = new Evernote.NoteStore.NoteFilter({ notebookGuid: nb.guid });
const spec = new Evernote.NoteStore.NotesMetadataResultSpec({});
const result = await noteStore.findNotesMetadata(filter, 0, 1, spec);
totalNotes += result.totalNotes;
}
return {
notebooks: notebooks.length,
tags: tags.length,
totalNotes,
estimatedApiCalls: totalNotes * 2 + notebooks.length + tags.length,
estimatedTimeMinutes: Math.ceil((totalNotes * 2 * 200) / 60000) // 200ms per call
};
}
Step 2: Export from Evernote
Export notes in three formats: ENEX (Evernote's XML format, preserves everything including resources), JSON (structured data for programmatic use), or Markdown (human-readable, loses some formatting).
async function exportToMarkdown(noteStore, noteGuid) {
const note = await noteStore.getNote(noteGuid, true, true, false, false);
const text = enmlToMarkdown(note.content);
return {
title: note.title,
content: text,
tags: note.tagNames || [],
created: new Date(note.created).toISOString(),
resources: (note.resources || []).map(r => ({
filename: r.attributes.fileName,
mime: r.mime,
size: r.data.size
}))
};
}
Step 3: Import to Evernote
Convert source data to ENML format, create notebooks to match source structure, and bulk-create notes with rate limit handling. Verify each import by comparing note counts and content hashes.
Step 4: Migration Runner
Build a migration runner with progress tracking, checkpointing (resume from failure), and verification. Log every operation for audit trail.
For the full migration planner, ENEX parser, format converters, migration runner, and verification tools, see Implementation Guide.
Output
Configure multi-environment setup for Evernote integrations.
ReadWriteEditBash(npm:*)Grep
Evernote Multi-Environment Setup
Overview
Configure separate development, staging, and production environments for Evernote integrations with proper isolation, configuration management, and environment-aware client factories.
Prerequisites
- Multiple Evernote API keys (sandbox for dev/staging, production for prod)
- Environment management infrastructure
- CI/CD pipeline (see
evernote-ci-integration)
Instructions
Step 1: Environment Configuration Files
Create per-environment config files that define the Evernote endpoint, sandbox flag, rate limit settings, and logging level.
// config/environments.js
const configs = {
development: {
sandbox: true,
apiUrl: 'https://sandbox.evernote.com',
rateLimitDelayMs: 0, // No throttle in dev
logLevel: 'debug'
},
staging: {
sandbox: true,
apiUrl: 'https://sandbox.evernote.com',
rateLimitDelayMs: 100,
logLevel: 'info'
},
production: {
sandbox: false,
apiUrl: 'https://www.evernote.com',
rateLimitDelayMs: 200,
logLevel: 'warn'
}
};
module.exports = configs[process.env.NODE_ENV || 'development'];
Step 2: Environment Variables
Define environment-specific .env files. Each environment uses its own API key and token. The EVERNOTE_SANDBOX flag controls which Evernote endpoint the SDK connects to.
# .env.development - sandbox with dev token
EVERNOTE_SANDBOX=true
EVERNOTE_DEV_TOKEN=S=s1:U=...
# .env.production - production with OAuth
EVERNOTE_SANDBOX=false
EVERNOTE_CONSUMER_KEY=prod-key
EVERNOTE_CONSUMER_SECRET=prod-secret
Step 3: Environment-Aware Client Factory
Build a factory that creates properly configured Evernote clients based on the active environment. Include validation that production never uses sandbox tokens.
Step 4: Docker Compose for Local Development
Define services for the app, Redis (caching), and a webhook receiver (ngrok or localtunnel) in docker-compose.yml. Mount .env.development as environment file.
Step 5: Health Check Endpoint
Create a /health endpoint that verifies Evernote API connectivity, reports the active environment, and checks cache availability.
app.get('/health', async (req, res) => {
const checks = {
environment: process.env.NODE_ENV,
sandbox: config.sandbox,
evernoteApi: 'unknown',
cacheConnected: false
};
try {
await userStore.getUser();
checks.evernoteApi = 'connected';
} catch { checks.evernoteApi = 'error'; }
res.json(checks);
});
For the full configuration loader, client factory, Docker setup, and CI/CD environment matrix, see
Implement observability for Evernote integrations.
ReadWriteEditBash(npm:*)Grep
Evernote Observability
Overview
Comprehensive observability setup for Evernote integrations: Prometheus metrics for API call tracking, structured JSON logging, OpenTelemetry tracing, health check endpoints, and alerting rules.
Prerequisites
- Monitoring infrastructure (Prometheus, Datadog, or CloudWatch)
- Log aggregation (ELK, Loki, or CloudWatch Logs)
- Alerting system (PagerDuty, Opsgenie, or Slack webhooks)
Instructions
Step 1: Metrics Collection
Track key metrics with Prometheus counters and histograms: evernoteapicallstotal (by method and status), evernoteapidurationseconds (latency histogram), evernoteratelimitstotal (rate limit hits), evernotequotausagebytes (upload quota consumption).
const { Counter, Histogram } = require('prom-client');
const apiCalls = new Counter({
name: 'evernote_api_calls_total',
help: 'Total Evernote API calls',
labelNames: ['method', 'status']
});
const apiDuration = new Histogram({
name: 'evernote_api_duration_seconds',
help: 'Evernote API call duration',
labelNames: ['method'],
buckets: [0.1, 0.5, 1, 2, 5, 10]
});
Step 2: Instrumented Client
Wrap the NoteStore with a Proxy that automatically records metrics for every API call. Increment counters on success/failure, observe latency in histograms, and count rate limit events.
Step 3: Structured Logging
Use JSON-formatted logs with consistent fields: timestamp, level, method, duration, userId (hashed), noteGuid. Redact access tokens from all log output.
function logApiCall(method, duration, error) {
const entry = {
timestamp: new Date().toISOString(),
service: 'evernote-integration',
method,
duration_ms: duration,
status: error ? 'error' : 'success',
error_code: error?.errorCode
};
console.log(JSON.stringify(entry));
}
Step 4: Health and Readiness Endpoints
Implement /health (liveness: is the process running?) and /ready (readiness: can we reach Evernote API?). Include cache connectivity check.
Step 5: Alert Rules
Configure Prometheus alerts: rate limit hits > 5 in 10 minutes, API error rate > 10%, p95 latency > 5 seconds, quota usage > 90%.
# prometheus-alerts.yml
groups:
- name: evernote
rules:
- alert: EvernoteRateLimited
expr: rate(evernote_rate_limits_total[10m]) > 0.5
for: 5m
labels: { severity: warning }
annotations:
summary: "Evernote rate limits detected"
For
Optimize Evernote integration performance.
ReadWriteEditGrep
Evernote Performance Tuning
Overview
Optimize Evernote API integration performance through response caching, efficient data retrieval, request batching, connection management, and performance monitoring.
Prerequisites
- Working Evernote integration
- Understanding of API rate limits
- Caching infrastructure (Redis recommended, in-memory for simpler setups)
Instructions
Step 1: Response Caching
Cache frequently accessed data (notebook lists, tag lists, note metadata) with TTL-based expiration. Notebook and tag lists change rarely -- cache for 5-15 minutes. Note metadata can be cached for 1-5 minutes.
class EvernoteCache {
constructor(redis) {
this.redis = redis;
}
async getOrFetch(key, fetcher, ttlSeconds = 300) {
const cached = await this.redis.get(key);
if (cached) return JSON.parse(cached);
const data = await fetcher();
await this.redis.setex(key, ttlSeconds, JSON.stringify(data));
return data;
}
async listNotebooks(noteStore) {
return this.getOrFetch('notebooks', () => noteStore.listNotebooks(), 600);
}
async listTags(noteStore) {
return this.getOrFetch('tags', () => noteStore.listTags(), 600);
}
}
Step 2: Efficient Data Retrieval
Use findNotesMetadata() instead of findNotes() to avoid transferring full note content. Only request needed fields in NotesMetadataResultSpec. Fetch full content only when the user explicitly opens a note.
// BAD: Fetches full content for all notes
const notes = await noteStore.findNotes(filter, 0, 100);
// GOOD: Fetches only metadata (title, dates, tags)
const metadata = await noteStore.findNotesMetadata(filter, 0, 100, spec);
// Fetch content only for the specific note user opens
const fullNote = await noteStore.getNote(guid, true, false, false, false);
Step 3: Request Batching
Batch multiple operations using sync chunks instead of individual API calls. Use getSyncChunk() to fetch up to 100 changed notes in a single call instead of 100 getNote() calls.
Step 4: Connection Optimization
Reuse the Evernote client instance across requests. The NoteStore maintains an HTTP connection that benefits from keep-alive. Create one client per user session, not per request.
Step 5: Performance Monitoring
Track API call counts, response times (p50, p95, p99), cache hit rates, and rate limit occurrences. Alert on degradation.
For the complete caching layer, batching strategies, monitoring setup, and benchmark examples, see Implementation Guide.
Output
- Redis-based response caching with TTL management
- Metadata-only query patterns (avoid unnecessary content transfer)<
Production readiness checklist for Evernote integrations.
ReadWriteEditGrep
Evernote Production Checklist
Overview
Comprehensive checklist for deploying Evernote integrations to production, covering API key activation, security hardening, rate limit handling, monitoring, and go-live verification.
Prerequisites
- Completed development and testing in sandbox
- Production API key approved by Evernote (requires review process)
- Production infrastructure provisioned
Instructions
API Key & Authentication
- [ ] Production API key requested and approved by Evernote
- [ ]
EVERNOTE_SANDBOX=false in production config
- [ ] Consumer key and secret stored in secrets manager (not env files)
- [ ] OAuth callback URL uses HTTPS on production domain
- [ ] Token expiration tracking implemented (
edam_expires)
- [ ] Token refresh/re-auth flow tested end-to-end
Security
- [ ] Access tokens encrypted at rest (AES-256-GCM)
- [ ] CSRF protection on OAuth flow
- [ ] API credentials not in source control (
.env in .gitignore)
- [ ] Log output redacts tokens and PII
- [ ] Input validation on all user-supplied content (ENML sanitization)
- [ ] Rate limit handling prevents API key suspension
Rate Limits & Performance
- [ ] Exponential backoff on
RATELIMITREACHED errors
- [ ] Minimum delay between API calls (100-200ms)
- [ ] Response caching for
listNotebooks() and listTags() (5-10 min TTL)
- [ ]
findNotesMetadata() used instead of findNotes() for listings
- [ ] Batch operations use sequential processing with delays
Monitoring & Alerting
- [ ] Health check endpoint verifies Evernote API connectivity
- [ ] Metrics tracked: API call count, latency, error rate, rate limits
- [ ] Alerts configured for rate limits, auth failures, and high error rates
- [ ] Structured logging with correlation IDs
- [ ] Quota usage monitoring with threshold alerts (75%, 90%)
Data Integrity
- [ ] ENML validation before every
createNote/updateNote call
- [ ] Note titles sanitized (max 255 chars, no newlines)
- [ ] Tag names validated (max 100 chars, no commas)
- [ ] Resource hashes verified (MD5 match)
- [ ] Sync state (USN) tracked and persisted for incremental sync
Deployment
- [ ] Production Docker image built with multi-stage build
- [ ]
NODE_ENV=production set in container
- [ ] Graceful shutdown handles in-flight API calls
- [ ] Rollback plan documented and tested
- [ ] Deployment verification script runs post-deploy
Verification Script
#!/bin/bash
set -euo pi
Handle Evernote API rate limits effectively.
ReadWriteEditGrep
Evernote Rate Limits
Overview
Evernote enforces rate limits per API key, per user. When exceeded, the API throws EDAMSystemException with errorCode: RATELIMITREACHED and rateLimitDuration (seconds to wait). Production integrations must handle this gracefully.
Prerequisites
- Evernote SDK setup
- Understanding of async/await patterns
- Error handling implementation
Instructions
Step 1: Rate Limit Handler
Catch EDAMSystemException and check for rateLimitDuration. Implement exponential backoff: wait the specified duration, then retry. Track retry attempts to avoid infinite loops.
async function withRateLimitRetry(operation, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (error.rateLimitDuration && attempt < maxRetries - 1) {
const waitMs = error.rateLimitDuration * 1000;
console.log(`Rate limited. Waiting ${error.rateLimitDuration}s...`);
await new Promise(r => setTimeout(r, waitMs));
continue;
}
throw error;
}
}
}
Step 2: Rate-Limited Client Wrapper
Wrap the NoteStore with a class that adds configurable delays between API calls. Use a request queue to prevent bursts. Track request timestamps for monitoring.
class RateLimitedClient {
constructor(noteStore, minDelayMs = 100) {
this.noteStore = noteStore;
this.minDelayMs = minDelayMs;
this.lastRequestTime = 0;
}
async call(method, ...args) {
const elapsed = Date.now() - this.lastRequestTime;
if (elapsed < this.minDelayMs) {
await new Promise(r => setTimeout(r, this.minDelayMs - elapsed));
}
this.lastRequestTime = Date.now();
return withRateLimitRetry(() => this.noteStore[method](...args));
}
}
Step 3: Batch Operations with Rate Limiting
Process items sequentially with delay between each operation. On rate limit, wait and retry the failed item. Report progress via callback. Collect successes and failures.
Step 4: Avoiding Rate Limits
Strategies to minimize API calls: cache listNotebooks() and listTags() results, use findNotesMetadata() instead of getNote() for listings, request only needed fields in NotesMetadataResultSpec, batch reads with sync chunks instead of individual fetches.
Step 5: Rate Limit Monitoring
Track request counts, rate limit hits, average response times, and wait times. Log statistics periodically to identify optimization opportunities.
For the complete rate limiter, batch processor, monitoring dashboard, and optimization examples, see
Reference architecture for Evernote integrations.
ReadWriteEditGrep
Evernote Reference Architecture
Overview
Production-ready architecture patterns for building scalable, maintainable Evernote integrations. Covers service layer design, caching strategy, sync architecture, and deployment topology.
Prerequisites
- Understanding of microservices or modular monolith architecture
- Cloud platform familiarity (AWS, GCP, or Azure)
- Knowledge of message queues and caching
Instructions
Architecture Layers
Client Layer [Web App / Mobile / CLI]
|
API Layer [Express/Fastify REST API]
|
Service Layer [NoteService | SearchService | SyncService]
|
Integration [EvernoteClient (rate-limited, instrumented)]
|
Infrastructure [Redis Cache | PostgreSQL | Message Queue]
Service Layer Design
Separate concerns into focused services:
- NoteService: CRUD operations, ENML formatting, tag management
- SearchService: Query building, pagination, result enrichment
- SyncService: Webhook handling, incremental sync, conflict resolution
- AuthService: OAuth flow, token storage, refresh logic
// services/index.js - Service registry
class ServiceRegistry {
constructor(noteStore, cache, db) {
this.notes = new NoteService(noteStore);
this.search = new SearchService(noteStore, cache);
this.sync = new SyncService(noteStore, db);
}
}
Caching Strategy
Cache at two levels: in-memory LRU for hot data (note metadata, user info) and Redis for shared state (notebook lists, tag lists, sync checkpoints). Invalidate on webhook notification.
Sync Architecture
Use webhooks as the primary change notification channel. Fall back to polling when webhooks are unavailable. Process changes through a message queue for reliability and retry. Store sync state (USN) in the database for crash recovery.
Evernote Webhook → API Gateway → Message Queue → Sync Worker → Database
↓
Evernote API (fetch changes)
Database Schema
Store mirrored Evernote data locally for fast reads. Key tables: users (token, expiration), notebooks, notes (content, metadata), tags, resources (metadata, file path), syncstate (userid, last_usn).
For the complete architecture diagrams, service implementations, database schema, and scaling guidelines, see Implementation Guide.
Output
- Layered architecture with clear separation of concerns
- Service registry pattern for de
Advanced Evernote SDK patterns and best practices.
ReadWriteEditGrep
Evernote SDK Patterns
Overview
Production-ready patterns for working with the Evernote SDK, including search with NoteFilter, pagination, attachments, tags, error handling wrappers, and batch operations with rate limit handling.
Prerequisites
- Completed
evernote-install-auth and evernote-hello-world
- Understanding of Evernote data model (Notes, Notebooks, Tags, Resources)
- Familiarity with async/await and Promises
Instructions
Pattern 1: Search with NoteFilter
Use NoteFilter for query terms and sort order, paired with NotesMetadataResultSpec to select returned fields. This avoids fetching full note content when only metadata is needed.
const filter = new Evernote.NoteStore.NoteFilter({
words: 'tag:important notebook:Work',
ascending: false,
order: Evernote.Types.NoteSortOrder.UPDATED
});
const spec = new Evernote.NoteStore.NotesMetadataResultSpec({
includeTitle: true, includeUpdated: true,
includeTagGuids: true, includeNotebookGuid: true
});
const result = await noteStore.findNotesMetadata(filter, 0, 100, spec);
Pattern 2: Creating Notes with Attachments
Compute the MD5 hash of the file buffer, create a Resource with the binary data and MIME type, embed it in ENML with , and attach it to the note.
const hash = crypto.createHash('md5').update(fileBuffer).digest('hex');
const resource = new Evernote.Types.Resource();
resource.data = new Evernote.Types.Data();
resource.data.body = fileBuffer;
resource.mime = 'image/png';
const note = new Evernote.Types.Note();
note.title = 'Note with Attachment';
note.content = wrapInENML(`<en-media type="image/png" hash="${hash}"/>`);
note.resources = [resource];
await noteStore.createNote(note);
Pattern 3: Error Handling Wrapper
Wrap API calls to distinguish EDAMUserException (client errors), EDAMSystemException (rate limits, maintenance), and EDAMNotFoundException (invalid GUIDs). Use error.rateLimitDuration for automatic retry delays.
Pattern 4: Batch Operations
Process items sequentially with configurable delay between operations. On rate limit errors, wait for rateLimitDuration seconds then retry. Track progress with callbacks.
Pattern 5: Tag and Notebook Management
Implement getOrCreateTag() and getOrCreateNotebook() for idempotent operations. Use listTags() / listNotebooks() to check existence before creating.
For all nine patterns with complete implementations, see Implementation Guide.
Output
Implement security best practices for Evernote integrations.
ReadWriteEditGrep
Evernote Security Basics
Overview
Security best practices for Evernote API integrations, covering credential management, OAuth hardening, token storage, data protection, and secure logging patterns.
Prerequisites
- Evernote SDK setup
- Understanding of OAuth 1.0a
- Basic cryptography concepts (AES encryption, hashing)
Instructions
Step 1: Credential Management
Store consumerKey, consumerSecret, and access tokens in environment variables or a secrets manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault). Never commit credentials to source control. Add .env to .gitignore.
// Load from environment, fail fast if missing
const requiredVars = ['EVERNOTE_CONSUMER_KEY', 'EVERNOTE_CONSUMER_SECRET'];
for (const v of requiredVars) {
if (!process.env[v]) throw new Error(`Missing required env var: ${v}`);
}
Step 2: Secure OAuth Flow
Add CSRF protection with a state parameter stored in the session. Validate the callback URL matches your registered domain. Use HTTPS-only for all OAuth endpoints. Set secure cookie flags for session tokens.
// Generate CSRF token for OAuth state
const csrfToken = crypto.randomBytes(32).toString('hex');
req.session.oauthCsrf = csrfToken;
// Verify on callback
if (req.query.state !== req.session.oauthCsrf) {
return res.status(403).send('CSRF validation failed');
}
Step 3: Encrypted Token Storage
Encrypt access tokens at rest using AES-256-GCM before storing in your database. Decrypt only when making API calls. Store the encryption key separately from the database.
Step 4: Input Validation
Sanitize all user input before embedding in ENML. Validate note titles (max 255 chars), tag names (max 100 chars, no commas), and notebook names (max 100 chars). Strip forbidden HTML elements and attributes.
Step 5: Secure Logging
Redact access tokens, consumer secrets, and user email addresses from log output. Log only the first 8 characters of tokens for debugging correlation.
function redactToken(token) {
if (!token || token.length < 12) return '***';
return token.slice(0, 8) + '...[REDACTED]';
}
Step 6: Token Lifecycle Management
Track token expiration (edamexpires), implement proactive refresh before expiry, and handle AUTHEXPIRED errors gracefully. Tokens default to 1-year validity but users can set shorter durations.
For the complete security implementation including encrypted storage, CSRF-protected OAuth, input validation, and audit logging, see Implementation Guide.
Output
Upgrade Evernote SDK versions and migrate between API versions.
ReadWriteEditBash(npm:*)Grep
Evernote Upgrade & Migration
Current State
!npm list evernote 2>/dev/null || echo 'evernote SDK not installed'
Overview
Guide for upgrading Evernote SDK versions, converting callback-based code to Promises, handling breaking changes, and maintaining backward compatibility during gradual migration.
Prerequisites
- Existing Evernote integration to upgrade
- Test environment for validation
- Understanding of current implementation patterns
Instructions
Step 1: Check Current Version
Identify your current SDK version and compare against the latest release. Check the changelog for breaking changes between versions.
# Check installed version
npm list evernote
# Check latest available
npm view evernote version
# View changelog
npm view evernote repository.url
Step 2: Review Breaking Changes
Common breaking changes across Evernote SDK versions:
- Constructor changes:
new Evernote.Note() became new Evernote.Types.Note()
- Callback to Promise: Older versions used callbacks, newer versions return Promises
- Import path changes: Module structure may change between major versions
- Thrift version updates: Underlying Thrift protocol may change serialization
Step 3: Convert Callbacks to Promises
Wrap callback-based SDK calls in Promise wrappers for modern async/await usage.
// OLD: Callback pattern
noteStore.getNote(guid, true, false, false, false, (error, note) => {
if (error) return handleError(error);
processNote(note);
});
// NEW: Promise/async pattern
const note = await noteStore.getNote(guid, true, false, false, false);
processNote(note);
Step 4: Compatibility Layer
Build a compatibility layer that supports both old and new SDK patterns during gradual migration. This allows upgrading module by module instead of a big-bang rewrite.
class EvernoteCompat {
constructor(noteStore) {
this.noteStore = noteStore;
}
// Works with both callback and Promise-based SDK
async getNote(guid, opts = {}) {
const { withContent = true, withResources = false } = opts;
return this.noteStore.getNote(guid, withContent, withResources, false, false);
}
async createNote(title, content, notebookGuid) {
const Note = Evernote.Types?.Note || Evernote.Note;
const note = new Note();
note.title = title;
note.content = content;
if (notebookGuid) note.notebookGuid = notebookGuid;
return this.noteStore.createNote(note);
}
}
Step 5: Test Suite Updates
Update test assertions for new SDK response shapes. Add tests for the compatibility layer. Run both unit and integration tests against the
Implement Evernote webhook notifications and sync events.
ReadWriteEditBash(curl:*)
Evernote Webhooks & Events
Overview
Implement Evernote webhook notifications for real-time change detection. Evernote webhooks notify your endpoint that changes occurred, but you must use the sync API to retrieve the actual changed data.
Prerequisites
- Evernote API key with webhook permissions
- HTTPS endpoint accessible from the internet
- Understanding of Evernote sync API
Instructions
Step 1: Webhook Endpoint
Create an Express endpoint that receives webhook POST requests. Evernote sends userId, guid (notebook GUID), and reason (create, update, notebook) as query parameters. Respond with HTTP 200 immediately, then process asynchronously.
app.post('/evernote/webhook', (req, res) => {
const { userId, guid, reason } = req.query;
res.sendStatus(200); // Respond immediately
// Process asynchronously
processWebhook({ userId, notebookGuid: guid, reason })
.catch(err => console.error('Webhook processing failed:', err));
});
Step 2: Webhook Reasons
Handle three webhook reasons: create (new note created), update (note modified), and notebook (notebook-level change). Each triggers a sync of the affected notebook.
Step 3: Sync State Management
Store the last sync USN per user. On webhook receipt, call getSyncState() to get the current server USN, then getFilteredSyncChunk() to fetch only the changes since your last sync.
const syncState = await noteStore.getSyncState();
const chunk = await noteStore.getFilteredSyncChunk(
lastUSN,
100, // maxEntries
new Evernote.NoteStore.SyncChunkFilter({
includeNotes: true,
includeNotebooks: true,
includeTags: true
})
);
Step 4: Event Processing and Handlers
Route sync chunk entries to typed handlers: onNoteCreated, onNoteUpdated, onNoteDeleted, onNotebookChanged. Implement idempotency by tracking processed USNs to handle duplicate webhook deliveries.
Step 5: Polling Fallback
Implement a polling fallback for environments where webhooks are unavailable. Poll getSyncState() on a timer (e.g., every 5 minutes) and sync when updateCount changes.
For the full webhook server, sync manager, event handlers, and polling implementations, see Implementation Guide.
Output
- Express webhook endpoint with async processing
- Sync state manager with USN tracking
- Event router for create, update, and delete operations
- Idempotent event processing (handles duplicate deliveries)
- Polling fallback for non-webhook environments
Error Handling
Ready to use evernote-pack?
|
|
|