Files
gmail-inbox-architect/skills/deploy/SKILL.md
T

22 KiB

name, description
name description
deploy Activates approved rules from the Tracker in Gmail. Use when the user says "deploy the rules", "turn on the rules", "make the rules live", "activate my Gmail rules", "set up the filters", or "I'm ready to go live". Implements gmail_filter_safe rules via Google Workspace MCP directly, walks the user through installing apps_script_needed rules via Google Apps Script with a step-by-step walkthrough, and parks studio_candidate rules for future use. Also creates the "! Rule Needed" label on first deploy. Runs after import approves rules. Part of the Gmail Inbox Architect plugin.

deploy — Gmail Inbox Architect

What This Skill Does

Takes approved rules from the Tracker and makes them live. There are three deployment paths depending on the rule type:

  • Gmail sorting rules (gmail_filter_safe) — Claude creates these directly via its Gmail connection. Fast, no user action required beyond confirming.
  • Automation scripts (apps_script_needed) — Claude writes a script, walks the user through installing it in Google Apps Script, guides the test run, and reviews the logs.
  • Studio candidates (studio_candidate) — acknowledged and parked. No deployment until Studio support is added to this plugin.

Nothing goes live without explicit user confirmation. All deployed rules are marked active in the Tracker after successful deployment.

This skill is entirely self-contained. All templates, script patterns, and walkthrough steps are embedded below.


When This Skill Is Triggered

  • User says: "deploy the rules", "turn on the rules", "set up the filters", "let's push this batch", "go live", "activate the rules"
  • Import skill directs the user here after the review queue is complete
  • User says: "the script is ready to deploy", "install the automation"

Prerequisites

Before deploying, verify:

  • _status.md exists with Phase = "Rule Build" or "Deploy-ready"
  • Tracker exists with at least one rule where enabled = FALSE and deployability is set
  • Bible.md exists (needed to confirm label taxonomy before creating filters)

Read the apps_script_log.md file from the project folder if it exists. This file tracks previous script deployments and known issues for this project. Reference it before writing any new Apps Script code to avoid repeating past mistakes.


Dependencies

  • Google Workspace MCP — for Gmail filter creation, label ID lookup, and Gmail search during post-deploy verification
  • No other external dependencies — Apps Script code is generated inline

Execution Flow

