CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/API & SDK
API & SDK/2026-05-04Intermediate

Auto-Classify and Draft Gmail Replies with Claude API and Google Apps Script

A hands-on guide to building a Gmail automation system using Claude API and Google Apps Script. Automatically classify incoming emails and generate reply drafts — with copy-paste code.

Claude API115Google Apps ScriptGmailautomation95email

Every morning I used to open my inbox and find myself writing variations of the same five replies — inquiries, collaboration requests, technical questions. The content varied, but the patterns didn't. That realization led me to build a simple system using Claude API and Google Apps Script (GAS) that now handles the first pass of my email triage automatically.

GAS runs on Google's servers for free, integrates with Gmail out of the box, and adding Claude API on top gives you AI-powered classification and draft generation without spinning up any infrastructure. If you're managing a small business, indie app, or content site and find email eating into your creative time, this setup is worth the two-hour investment to get running.

Why Claude API + Google Apps Script

There are plenty of email automation tools — Zapier, Make, n8n. Most charge a monthly fee, and the visual workflow editors get complicated fast once you need custom logic.

The GAS + Claude API combination works well for three reasons. First, GAS runs serverlessly on Google's infrastructure — no hosting costs, no maintenance, and you can trigger it hourly without thinking about it. Second, Gmail's GAS APIs are genuinely excellent: fetching threads, applying labels, and creating drafts all take a few lines of code. Third, Claude API costs are manageable for email volume. Processing 30–50 emails per day with Haiku 4.5 typically costs a few dollars per month at most, per the Claude API pricing guide.

Setup — API Key and GAS Project

Get your Claude API key:

Log in to the Anthropic Console, navigate to "API Keys," and generate a new key. It starts with sk-ant-....

Create your GAS project:

  1. Go to script.google.com and create a new project
  2. Open "⚙️ Project Settings" → "Script Properties" and add CLAUDE_API_KEY with your key as the value

Storing the key in Script Properties keeps it out of your code and version history.

Fetching Emails and Sending Them to Claude

Here's the entry point — fetching unread messages and routing them through the pipeline:

/**
 * Fetch unread emails and process them with Claude
 * Recommended GAS trigger: time-based, every hour
 */
function processUnreadEmails() {
  const apiKey = PropertiesService.getScriptProperties()
    .getProperty('CLAUDE_API_KEY');
  
  // Fetch up to 10 unread threads from the last 24 hours
  const threads = GmailApp.search('is:unread newer_than:1d', 0, 10);
  
  if (threads.length === 0) {
    console.log('No unread emails');
    return;
  }
  
  threads.forEach(thread => {
    const messages = thread.getMessages();
    const latestMessage = messages[messages.length - 1];
    
    // Skip automated emails (newsletters, notifications)
    const from = latestMessage.getFrom();
    if (isAutoSentEmail(from)) {
      latestMessage.markRead();
      return;
    }
    
    const subject = latestMessage.getSubject();
    const body = latestMessage.getPlainBody().slice(0, 2000);
    
    const result = classifyAndDraftReply(apiKey, subject, body, from);
    applyLabelAndCreateDraft(thread, latestMessage, result);
    latestMessage.markRead();
  });
}
 
/**
 * Basic check to skip automated sender addresses
 */
function isAutoSentEmail(from) {
  const autoPatterns = [
    'noreply', 'no-reply', 'donotreply',
    'notification', 'newsletter', 'mailer-daemon'
  ];
  return autoPatterns.some(pattern => 
    from.toLowerCase().includes(pattern)
  );
}

Classifying and Drafting Replies with Claude

The key design decision here is to do classification and draft generation in a single API call. This cuts latency in half and keeps costs low.

/**
 * Call Claude API to classify the email and generate a reply draft
 * @returns {Object} { category, priority, summary, draftReply }
 */
function classifyAndDraftReply(apiKey, subject, body, from) {
  const prompt = `
Analyze the following email and respond in JSON format only.
 
From: ${from}
Subject: ${subject}
Body:
${body}
 
Respond with this exact JSON structure:
{
  "category": "inquiry|collaboration|technical|spam|other",
  "priority": "high|medium|low",
  "summary": "One sentence summary (under 20 words)",
  "draftReply": "Professional reply draft (under 150 words)"
}
 
Category definitions:
- inquiry: questions about products, services, or pricing
- collaboration: partnership proposals, guest posts, sponsorships
- technical: bug reports, technical questions
- spam: unsolicited sales, irrelevant promotions
- other: anything else
`;
 
  const payload = {
    model: 'claude-haiku-4-5-20251001',
    max_tokens: 1024,
    messages: [
      { role: 'user', content: prompt }
    ]
  };
 
  const options = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'x-api-key': apiKey,
      'anthropic-version': '2023-06-01'
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };
 
  try {
    const response = UrlFetchApp.fetch(
      'https://api.anthropic.com/v1/messages',
      options
    );
    
    if (response.getResponseCode() !== 200) {
      console.error('API error:', response.getContentText());
      return { category: 'other', priority: 'low', draftReply: '' };
    }
    
    const data = JSON.parse(response.getContentText());
    const content = data.content[0].text;
    
    // Extract JSON even if Claude wraps it in a code block
    const jsonMatch = content.match(/\{[\s\S]*\}/);
    if (!jsonMatch) {
      return { category: 'other', priority: 'low', draftReply: '' };
    }
    
    return JSON.parse(jsonMatch[0]);
    
  } catch (error) {
    console.error('Processing error:', error.toString());
    return { category: 'other', priority: 'low', draftReply: '' };
  }
}

