From spamhole
Capture an unsolicited email into the user's SpamHole corpus, ask why it's spam (what pattern it represents), copy the raw email into the corpus, suggest a filter pattern, suggest system-prompt tuning to catch similar emails, optionally push a server-side Gmail filter via an email MCP, and draft a manual unsubscribe-request reply. Use when the user says "save this as a spam example" or "add this to my SpamHole".
npx claudepluginhub danielrosehill/claude-code-plugins --plugin spamholeThis skill uses the workspace's default tool permissions.
Main entry point. Turns one annoying email into a structured corpus entry plus durable defenses.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Share bugs, ideas, or general feedback.
Main entry point. Turns one annoying email into a structured corpus entry plus durable defenses.
gws-personal / gws-business is available).eml file the user already savedwhy_added field and seeds the filter pattern.If the user invoked the skill without a rationale, ask once: "What kind of spam does this represent? (one sentence is fine — e.g. 'AI-faked personalised outreach asking for a backlink', 'unsolicited drip-cadence sales pitch', 'fake-friendly intro request from a scraped list')."
Read ${CLAUDE_USER_DATA:-$HOME/.local/share/claude}/spamhole/config.json for corpus_path. If missing, run setup-corpus first.
Materialise the raw email: if the input is a Gmail id, fetch via the available email MCP (mcp__*__read_email with format: raw or equivalent). If it's pasted text, write it as-is. Save to <corpus>/inputs/emails/<YYYY-MM-DD>-<slug>.eml. Slug = lowercased sender domain + short subject hash.
Parse: extract From, Reply-To, Return-Path, Subject, Date, List-Unsubscribe, plain-text body, HTML body.
Run the email-analyst agent (agents/email-analyst.md) on the saved file. It returns structured JSON: indicators, tracking pixels, confidence, and a filter_instruction snippet.
Append to inventories (all under <corpus>/data/):
processed-emails.json — full entry: filename, dates, sender, subject, body_plain, spam_confidence, spam_reasoning, why_added (the user's rationale), flags object (scraped_personalisation, asymmetric_ask, tracking_links, tracking_pixels, artificial_urgency, templated_structure, commercial_disguised_as_personal), trackers_found counts.sender-blocklist.json — { email, domain, display_name, reason, date }.tracking-domains.json — for each pixel/click-tracker the analyst found.Suggest a filter pattern: based on indicators, propose ONE concrete rule the user can paste into Gmail / Sieve / Fastmail filters. Examples:
from:(*@<domain>) has:the-words "<scraped phrase>" → label + skip inboxlist:(<list-id>) → trash when a List-Id header is the strongest signalSuggest system-prompt tuning: emit a single bullet the user can paste into the system prompt of an AI email-triage agent. Format: "If an email , treat as scraped-list outreach unless the sender has prior thread history." Phrase as an additive heuristic, not a replacement for existing prompt.
Offer optional actions (ask before doing — these have side-effects):
mcp__*__create_filter or similar), offer to create a filter that auto-trashes/labels future mail from this sender. If the older scripts/gmail-block-sender.py is being used by the user instead of an MCP, surface it as a fallback path but recommend the MCP route.Report to the user, in this shape:
Raw .eml may contain personal data (recipient address, internal references, embedded recipient IDs in tracking links). Default to keeping the corpus local. If the user wants to share samples publicly (e.g. to seed a research dataset), redact via the existing inputs/emails/sanitised/ convention before sharing — never copy raw originals into a public location.