◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 44 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 44 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
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 API128Google Apps ScriptGmailautomation109email

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-05-15
Automating Wallpaper Classification with Claude Vision API — Accuracy, Cost, and a Batch Job That Survives Interruptions
A firsthand account of automating wallpaper category classification with the Claude Vision API. Honest numbers on accuracy and cost, plus the batch design that stopped me re-paying for the same images.
⬡ API & SDK2026-10-16
Sending 40 Rows From Make to the Claude API Stops at Row 14 — Avoiding the 429 With One Subtraction
Why a no-code automation that sends spreadsheet rows to the Claude API one by one hits a 429 partway through, and how to fix it. Seven spacing strategies tested on a mock bucket, a formula for the Sleep delay, and settings that stop failed rows from vanishing.
⬡ API & SDK2026-10-13
Haiku 5.5 is cheaper per token, but your bill jumps fivefold past about 77k Haiku 4.5 tokens
On the price sheet, Haiku 5.5 costs roughly a tenth of Haiku 4.5. But the same text becomes about 30% more tokens, and a prompt over 100k tokens is billed at five times the rate. Here is the boundary in Haiku 4.5 terms, a script I actually ran, and the changes that return a 400.