Using claude-haiku-4-5-20251001 here is intentional. Email triage doesn't require deep reasoning — it needs consistency and speed. Haiku delivers both at roughly one-fifth the cost of Sonnet, and in practice the classification accuracy is nearly identical for this task.

Applying Labels and Creating Drafts in Gmail

This function takes Claude's output and writes it back into Gmail:

/**
 * Apply labels based on classification and create a reply draft
 */
function applyLabelAndCreateDraft(thread, message, result) {
  const { category, priority, summary, draftReply } = result;
  
  // Get or create the category label
  const labelName = `AI-Classified/${category}`;
  let label = GmailApp.getUserLabelByName(labelName);
  if (!label) {
    label = GmailApp.createLabel(labelName);
  }
  thread.addLabel(label);
  
  // Flag high-priority emails separately
  if (priority === 'high') {
    const urgentLabel = GmailApp.getUserLabelByName('AI-Classified/⭐ Needs Action')
      || GmailApp.createLabel('AI-Classified/⭐ Needs Action');
    thread.addLabel(urgentLabel);
    thread.markImportant();
  }
  
  // Create draft reply (skip spam)
  if (category !== 'spam' && draftReply) {
    const replyBody = `${draftReply}\n\n---\n[AI draft | ${summary}]`;
    GmailApp.createDraft(
      message.getFrom(),
      `Re: ${message.getSubject()}`,
      replyBody,
      {
        threadId: thread.getId(),
        inReplyTo: message.getId()
      }
    );
  }
  
  console.log(`Processed: ${category} / ${priority} / ${summary}`);
}

After running, you'll see an AI-Classified label group in your Gmail sidebar with sub-labels for each category. Drafts land in your Drafts folder attached to the original thread — review, edit, send.

Three Gotchas Worth Knowing About

1. GAS execution time limit

GAS scripts time out at 6 minutes. If your inbox gets heavy and Claude responses are slow, processing more than 10 threads per run risks hitting this limit. Keep the batch size at 10 or fewer and let the hourly trigger handle the queue.

2. JSON extraction from Claude's response

Claude reliably returns JSON, but occasionally wraps it in a markdown code block (```json ... ```). The regex content.match(/\{[\s\S]*\}/) extracts the raw JSON regardless of formatting, which prevents silent failures.

3. Rate limit collisions

If multiple GAS triggers fire in quick succession (which can happen with overlapping time-based triggers), you may hit Claude's rate limits. The Claude API rate limit guide covers handling 429 errors, but the simpler fix is to set a single hourly trigger and store a lastRun timestamp in Script Properties as a guard against double-execution.

Start Small Before Automating

Run the script manually from the GAS editor first. Check that the labels appear correctly and that the draft replies make sense for your email style. Once the output looks good, add a time-based trigger from "Triggers" → "Add Trigger" and set it to run every hour.

The AI-generated drafts aren't meant to go out as-is — they're a starting point. You still read and send. What changes is that you're editing a 90% complete reply instead of writing from scratch, which cuts the time per email from 5 minutes to under 30 seconds for routine messages.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API & SDK2026-07-05
Fable 5 Is Back Worldwide and Sonnet 5 Is the Default — Where Each of the Three Models Belongs in a Solo Automation Stack
With Fable 5 redeployed worldwide and Sonnet 5 now the default, solo automation suddenly has three capable top-tier models to reach for. Instead of ranking them, this piece assigns each a role and captures that in a policy object with a fallback ladder and run-level logging.
API & SDK2026-07-02
Introductory Pricing Has an End Date — Effective-Dated Cost Forecasts for the Sonnet 5 Price Step
Claude Sonnet 5's introductory $2/$10 pricing ends on 2026-08-31 and reverts to $3/$15. A static price map will quietly understate your September forecast by a third. Here is an effective-dated price table and forecast design that absorbs the step.
API & SDK2026-06-28
Did That Post Actually Go Through? Safely Retrying an Interrupted MCP Write Without Double-Executing
When an MCP write tool call is interrupted by a dropped connection, you can't tell whether the server ran it. Here's why naive retries cause double-execution, and a working wrapper that uses idempotency keys and a reconcile read to retry safely — with examples from an unattended pipeline.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →