Skip to content
evernote-sdk-patterns logo

Evernote SDK Patterns

evernote-sdk-patterns

Advanced Evernote SDK patterns and best practices. Use when implementing complex note operations, batch processing, search queries, or optimizing SDK usage. Trigger with phrases like "evernote sdk patterns", "evernote best practices", "evernote advanced", "evernote batch operations".

SKILL.md

Full skill instructions

Evernote SDK Patterns

Overview

Production-ready patterns for working with the Evernote SDK, including search, filtering, batch operations, and resource handling.

Prerequisites

  • Completed evernote-install-auth and evernote-hello-world
  • Understanding of Evernote data model (Notes, Notebooks, Tags, Resources)
  • Familiarity with Promises/​async patterns

Core Patterns

Pattern 1: Search with NoteFilter

const Evernote = require('evernote');

async function searchNotes(noteStore, searchQuery, maxResults = 100) {
  // Create filter with search grammar
  const filter = new Evernote.NoteStore.NoteFilter({
    words: searchQuery,
    ascending: false,
    order: Evernote.Types.NoteSortOrder.UPDATED
  });

  // Specify which metadata to return
  const spec = new Evernote.NoteStore.NotesMetadataResultSpec({
    includeTitle: true,
    includeContentLength: true,
    includeCreated: true,
    includeUpdated: true,
    includeTagGuids: true,
    includeNotebookGuid: true
  });

  // Find notes (offset, maxNotes)
  const result = await noteStore.findNotesMetadata(filter, 0, maxResults, spec);

  console.log(`Found ${result.totalNotes} notes (returned ${result.notes.length})`);
  return result;
}

// Usage examples:
// searchNotes(noteStore, 'tag:important notebook:Work');
// searchNotes(noteStore, 'intitle:meeting created:day-7');
// searchNotes(noteStore, 'todo:false'); // Unchecked todos

Pattern 2: Search Grammar Queries

// Build complex search queries
function buildSearchQuery(options = {}) {
  const parts = [];

  if (options.notebook) {
    parts.push(`notebook:"${options.notebook}"`);
  }

  if (options.tags && options.tags.length) {
    options.tags.forEach(tag => parts.push(`tag:"${tag}"`));
  }

  if (options.excludeTags && options.excludeTags.length) {
    options.excludeTags.forEach(tag => parts.push(`-tag:"${tag}"`));
  }

  if (options.createdAfter) {
    parts.push(`created:${formatDateForSearch(options.createdAfter)}`);
  }

  if (options.updatedAfter) {
    parts.push(`updated:${formatDateForSearch(options.updatedAfter)}`);
  }

  if (options.inTitle) {
    parts.push(`intitle:"${options.inTitle}"`);
  }

  if (options.hasAttachments) {
    parts.push('resource:*');
  }

  if (options.hasTodos !== undefined) {
    parts.push(`todo:${options.hasTodos}`);
  }

  if (options.text) {
    parts.push(options.text);
  }

  // Use 'any:' for OR logic, default is AND
  if (options.matchAny) {
    return 'any: ' + parts.join(' ');
  }

  return parts.join(' ');
}

function formatDateForSearch(date) {
  // Format: YYYYMMDD
  return date.toISOString().slice(0, 10).replace(/​-/​g, '');
}

// Example: Find notes tagged "urgent" in "Work" notebook from last week
const query = buildSearchQuery({
  notebook: 'Work',
  tags: ['urgent'],
  createdAfter: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
});
// Result: 'notebook:"Work" tag:"urgent" created:20240115'

Pattern 3: Paginated Note Retrieval

async function* getAllNotesMetadata(noteStore, filter, spec, pageSize = 100) {
  let offset = 0;
  let hasMore = true;

  while (hasMore) {
    const result = await noteStore.findNotesMetadata(filter, offset, pageSize, spec);

    for (const note of result.notes) {
      yield note;
    }

    offset += result.notes.length;
    hasMore = offset < result.totalNotes;

    console.log(`Progress: ${offset}/​${result.totalNotes}`);
  }
}

