email-ingestion
Email ingestion for RAG. Covers EML (Python email module, mailparser), MSG (extract-msg), Gmail and Outlook APIs, thread reconstruction via In-Reply-To / References, recursive attachment handling, signature/quote redaction, chain deduplication, and metadata (sender, date, subject) preservation. USE WHEN: user mentions "email ingestion", "EML", "MSG file", "mailparser", "extract-msg", "Gmail API", "Outlook API", "Microsoft Graph mail", "email thread", "reply-to header", "email attachments", "signature stripping" DO NOT USE FOR: PDF attachments inside emails (extract here, then route to `pdf-extraction`); office attachments (route to `office-docs`); image attachments needing OCR - route to `ocr`; calendar ICS files - not covered here
What this skill does
# Email Ingestion for RAG
## Format Matrix
| Source | Library | Attachments | Threading Info | Auth |
|--------|---------|-------------|----------------|------|
| EML files | `email` (stdlib), `mailparser` | Yes | Headers | None |
| MSG files (Outlook) | `extract-msg` | Yes | Headers + MAPI props | None |
| Gmail API | `google-api-python-client` | Download via attachmentId | `threadId` | OAuth |
| Outlook / MS365 | `msgraph-sdk` | `/attachments` endpoint | `conversationId` | OAuth |
| mbox | `mailbox` (stdlib) | Yes | Headers | None |
## EML — Python stdlib
```python
from email import policy
from email.parser import BytesParser
from email.utils import parseaddr, parsedate_to_datetime
def parse_eml(path: str) -> dict:
with open(path, "rb") as f:
msg = BytesParser(policy=policy.default).parse(f)
body_text = ""
body_html = ""
attachments: list[dict] = []
for part in msg.walk():
if part.is_multipart():
continue
disp = (part.get("Content-Disposition") or "").lower()
ctype = part.get_content_type()
if "attachment" in disp or part.get_filename():
attachments.append({
"filename": part.get_filename(),
"content_type": ctype,
"payload": part.get_payload(decode=True),
"content_id": part.get("Content-ID"),
})
elif ctype == "text/plain" and not body_text:
body_text = part.get_content()
elif ctype == "text/html" and not body_html:
body_html = part.get_content()
return {
"message_id": msg.get("Message-ID"),
"in_reply_to": msg.get("In-Reply-To"),
"references": (msg.get("References") or "").split(),
"from": parseaddr(msg.get("From") or ""),
"to": [parseaddr(a) for a in (msg.get_all("To") or [])],
"cc": [parseaddr(a) for a in (msg.get_all("Cc") or [])],
"subject": msg.get("Subject"),
"date": parsedate_to_datetime(msg.get("Date")) if msg.get("Date") else None,
"body_text": body_text,
"body_html": body_html,
"attachments": attachments,
}
```
## mailparser (Higher Level)
```python
import mailparser
m = mailparser.parse_from_file("note.eml")
print(m.from_, m.to, m.subject, m.date)
print(m.body)
for att in m.attachments:
with open(f"out/{att['filename']}", "wb") as f:
f.write(att["payload"].encode() if isinstance(att["payload"], str)
else att["payload"])
```
## MSG — Outlook Files
```python
import extract_msg
msg = extract_msg.Message("meeting.msg")
data = {
"sender": msg.sender,
"to": msg.to,
"cc": msg.cc,
"subject": msg.subject,
"date": msg.date,
"body": msg.body, # plain text
"html": msg.htmlBody,
"headers": dict(msg.header.items()) if msg.header else {},
}
for att in msg.attachments:
with open(f"out/{att.longFilename}", "wb") as f:
f.write(att.data)
# Embedded MSGs (forwarded messages) are recursive
for att in msg.attachments:
if isinstance(att.data, extract_msg.Message):
inner = att.data
print("embedded subject:", inner.subject)
```
## Gmail API
```python
import os, base64
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
creds = Credentials.from_authorized_user_file("token.json",
["https://www.googleapis.com/auth/gmail.readonly"])
svc = build("gmail", "v1", credentials=creds)
def list_messages(query: str = "newer_than:30d"):
resp = svc.users().messages().list(userId="me", q=query, maxResults=500).execute()
return resp.get("messages", [])
def fetch_message(msg_id: str) -> dict:
m = svc.users().messages().get(userId="me", id=msg_id, format="full").execute()
headers = {h["name"].lower(): h["value"] for h in m["payload"]["headers"]}
def walk(part):
if "parts" in part:
for p in part["parts"]:
yield from walk(p)
else:
yield part
text_parts, attachments = [], []
for part in walk(m["payload"]):
body = part.get("body", {})
if body.get("attachmentId"):
att = svc.users().messages().attachments().get(
userId="me", messageId=msg_id, id=body["attachmentId"]
).execute()
data = base64.urlsafe_b64decode(att["data"])
attachments.append({
"filename": next((h["value"] for h in part.get("headers", [])
if h["name"].lower() == "content-disposition"), ""),
"mime": part.get("mimeType"),
"data": data,
})
elif part.get("mimeType") == "text/plain" and body.get("data"):
text_parts.append(base64.urlsafe_b64decode(body["data"]).decode("utf-8", "replace"))
return {
"id": msg_id,
"thread_id": m.get("threadId"),
"from": headers.get("from"),
"subject": headers.get("subject"),
"date": headers.get("date"),
"message_id": headers.get("message-id"),
"in_reply_to": headers.get("in-reply-to"),
"references": (headers.get("references") or "").split(),
"body": "\n".join(text_parts),
"attachments": attachments,
}
```
## Outlook via Microsoft Graph
```python
from msgraph import GraphServiceClient
from azure.identity.aio import ClientSecretCredential
cred = ClientSecretCredential(
tenant_id=os.environ["MS_TENANT_ID"],
client_id=os.environ["MS_CLIENT_ID"],
client_secret=os.environ["MS_CLIENT_SECRET"],
)
graph = GraphServiceClient(credentials=cred, scopes=["https://graph.microsoft.com/.default"])
async def list_messages(user_id: str):
page = await graph.users.by_user_id(user_id).messages.get()
return page.value
async def get_attachments(user_id: str, message_id: str):
return (await graph.users.by_user_id(user_id)
.messages.by_message_id(message_id)
.attachments.get()).value
```
## Thread Reconstruction
```python
from collections import defaultdict
def build_threads(messages: list[dict]) -> dict[str, list[dict]]:
by_id = {m["message_id"]: m for m in messages if m.get("message_id")}
children = defaultdict(list)
roots = []
for m in messages:
parent = m.get("in_reply_to")
if parent and parent in by_id:
children[parent].append(m)
else:
roots.append(m)
threads: dict[str, list[dict]] = {}
def walk(root):
out = [root]
for c in sorted(children[root["message_id"]], key=lambda x: x.get("date") or ""):
out.extend(walk(c))
return out
for root in roots:
threads[root["message_id"]] = walk(root)
return threads
```
For Gmail / Outlook the provider already exposes `threadId` / `conversationId`. Prefer those when available; fall back to header-based reconstruction for EML/mbox corpora.
## Signature and Reply-Quote Stripping
```python
import re
from email_reply_parser import EmailReplyParser # pip install email-reply-parser
SIGNATURE_MARKERS = [
r"^-- $", # RFC 3676 sig separator
r"^Sent from my (iPhone|iPad|Android)",
r"^Get Outlook for",
r"^Best regards,",
r"^Thanks,?\s*$",
r"^Cheers,?\s*$",
]
SIG_RE = re.compile("|".join(SIGNATURE_MARKERS), re.MULTILINE | re.IGNORECASE)
def clean_body(text: str) -> str:
# Strip quoted replies ("On <date> X wrote:" blocks and ">" lines)
reply = EmailReplyParser.parse_reply(text)
# Cut at first signature marker
m = SIG_RE.search(reply)
if m:
reply = reply[: m.start()]
# Drop quoted lines as last resort
lines = [ln for ln in reply.splitlines() if not ln.lstrip().startswith(">")]
return "\n".join(lines).strip()
```
## Attachment Recursion
```python
from pathlib import Path
def route_attachment(att: dict, base_dir: Path) -> list[dict]:
name = att["filename"] oRelated in Image & Video
watch
IncludedWatch a video (URL or local path). Downloads with yt-dlp, extracts auto-scaled frames with ffmpeg, pulls the transcript from captions (or Whisper API fallback), and hands the result to Claude so it can answer questions about what's in the video.
physical-ai-defect-image-generation
IncludedUse when the user wants to orchestrate defect image generation, run associated setup, or handle outputs on OSMO. The Day 0 path handles cold-start with USD-to-ROI, image-edit augmentation, and AnomalyGen to create initial PCBA datasets. The Day 1 path performs inference and labeling on real images. This skill helps with first-time asset setup, creation of finetuning checkpoints, and configuring deployment. Trigger keywords: defect image generation, dig workflow, dig pipeline, defect image detection workflow, aoi pipeline, aoi anomalygen, usd2roi anomalygen, day 0 pcba, day 1 pcba, day 1 real-photo alignment, day 1 manual roi, metal surface anomaly, glass defect, anomalygen finetune, setup_pcb, setup_metal, setup_glass, setup_pretrained, dig setup, dig datasets, dig pretrained checkpoint, dig image-edit endpoint.
accelint-react-best-practices
IncludedReact performance optimization and best practices. ALWAYS use this skill when working with any React code - writing components, hooks, JSX; refactoring; optimizing re-renders, memoization, state management; reviewing for performance; fixing hydration mismatches; debugging infinite re-renders, stale closures, input focus loss, animations restarting; preventing remounting; implementing transitions, lazy initialization, effect dependencies. Even simple React tasks benefit from these patterns. Covers React 19+ (useEffectEvent, Activity, ref props). Triggers - useEffect, useState, useMemo, useCallback, memo, inline components, nested components, components inside components, re-render, performance, hydration, SSR, Next.js, useDeferredValue, combined hooks.
elevenlabs-agents
IncludedBuild conversational AI voice agents with ElevenLabs Platform using React, JavaScript, React Native, or Swift SDKs. Configure agents, tools (client/server/MCP), RAG knowledge bases, multi-voice, and Scribe real-time STT. Use when: building voice chat interfaces, implementing AI phone agents with Twilio, configuring agent workflows or tools, adding RAG knowledge bases, testing with CLI "agents as code", or troubleshooting deprecated @11labs packages, Android audio cutoff, CSP violations, dynamic variables, or WebRTC config. Keywords: ElevenLabs Agents, ElevenLabs voice agents, AI voice agents, conversational AI, @elevenlabs/react, @elevenlabs/client, @elevenlabs/react-native, @elevenlabs/elevenlabs-js, @elevenlabs/agents-cli, elevenlabs SDK, voice AI, TTS, text-to-speech, ASR, speech recognition, turn-taking model, WebRTC voice, WebSocket voice, ElevenLabs conversation, agent system prompt, agent tools, agent knowledge base, RAG voice agents, multi-voice agents, pronunciation dictionary, voice speed control, elevenlabs scribe, @11labs deprecated, Android audio cutoff, CSP violation elevenlabs, dynamic variables elevenlabs, case-sensitive tool names, webhook authentication
humanizer
IncludedHumanize AI-generated text by detecting and removing patterns typical of LLM output. Rewrites text to sound natural, specific, and human. Uses 28 pattern detectors, 560+ AI vocabulary terms across 3 tiers, and statistical analysis (burstiness, type-token ratio, readability) for comprehensive detection. Use when asked to humanize text, de-AI writing, make content sound more natural/human, review writing for AI patterns, score text for AI detection, or improve AI-generated drafts. Covers content, language, style, communication, and filler categories.
generating-mermaid-diagrams
IncludedSalesforce architecture diagrams using Mermaid with ASCII fallback. Use this skill when generating text-based diagrams for Salesforce architecture, OAuth flows, ERDs, integration sequences, or Agentforce structure. TRIGGER when: user says "diagram", "visualize", "ERD", or asks for sequence diagrams, flowcharts, class diagrams, or architecture visualizations in Mermaid. DO NOT TRIGGER when: user wants PNG/SVG image output (use generating-visual-diagrams), or asks about non-Salesforce systems.