Skip to main content
gmail-triage Daily multi-account Gmail inbox triage โ scan unread, classify by urgency, cross-reference contacts, generate actionable digest. Supports ace/personal/skestates accounts.
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/vamseeachanta/workspace-hub --skill gmail-triageThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository Write outbound email and external messages in Vamsee Achanta's voice โ a subtle offer to help, never bold or rash claims. Load before drafting ANY email, LinkedIn/Collide reply, proposal note, or outreach sent under his name.
Save/publish analysis or computation results from ANY ecosystem repo to Hugging Face as a queryable, viewer-renderable dataset. Use when the user wants to "save results to hugging face", "publish dataset to HF", "hugging face data saving", "save analysis results", "hf dataset", "make results queryable", or "render via datasets-server API". Reshapes nested results into flat parquet tables, writes a dataset card with a viewer `configs:` block and provenance, applies license/public-vs-private routing, enforces a domain data-quality gate (faithful-to-source != correct), publishes to `aceengineer/<repo>-<projection>`, and verifies via the datasets-server API.
Clone, create, fork, configure, and manage GitHub repositories. Manage remotes, secrets, releases, and workflows. Works with gh CLI or falls back to git + GitHub REST API via curl.
Related occupations SOC
Based on SOC occupation classification
name gmail-triage description Daily multi-account Gmail inbox triage โ scan unread, classify by urgency, cross-reference contacts, generate actionable digest. Supports ace/personal/skestates accounts. version 1.0.0 author vamsee tags ["email","gmail","triage","digest","automation"] related_skills ["gmail-multi-account","himalaya","gmail-outreach","gmail-extract-and-act"] metadata {"hermes":{"tags":["email","gmail","triage","digest"],"related_skills":["gmail-multi-account","himalaya"]}}
Gmail Triage
Scan all 3 Gmail accounts, classify emails, cross-reference contacts, and produce an actionable digest.
Prerequisites
himalaya configured with 3 accounts (see gmail-multi-account skill)
Contact CSVs available in respective repos
Core Principle: Email is a QUEUE, Not an ARCHIVE Do NOT save all emails to repos. The workflow is:
INBOUND โ TRIAGE โ EXTRACT DATA โ ACT โ DELETE
โ
Topic completed? โ DELETE email
Awaiting reply? โ KEEP (live)
New reply arrives โ RE-ACTIVATE
Extract only structured DATA/information needed (not raw email bodies)
Delete the email when the topic is completed
Keep email alive when awaiting response from other party
When client/other party responds, the topic is live again
Learn from patterns โ extraction and routing improve over time
See GitHub issue #2017 for the full workflow design.
Triage Workflow
Step 1: Scan all inboxes for acct in ace personal skestates; do
echo "=== $acct ==="
himalaya --account $acct envelope list --page-size 50 --output json
done
Step 2: Classify each email Categories (priority order):
URGENT โ from VIP/client contacts, contains "urgent", "asap", "deadline", invoice/payment
ACTIONABLE โ requires response, question asked, meeting request, RFP
FYI โ informational, no action needed, CC'd
NEWSLETTER โ marketing, subscription content, bulk sender
SPAM โ unknown sender, no contact match, suspicious
Step 3: Cross-reference contacts
Search contact CSV for the account
If found: use contact category (client/vendor/recruiter/personal)
If NOT found: flag as "unknown sender" โ recommend add-to-contacts or unsubscribe
Step 4: Generate digest === GMAIL DAILY DIGEST โ {date} ===
[ACE] vamsee.achanta@aceengineer.com
URGENT (2):
- From: client@company.com | Subject: RFP Response Deadline
- From: vendor@co.com | Subject: Invoice #1234 Past Due
ACTIONABLE (3):
- ...
FYI (5): [collapsed]
NEWSLETTER (12): [collapsed, unsubscribe candidates marked]
[PERSONAL] achantav@gmail.com
...
[SKESTATES] skestatesinc@gmail.com
...
=== RECOMMENDED ACTIONS ===
1. Reply to client@company.com RE: RFP (ACE)
2. Review invoice from vendor@co.com (ACE)
3. Unsubscribe from 8 newsletters (PERSONAL)
4. Add 2 unknown senders to contacts or block
Communication Style Profiles When drafting suggested responses in the digest, load the per-account style profile:
config/email/ace-style.yaml โ professional-technical, concise (2-5 sentences)
config/email/personal-style.yaml โ casual-terse, ultra-short (1-2 sentences)
config/email/skestates-style.yaml โ business-formal-warm, medium (3-6 sentences)
Each profile defines greeting, closing, signature, tone, and formality rules.
Match the profile to the sending account and check formality_rules for context-specific adjustments.
See config/email/README.md for full usage guide. Reference: #1986.
Account-Specific Classification Rules
ace
VIP: anyone in GTM prospect list, active clients
URGENT: anything from @ril.com, @lng-agroup.com, @mcdermott.com (known clients)
EXTRACT (not noise): CRE listing senders โ sandsig.com, marcusmillichap.com, loopnet.com,
partnersrealestate.com, ten-x.ccsend.com, c.costarmail.com. These feed structured data
extraction to assethold/data/cre-listings/ via the cre-listing template (#1991).
Do NOT classify as NEWSLETTER or recommend unsubscribe.
NEWSLETTER: LinkedIn notifications, industry digests (keep subscribed but low priority)
NOISE: confirmed noise domains listed in config/email-filters/ace-noise-domains.yaml โ
safe to delete and unsubscribe (collide.io, skylineseven.ccsend.com, etc.)
personal
VIP: family (achanta*, @gmail.com family addresses)
URGENT: banks, government, medical
NEWSLETTER: aggressive unsubscribe candidates
skestates
Automation This skill is designed to run as a cron job:
# Daily at 7 AM CT
0 7 * * * hermes "Load gmail-triage skill. Scan all 3 accounts and deliver digest."
Deliver to: Telegram or CLI local file at ~/.hermes/email-digests/
Gmail API Direct Usage Pattern (no dependencies) For headless server automation, use the Gmail REST API directly with urllib (no pip deps):
import json, os, urllib.request, urllib.parse, base64
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
cfg_path = os.path.expanduser("~/.gmail-mcp/oauth-env.json" )
with open (cfg_path) as f:
cfg = json.load(f)
def refresh_token (acct ):
"""Refresh access token using stored refresh token"""
cred_path = os.path.expanduser(f"~/.gmail-{acct} /credentials.json" )
with open (cred_path) as f:
saved = json.load(f)
data = urllib.parse.urlencode({
"client_id" : cfg["client_id" ],
"client_secret" : cfg["client_secret" ],
"refresh_token" : saved["refresh_token" ],
"grant_type" : "refresh_token" ,
}).encode("utf-8" )
req = urllib.request.Request("https://oauth2.googleapis.com/token" , data=data, method="POST" )
with urllib.request.urlopen(req, timeout=30 ) as resp:
tokens = json.loads(resp.read().decode())
saved.update(tokens)
with open (cred_path, "w" ) as f:
json.dump(saved, f, indent=2 )
return tokens["access_token" ]
def gmail_get (endpoint, token ):
req = urllib.request.Request(
f"https://gmail.googleapis.com/gmail/v1/{endpoint} " ,
headers={"Authorization" : f"Bearer {token} " }
)
with urllib.request.urlopen(req, timeout=30 ) as resp:
return json.loads(resp.read().decode())
def gmail_post (endpoint, token, body ):
data = json.dumps(body).encode("utf-8" )
req = urllib.request.Request(
f"https://gmail.googleapis.com/gmail/v1/{endpoint} " ,
data=data,
headers={"Authorization" : f"Bearer {token} " , "Content-Type" : "application/json" },
method="POST"
)
with urllib.request.urlopen(req, timeout=30 ) as resp:
return json.loads(resp.read().decode())
token = refresh_token("ace" )
profile = gmail_get("users/me/profile" , token)
messages = gmail_get(f"users/me/messages?maxResults=25&q=is:unread" , token)
Creating a Draft in an Existing Thread To reply within an existing email chain (not create a new thread):
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
detail = gmail_get(f"users/me/threads/{thread_id} " , token)
last_msg = detail["messages" ][-1 ]
last_hdrs = {h["name" ]: h["value" ] for h in last_msg["payload" ]["headers" ]}
last_message_id = last_hdrs.get("Message-ID" , "" )
msg = MIMEMultipart("alternative" )
msg["To" ] = "recipient@example.com"
msg["Subject" ] = last_hdrs["Subject" ]
msg["In-Reply-To" ] = last_message_id
msg["References" ] = " " .join(all_message_ids_in_thread)
msg.attach(MIMEText(text_body, "plain" ))
msg.attach(MIMEText(html_body, "html" ))
raw_b64 = base64.urlsafe_b64encode(msg.as_bytes()).decode("utf-8" )
gmail_post("users/me/drafts" , token, {"message" : {"threadId" : thread_id, "raw" : raw_b64}})
Extracting Structured Data from Email Subjects Common CRE/business email patterns:
import re
cap_match = re.search(r'(\d+\.?\d*)\s*%?\s*CAP' , subject, re.IGNORECASE)
price_match = re.search(r'\$(\d+(?:,\d+)*(?:\.\d+)?[KMB]?)' , subject)
sf_match = re.search(r'([\d,]+)\s*SF' , subject, re.IGNORECASE)
years_match = re.search(r'(\d+)\s*(?:Years?|Yr)(?:\s+Remaining)?' , subject, re.IGNORECASE)
vpd_match = re.search(r'([\d,]+)\s*VPD' , subject, re.IGNORECASE)
state_match = re.search(r'\|\s*([A-Z]{2})\s*\|' , subject)
Legal Scan Before Committing Email Data Always scan extracted email content before committing to git repos:
import yaml
with open ("/path/to/workspace-hub/.legal-deny-list.yaml" ) as f:
deny = yaml.safe_load(f)
all_text = " " .join(extracted_data).lower()
for item in deny.get("client_references" , []):
pattern = item["pattern" ]
case_sensitive = item.get("case_sensitive" , False )
searchable = all_text if not case_sensitive else " " .join(extracted_data)
if (pattern.lower() if not case_sensitive else pattern) in searchable:
raise ValueError(f"Protected client reference found: {pattern} " )
Pitfalls
himalaya JSON output can be large โ use --page-size to limit
Contact CSV parsing: watch for malformed entries (angle brackets in email fields)
Don't auto-act on emails โ digest is READ-ONLY, actions need user approval
Rate limit Gmail IMAP โ space requests 1-2 seconds apart
Some emails have no From header โ skip gracefully
Do NOT archive entire email bodies to repos โ extract structured data only (#2017)
Old skills (now in _archived/) used the archive-everything model โ use the queue model via gmail-extract-and-act instead (#2019)