// Usage with async iteration
async function processAllNotes(noteStore) {
  const filter = new Evernote.NoteStore.NoteFilter({ words: 'tag:process' });
  const spec = new Evernote.NoteStore.NotesMetadataResultSpec({
    includeTitle: true,
    includeUpdated: true
  });

  for await (const note of getAllNotesMetadata(noteStore, filter, spec)) {
    console.log(`Processing: ${note.title}`);
    // Process each note
  }
}

Pattern 4: Efficient Note Content Retrieval

async function getNoteWithOptions(noteStore, noteGuid, options = {}) {
  const {
    withContent = true,
    withResources = false,
    withRecognition = false,
    withAlternateData = false
  } = options;

  return noteStore.getNote(
    noteGuid,
    withContent,
    withResources,        // Include binary resource data
    withRecognition,      // Include OCR data
    withAlternateData     // Include alternate representations
  );
}

// Only get content (most common)
const note = await getNoteWithOptions(noteStore, guid);

// Get note with all attachments
const noteWithFiles = await getNoteWithOptions(noteStore, guid, {
  withResources: true
});

Pattern 5: Creating Notes with Attachments

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

async function createNoteWithAttachment(noteStore, title, content, filePath) {
  const Evernote = require('evernote');

  // Read file and compute hash
  const fileBuffer = fs.readFileSync(filePath);
  const hash = crypto.createHash('md5').update(fileBuffer).digest('hex');
  const mimeType = getMimeType(filePath);

  // Create resource (attachment)
  const resource = new Evernote.Types.Resource();
  resource.data = new Evernote.Types.Data();
  resource.data.body = fileBuffer;
  resource.data.size = fileBuffer.length;
  resource.data.bodyHash = Buffer.from(hash, 'hex');
  resource.mime = mimeType;

  // Set file attributes
  resource.attributes = new Evernote.Types.ResourceAttributes();
  resource.attributes.fileName = path.basename(filePath);

  // Create ENML with embedded resource
  const enml = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/​pub/​enml2.dtd">
<en-note>
  ${content}
  <en-media type="${mimeType}" hash="${hash}"/>
</​en-note>`;

  // Create note
  const note = new Evernote.Types.Note();
  note.title = title;
  note.content = enml;
  note.resources = [resource];

  return noteStore.createNote(note);
}

function getMimeType(filePath) {
  const ext = path.extname(filePath).toLowerCase();
  const mimeTypes = {
    '.png': 'image/​png',
    '.jpg': 'image/​jpeg',
    '.jpeg': 'image/​jpeg',
    '.gif': 'image/​gif',
    '.pdf': 'application/​pdf',
    '.txt': 'text/​plain',
    '.doc': 'application/​msword',
    '.docx': 'application/​vnd.openxmlformats-officedocument.wordprocessingml.document'
  };
  return mimeTypes[ext] || 'application/​octet-stream';
}

Pattern 6: Working with Tags

async function getOrCreateTag(noteStore, tagName) {
  // Try to find existing tag
  const tags = await noteStore.listTags();
  const existing = tags.find(t =>
    t.name.toLowerCase() === tagName.toLowerCase()
  );

  if (existing) {
    return existing;
  }

  // Create new tag
  const tag = new Evernote.Types.Tag();
  tag.name = tagName;
  return noteStore.createTag(tag);
}

async function addTagsToNote(noteStore, noteGuid, tagNames) {
  // Get current note
  const note = await noteStore.getNote(noteGuid, false, false, false, false);

  // Resolve tag names to GUIDs
  const tagGuids = await Promise.all(
    tagNames.map(async name => {
      const tag = await getOrCreateTag(noteStore, name);
      return tag.guid;
    })
  );

  // Merge with existing tags
  const existingTags = note.tagGuids || [];
  const allTags = [...new Set([...existingTags, ...tagGuids])];

  // Update note
  note.tagGuids = allTags;
  return noteStore.updateNote(note);
}

Pattern 7: Notebook Operations

async function getOrCreateNotebook(noteStore, notebookName, stack = null) {
  const notebooks = await noteStore.listNotebooks();
  const existing = notebooks.find(n =>
    n.name.toLowerCase() === notebookName.toLowerCase()
  );

  if (existing) {
    return existing;
  }

  const notebook = new Evernote.Types.Notebook();
  notebook.name = notebookName;
  if (stack) {
    notebook.stack = stack;
  }

  return noteStore.createNotebook(notebook);
}

async function moveNoteToNotebook(noteStore, noteGuid, notebookName) {
  const notebook = await getOrCreateNotebook(noteStore, notebookName);
  const note = await noteStore.getNote(noteGuid, false, false, false, false);

  note.notebookGuid = notebook.guid;
  return noteStore.updateNote(note);
}

Pattern 8: Error Handling Wrapper

class EvernoteError extends Error {
  constructor(originalError) {
    super(originalError.message || 'Evernote API error');
    this.name = 'EvernoteError';
    this.code = originalError.errorCode;
    this.parameter = originalError.parameter;
    this.rateLimitDuration = originalError.rateLimitDuration;
    this.original = originalError;
  }

  get isRateLimit() {
    return this.code === Evernote.Errors.EDAMErrorCode.RATE_LIMIT_REACHED;
  }

  get isNotFound() {
    return this.code === Evernote.Errors.EDAMErrorCode.UNKNOWN;
  }

  get isInvalidData() {
    return this.code === Evernote.Errors.EDAMErrorCode.BAD_DATA_FORMAT;
  }
}

async function withErrorHandling(operation) {
  try {
    return await operation();
  } catch (error) {
    if (error.errorCode !== undefined) {
      throw new EvernoteError(error);
    }
    throw error;
  }
}

// Usage
try {
  const note = await withErrorHandling(() =>
    noteStore.getNote(guid, true, false, false, false)
  );
} catch (error) {
  if (error instanceof EvernoteError && error.isRateLimit) {
    console.log(`Rate limited. Retry in ${error.rateLimitDuration}s`);
  }
}

Pattern 9: Batch Operations with Rate Limit Handling

async function batchProcess(items, operation, options = {}) {
  const {
    concurrency = 1,  // Keep at 1 to avoid rate limits
    delayMs = 100,
    onProgress = () => {}
  } = options;

  const results = [];
  let processed = 0;

  for (const item of items) {
    try {
      const result = await operation(item);
      results.push({ success: true, item, result });
    } catch (error) {
      if (error.rateLimitDuration) {
        // Wait for rate limit to clear
        console.log(`Rate limited, waiting ${error.rateLimitDuration}s...`);
        await sleep(error.rateLimitDuration * 1000);
        // Retry
        const result = await operation(item);
        results.push({ success: true, item, result });
      } else {
        results.push({ success: false, item, error });
      }
    }

    processed++;
    onProgress(processed, items.length);

    // Add delay between operations
    if (processed < items.length) {
      await sleep(delayMs);
    }
  }

  return results;
}

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

// Usage
const notes = await searchNotes(noteStore, 'tag:archive');
const results = await batchProcess(
  notes.notes,
  note => noteStore.deleteNote(note.guid),
  {
    delayMs: 200,
    onProgress: (done, total) => console.log(`${done}/​${total}`)
  }
);

Output

  • Reusable SDK patterns for common operations
  • Efficient search with NoteFilter
  • Pagination for large result sets
  • Attachment handling with proper MIME types
  • Tag and notebook management utilities
  • Production error handling

Error Handling

ErrorCauseSolution
RATE_LIMIT_REACHEDToo many API callsUse rateLimitDuration, add delays
BAD_DATA_FORMATInvalid ENMLValidate before sending
DATA_CONFLICTConcurrent modificationRefetch and retry
QUOTA_REACHEDAccount storage fullCheck user's remaining quota

Resources

Next Steps

See evernote-core-workflow-a for note creation and management workflows.

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View