Phase 1 → Sanity check and queue load (what's ready to deploy)
Phase 2 → Deploy gmail_filter_safe rules (Claude executes via Gmail connection)
Phase 3 → Generate and deploy apps_script_needed rules (walkthrough)
Phase 4 → Acknowledge studio_candidate rules (park, no action)
Phase 5 → Update Tracker (mark deployed rules as enabled=TRUE)
Phase 6 → Update _status.md and close out

Phases 2, 3, and 4 are independent — run whichever apply based on what's in the queue. It is valid to do Phase 2 only (no Apps Script rules in this batch), or Phase 3 only.


Phase 1 — Sanity Check and Queue Load

Read _status.md and the Tracker.

From the Tracker, load all rules where enabled = FALSE. Group by deployability:

  • gmail_filter_safe → Phase 2 queue
  • apps_script_needed → Phase 3 queue
  • studio_candidate → Phase 4 queue

Also load any rules where enabled = FALSE and deployability is blank — treat these as needing a deployability decision. Ask Claude which path they belong on before proceeding.

Present the deployment plan:

"Here's what's ready to go live:

[N] sorting rules — I can create these directly in Gmail right now. [N] automation script rules — these need a small script installed in Google Apps Script. [N] parked rules — saved for a future step.

Want to start with the sorting rules? That's the fastest part."


Phase 2 — Deploy Gmail Filter Rules

Step 2.1 — Present for final confirmation

Before creating any filters, show a clean summary. Group rules by target label.

"Here are the [N] sorting rules I'm about to create. Take a look before I turn them on:

Finance/AMEX — email from americanexpress.com with 'statement' in the subject Vendors/Shipping — email from ups.com, fedex.com — automatically filed, marked as read Clients/Transit — any email where [client domain] is sender or recipient [... etc]

Once I create these, new email will start sorting automatically. Ready?"

Wait for explicit confirmation. Do not proceed without it.

Step 2.2 — Look up label IDs

Call list_gmail_labels to get the Gmail label ID for each target label. Gmail filters require label IDs, not label names.

Map each base_label value in the queue to its corresponding label ID.

If a label is missing (not found in Gmail):

"I can't find the label '[label name]' in Gmail — it may not have been created yet. Do you want me to create it now, or skip this rule for now?"

Wait for answer before proceeding.

Step 2.3 — Create filters

For each gmail_filter_safe rule, call manage_gmail_filter with:

Criteria (build from Tracker fields):

  • from_domain: build as from:domain.com — multiple domains use OR logic: {from:domain1.com from:domain2.com}
  • to_or_cc_domain (participant pattern): combine as: {from:domain.com to:domain.com}
  • subject_contains: subject:keyword — multiple keywords joined with OR or AND based on the rule's intent (default OR unless notes specify otherwise)
  • has_attachment: has:attachment
  • Combined signals: join with spaces (AND logic in Gmail search)

Actions (from Tracker fields):

  • addLabelIds: the label ID from Step 2.2
  • markAsRead: TRUE if mark_read = TRUE
  • Skip inbox (archive): set removeLabelIds: ["INBOX"] if archive = TRUE

Create one filter per rule. Do not batch multiple rules into one filter — they are harder to manage and delete individually.

After each successful creation, note it. If a creation fails, log the error and continue with the remaining rules — do not abort the entire batch.

Step 2.4 — Post-deploy verification

After all filters are created, run a quick spot-check. For 2-3 of the higher-volume rules, call search_gmail_messages using the same query and confirm Gmail returns the expected threads. This validates the filter syntax fired correctly.

Report back:

"Done — [N] sorting rules are now active in Gmail. New email matching these patterns will be sorted automatically from this point forward.

[If any failed:] [N] rules couldn't be created — [plain English reason]. Want to troubleshoot those now or skip them?"


Phase 3 — Apps Script Deployment

Apps Script handles rules that Gmail's native filters can't: attachment filename patterns, body text signals, multi-condition logic.

Step 3.1 — Read the issue log

Before writing any code, check for apps_script_log.md in the project Drive folder. If it exists, read it. Note any previous errors or patterns to avoid.

Step 3.2 — Generate the script

Write a complete, runnable Google Apps Script file from the apps_script_needed rules in the Tracker. The script must be syntactically correct, well-commented, and ready to paste without modification.

Script architecture:

/**
 * Gmail Classification Script
 * Project: Gmail Inbox Architect — [account]
 * Generated: [date]
 * Rules covered: [comma-separated list of rule_ids]
 *
 * BEFORE RUNNING:
 * 1. Set DRY_RUN = true for your first test run
 * 2. Review the execution log after the test
 * 3. Set DRY_RUN = false only after confirming the test looks correct
 *
 * Issue log: apps_script_log.md in your project Drive folder
 */

// ─── CONFIGURATION ────────────────────────────────────────────────────────────
const DRY_RUN = true;        // true = log only, no changes. Set false after testing.
const MAX_THREADS = 50;      // Threads to process per rule per run (raise if needed)
const LOOKBACK_HOURS = 2;    // Only check email newer than this many hours
// ──────────────────────────────────────────────────────────────────────────────

function runClassification() {
  const startTime = new Date();
  Logger.log('=== Gmail Classification Run ===');
  Logger.log('Mode: ' + (DRY_RUN ? 'DRY RUN (no changes)' : 'LIVE'));
  Logger.log('Started: ' + startTime.toISOString());

  const rules = buildRules();
  let totalLabeled = 0;
  const errors = [];

  for (const rule of rules) {
    try {
      const count = applyRule(rule);
      totalLabeled += count;
      if (count > 0) {
        Logger.log('[' + rule.id + '] Applied to ' + count + ' thread(s)');
      }
    } catch (e) {
      const msg = '[' + rule.id + '] ERROR: ' + e.message;
      Logger.log(msg);
      errors.push(msg);
    }
  }

  Logger.log('---');
  Logger.log('Total threads processed: ' + totalLabeled);
  Logger.log('Errors: ' + errors.length);
  Logger.log('Finished: ' + new Date().toISOString());

  if (errors.length > 0) {
    Logger.log('ERROR DETAILS:');
    errors.forEach(e => Logger.log(e));
  }
}

function applyRule(rule) {
  // Build time-bounded query so we only process recent email
  const cutoff = new Date(Date.now() - LOOKBACK_HOURS * 60 * 60 * 1000);
  const after = Utilities.formatDate(cutoff, 'UTC', 'yyyy/MM/dd');
  const query = rule.query + ' after:' + after;

  const threads = GmailApp.search(query, 0, MAX_THREADS);
  if (threads.length === 0) return 0;

  const label = rule.labelName
    ? GmailApp.getUserLabelByName(rule.labelName)
    : null;

  if (rule.labelName && !label) {
    throw new Error('Label not found: ' + rule.labelName +
      ' — create this label in Gmail first');
  }

  let count = 0;
  for (const thread of threads) {
    const subject = thread.getFirstMessageSubject();

    if (DRY_RUN) {
      Logger.log('  [DRY RUN] Would label "' + subject + '" → ' + rule.labelName);
    } else {
      if (label) label.addToThread(thread);
      if (rule.archive) thread.moveToArchive();
      if (rule.markRead) thread.markRead();
      Logger.log('  Labeled: "' + subject + '" → ' + rule.labelName);
    }
    count++;
  }
  return count;
}

function buildRules() {
  // Each entry below is one rule from the Tracker.
  // query: Gmail search query that identifies matching threads
  // labelName: exact Gmail label path (must exist in Gmail already)
  // archive: move out of inbox after labeling
  // markRead: mark as read after labeling (use sparingly)
  return [
    [RULES_ARRAY  generated per rule below]
  ];
}

Query building per rule type:

For each apps_script_needed rule in the Tracker, build a query string:

  • from_domain only: "from:domain.com"
  • from_domain + subject_contains: "from:domain.com subject:keyword"
  • has_attachment = TRUE: append "has:attachment"
  • body_signal (body text): use bare keyword (Gmail search searches body by default): "from:domain.com invoice" — note this in the rule's comment
  • Attachment filename pattern: Gmail search does not support filename regex natively; use "has:attachment from:domain.com" as the filter and note in comments that the Apps Script getAttachments() check refines this. Add attachment name check inline using thread.getMessages()[0].getAttachments() pattern match if the rule requires filename specificity.

Attachment filename check pattern (embed when needed):

// Check attachment filename pattern
const messages = thread.getMessages();
let hasMatchingAttachment = false;
for (const msg of messages) {
  for (const att of msg.getAttachments()) {
    if (/\.(stp|step)$/i.test(att.getName())) {
      hasMatchingAttachment = true;
      break;
    }
  }
  if (hasMatchingAttachment) break;
}
if (!hasMatchingAttachment) continue; // skip thread

After writing the script:

Show it to Claude for review before presenting to the user:

"Here's the script I've written. It covers [N] rules. Take a look — I'll walk you through installing it once you're ready."

Then present the script to the user in a code block they can copy.

Step 3.3 — Installation walkthrough

Walk the user through step by step. Use numbered steps, plain English.

"Here's how to install this. It sounds more technical than it is — just follow the steps and let me know if anything looks different from what I describe.

Step 1 — Open Google Apps Script Go to script.google.com. You may need to sign in with your Google account.

Step 2 — Create a new project Click the blue '+ New project' button in the top left. You'll see a page with some starter code already in it.

Step 3 — Replace the existing code Click anywhere in the code editor. Press Ctrl+A (Windows) or Cmd+A (Mac) to select everything. Then delete it. Paste the script I gave you above.

Step 4 — Name the project Click 'Untitled project' at the top left and rename it to: 'Gmail Classifier — [account]' Then press Enter.

Step 5 — Save Press Ctrl+S (Windows) or Cmd+S (Mac). You should see the save icon stop spinning.

Let me know when you've done these steps and I'll walk you through the test run."

Wait for confirmation before continuing.

Step 3.4 — Test run walkthrough

"Now let's test it. The script is set to 'dry run' mode right now — it will tell you what it would do without actually changing anything.

Step 6 — Run the test At the top of the script editor, you'll see a dropdown that says 'runClassification'. Make sure that's selected, then click the triangle (▶) Run button.

Step 7 — Authorize the script A popup will appear asking for permission. Click 'Review permissions', choose your Google account, then click 'Allow'. This lets the script read your Gmail. (This only happens the first time.)

Step 8 — View the logs After the run finishes, click 'Execution log' at the bottom of the screen. You'll see a list of what the script found.

Copy everything from the execution log and paste it back to me."

Wait for the user to paste the logs.

Step 3.5 — Review the test logs

Read the pasted execution log. Check for:

  • Errors: any [ERROR] lines → diagnose and fix the script
  • Unexpected matches: threads labeled that shouldn't be → tighten the query
  • Missing matches: expected threads not found → check the query or LOOKBACK_HOURS
  • Label not found errors: the Gmail label doesn't exist yet → create it first

If the log looks clean:

"The test looks good — the script found [N] threads that match the rules, and nothing looks out of place. Ready to go live?

Here's what you need to change: open the script, find the line that says const DRY_RUN = true; near the top, and change true to false. Save it. That's the only change."

If there are issues, fix the script, show the corrected version, and repeat from Step 3.4.

Step 3.6 — Set up the trigger

After the user confirms DRY_RUN = false:

"Last step — let's set it to run automatically.

Step 9 — Open triggers In the script editor, click the clock icon on the left sidebar (it looks like a stopwatch). Or go to Extensions → Apps Script (if you're in Google Sheets) → Triggers.

Actually, since you're already in the editor: look for 'Triggers' in the left sidebar. It's the icon that looks like an alarm clock. Click it.

Step 10 — Add a trigger Click '+ Add Trigger' in the bottom right.

Set these options:

  • Function to run: runClassification
  • Deployment: Head
  • Event source: Time-driven
  • Type of time-based trigger: Hour timer
  • Every: [1 hour is a good starting point — suggest 15 minutes if the user wants near-real-time; suggest 6 hours if they prefer low noise]

Click Save.

The script will now run automatically on that schedule."

Step 3.7 — Escalation path

At any point in the walkthrough where the user is uncertain or stuck:

"If any of this feels unclear or you want someone to help install it, this is a great time to loop in one of your team's AI experts or technical contacts. The script and all these instructions are saved in your project Drive folder — they'll have everything they need to get it set up."

Log this suggestion in apps_script_log.md as a note if the user escalates.


Phase 4 — Studio Candidates

After handling the two active deployment paths, acknowledge parked rules:

"[N] rules are saved for a future step — these are cases where the pattern is too fuzzy for the current tools to handle reliably. They're in your Tracker and won't be lost. When we add Studio support to this system, we'll come back to them."

No action required. These remain in the Tracker with enabled = FALSE and deployability = studio_candidate.


Phase 5 — Update Tracker

After successful deployment, update each deployed rule in the Tracker:

  • Set enabled = TRUE
  • Append to notes: "Deployed [date] via [gmail_filter / apps_script v[N]]"

For rules that failed deployment:

  • Leave enabled = FALSE
  • Append to notes: "Deploy attempted [date] — failed: [reason]"

For Apps Script rules deployed via script: update all rules covered by the script to enabled = TRUE together, since they go live as a bundle when DRY_RUN is set false.


Phase 6 — Update apps_script_log.md

After any Apps Script deployment (successful or not), update or create apps_script_log.md in the project Drive folder.

File format:

# Apps Script Log
## Project: [Gmail account]
## Purpose: Read this file before writing any new Apps Script for this project.
## Reference it to avoid repeating past mistakes.

---

## Script v[N] — [date]

**Rules covered:** [comma-separated rule_ids]
**Status:** [Testing / Active / Retired]
**DRY_RUN disabled:** [date]
**Trigger:** Every [N] hours

### Known issues
[List any errors encountered during testing or after going live]

### Resolutions
[What was changed to fix each issue]

### Notes
[Anything the next session should know before touching this script]

If the file already exists, append a new version block. Do not overwrite old entries — the history is the point.


Phase 7 — Update _status.md and Close Out

Update _status.md:

Last Updated: [timestamp]
Last Agent: Claude (CoWork) — deploy skill
Phase: [Deploy / Maintenance — depending on whether all rules are now live]
Last Completed Step: [N] gmail_filter_safe rules created. [N] apps_script_needed rules
  deployed via Apps Script v[N]. [N] studio_candidate rules parked.
Pending Work: [NONE / or: [N] rules failed deployment — see notes]
Handoff Target: NONE
Notes: [Apps Script log updated. Tracker updated. Any escalations noted.]

Close out with the user:

If everything deployed successfully:

"You're live. Here's what's now running:

[N] sorting rules active in Gmail — new email will be sorted automatically [N] automation script rules running every [N] hours — checking for matching patterns 🅿️ [N] rules parked for future Studio support

If you notice anything sorting incorrectly, say 'review a rule' and I'll help adjust it. If email stops sorting the way you expect, say 'check the script' and I'll look at the logs."

If some rules failed:

"Most rules are live. [N] couldn't be deployed right now — [plain English summary]. Want to troubleshoot those now or come back to them later?"


Handling Bulk Backfill Requests

If the user asks to apply labels to thousands of existing emails (not just new ones going forward), this is a separate operation from filter deployment. Apply the North Star rule:

  1. Lowest-cost path first. If the volume is manageable (under 500 threads per label), Claude can run the Apps Script with a larger MAX_THREADS value and a wider lookback window. Explain this option.

  2. For large volumes (thousands of threads), produce a task order to a lower-cost agent. Do not burn Claude tokens on mechanical row-by-row operations.

  3. Never auto-backfill. Always confirm with the user before bulk-applying labels to existing email.


Non-Technical User Language Rules

Never use: filter, API, MCP, schema, regex, Apps Script (except when naming the product explicitly), boolean, deploy, trigger (use "schedule" instead)

Substitute:

  • "Gmail filter" → "sorting rule"
  • "Apps Script" → "automation script" (or "Google's automation tool" on first mention)
  • "trigger" → "schedule" or "automatic schedule"
  • "deploy" → "turn on" or "go live"
  • "boolean" → "yes/no"
  • "query" → "search pattern"

Number every step in the walkthrough. Confirm before proceeding to the next major step.


Safety Rules (Hardcoded — Cannot Be Overridden)

  1. Explicit confirmation before any Gmail mutation. Present the full list of rules to be deployed and wait for a clear "yes" before creating any filter or running any live script.

  2. DRY_RUN = true in every generated script. Never generate a script with DRY_RUN = false. The user sets this manually after reviewing test logs.

  3. No filter creation without a valid label ID. If a label doesn't exist in Gmail, stop and ask. Do not create filters pointing to non-existent labels.

  4. No bulk backfill without explicit request and confirmation. Filters and scripts apply to new email only by default. Backfilling existing email requires a separate explicit decision.

  5. Tracker updated after deployment. Every deployed rule must be marked enabled=TRUE. Every failed rule must have its failure noted. No silent state drift.

  6. apps_script_log.md always updated. Never complete an Apps Script deployment without updating the log file. The log is how future sessions avoid repeating mistakes.