Skip to main content

apollo-email-dispatch

Process an email that Justin forwarded to goff.justin+apollo@gmail.com — parse the inline instruction (first line of body or subject prefix), turn the email into the right kind of Obsidian artifact (note, Person note update, append to existing note, ad-hoc judgment), and report back to Telegram. Read-only on Gmail.

インストールへ移動

ソース情報

リポジトリ
justinsensei/apollo-backup
ソースの最終更新活動
2026年7月2日 11:30
検出された SKILL.md の言語
英語
スター
1
フォーク
0

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

ファイルエクスプローラー
3 ファイル

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
apollo-email-dispatch
description
Process an email that Justin forwarded to goff.justin+apollo@gmail.com — parse the inline instruction (first line of body or subject prefix), turn the email into the right kind of Obsidian artifact (note, Person note update, append to existing note, ad-hoc judgment), and report back to Telegram. Read-only on Gmail.
version
1.2.0
platforms
["linux","macos"]
metadata
{"hermes":{"tags":["email","gmail","obsidian","dispatch","polling"],"related_skills":["google-workspace","obsidian","llm-wiki","polling-cron-agent"]}}
# Apollo Email Dispatch Handler skill for the email-forwarding workflow. Justin forwards an email to `goff.justin+apollo@gmail.com` with a one-line instruction at the top of the body (or just a subject prefix); a Gmail filter labels it `Apollo/Inbox`; a poller (`poll_apollo_inbox.py`) detects it and invokes this skill once per new message ID. This skill is **read-only on Gmail**. It never archives, marks read, or replies. The source of truth for "has Apollo seen this?" is the watermark file (`~/.hermes/state/apollo-inbox-watermark.json`), not Gmail label state. ## Inputs One Gmail message ID. The poller passes it via the cron prompt; you call `scripts/load_context.py <id>` to fetch. ## Core Principle: Tweakable Dual-Action Dispatch When Justin forwards an email, he wants one of two actions (or sometimes both) to happen based on keywords and context in his one-line instruction (`instruction` field from `load_context.py`): 1. **Add the email to his Obsidian Vault (File it)** 2. **Create a Todoist task based on the email (Task it)** For each action, Justin will include keywords and some context on the instruction line, for example: - *File this. I like the idea about the flux capacitor. I wonder if we could use it for the improbablity engine.* - *Task due Friday. Gotta finish the book before the weekend.* - *TODO due tomorrow. Check with Anya.* If his instruction contains keywords for **both** (or if he lists them on separate lines/sentences), you must perform **both** actions! See [references/forwarding-examples.md](references/forwarding-examples.md) for concrete mockups of instruction inputs and expected tool outputs. ### Default Behavior (Fallback) If the instruction is empty, has no keywords, or is just "Save as a note", default to **Action 1: Add the email to his vault**. --- ## Action 1: File Email (Save to Vault Inbox) ### Trigger Keywords - Contains: `File this`, `file this`, `file`, `save this`, `save`, `archive this` - Or: Instruction is **empty / none** (default fallback, files to `Inbox/`) ### Storage Destination - Create a new markdown note inside the **`Inbox/`** directory of his vault: `/home/justin.guest/Developer/obsidian-vault/Inbox/<Capitalized Spaced Cleaned Subject> <ID>.md` *(Where `Capitalized Spaced Cleaned Subject` is a capitalized, spaced version of the subject, and `ID` is the 14-digit creation timestamp string `YYYYMMDDHHmmss` at write time. This conforms to the vault's standard `Title ID.md` format. Never use kebab-case or lowercase filenames).* ### Note Structure & Frontmatter All new notes must start with standard frontmatter format (pre-evaluating Templater values at write time using Justin's local time (America/New_York)): ```yaml --- id: "<YYYYMMDDHHmmss at write time, e.g. 20260520143157>" daily_note: "[[<YYYY-MM-DD Weekday>|YYYY-MM-DD Weekday]]" category: "[[Scraps]]" --- ``` Below the frontmatter, format the note body cleanly: 1. **Title:** Large H1 heading `# [Cleaned Subject]` 2. **Email Metadata:** Labeled key-value pairs: - **From:** [Original Sender Name <Email>] - **To:** [Recipient Email] - **Date:** [Original Email Date] 3. **Context Note:** If Justin provided an instruction or extra thoughts (e.g., "I like the idea about the flux capacitor..."), include them in a `## Context` section. 4. **Summary:** Add a concise markdown summary of the email's content (who sent it, what it is about). 5. **Email Content:** A `---` line or `## Email Content` header followed by the cleaned plaintext body of the forwarded email. --- ## Action 1b: Log Email (Save to Inputs/Emails/) ### Trigger Keywords - Contains: `log this`, `log email`, `log thread`, `save as email log`, `log` ### Storage Destination - Create a new markdown note inside **`Inputs/Emails/`** (`$OBSIDIAN_VAULT_PATH/Inputs/Emails/YYYY-MM-DD - Spaced Subject.md`) *(Where `YYYY-MM-DD` is the current date, and `Spaced Subject` is a cleaned, capitalized, spaced version of the subject).* ### Note Structure & Frontmatter All new email log notes must start with this frontmatter format: ```yaml --- id: "<YYYYMMDDHHmmss at write time>" daily_note: "[[<YYYY-MM-DD Weekday>|YYYY-MM-DD Weekday]]" category: "[[Emails]]" type: email original_url: "<Gmail message search link, e.g. https://mail.google.com/mail/u/0/#search/rfc822msgid:<Message-ID> or search query>" --- ``` Below the frontmatter, format the note body cleanly: 1. **Title:** Large H1 heading `# 📧 Email Log: [Cleaned Subject]` 2. **Email Metadata:** Labeled key-value pairs: - **From:** [Original Sender Name <Email>] - **Date:** [Original Email Date] - **Subject:** [Cleaned Subject] - **Message ID:** [Gmail Message ID] (to fetch full email if needed) 3. **Context Note:** If Justin provided any instruction or extra thoughts, include them in a `## Context` section. 4. **Summary:** Add a brief summary of the entire thread (concise bullets or paragraph outlining key discussions, decisions, and action items — **do not copy the email contents verbatim**). --- ## Action 2: Create a Todoist Task ### Trigger Keywords - Contains: `Task`, `task`, `TODO`, `todo`, `to do`, `To do` ### Task Creation Specifications Use `mcp_todoist_add_tasks` to add a single task to Justin's Todoist **Inbox** (projectId: `"6VGcQ7r6HW5r87j9"` or `"inbox"`): 1. **Task Name (`content`):** Determine a meaningful, clear, actionable name based on the email's subject and summary. Do NOT use the raw `Fwd: ...` subject or a generic name. 2. **Task Description (`description`):** Append whatever contextual note Justin has given you to the end of the task description (e.g. `Gotta finish the book before the weekend.`). 3. **Due Date (`dueString`):** Extract any due date specified in the instruction (e.g., "due Friday", "due tomorrow", "due 6/15"). Pass this as the `dueString` parameter of the task. If no due date is mentioned, omit the `dueString` parameter. 4. **Comment on Task:** After creating the task, immediately call `mcp_todoist_add_comments` to add a comment containing a concise summary of the email, including the original sender, recipient, and date. This keeps the task neat while preserving the original context. --- ## Other Intent Shapes (Legacy Support) If Justin explicitly uses the following phrasing, support these specific paths: - **Person note:** *"Person note for <Name>"* or *"<Name> works at <Org>"* → Create a brand-new Person note named `<Firstname> <Lastname> <ID>.md` (e.g. `Aly Lalji 20260611074153.md`, where `ID` is `YYYYMMDDHHmmss` at write time) in `/home/justin.guest/Developer/obsidian-vault/Inbox/` (or update the existing note in-place if it already exists under `Notes/Contacts/` or `/Inbox/`). Conforms to the `obsidian-contacts.mdc` standard. - **Company/Organization note:** *"New company <Name>"*, *"Company note for <Name>"*, or *"<Name> is a new company"* → Create a brand-new Organization note named `<Organization Name> <ID>.md` (e.g. `SignLab 20260101000000.md`, where `ID` is `YYYYMMDDHHmmss` at write time) in `/home/justin.guest/Developer/obsidian-vault/Inbox/` (or update the existing note in-place if it already exists under `Notes/Contacts/` or `/Inbox/`). Conforms to the `obsidian-contacts.mdc` standard. - **Project note:** *"New project <Name>"*, *"Project note for <Name>"*, or *"<Name> is a new project"* → Create `<Capitalized Spaced Project Name>.md` (no ID, e.g. `B2C Expansion Strategy.md`) under `/home/justin.guest/Developer/obsidian-vault/Inbox/` first (or `/home/justin.guest/Developer/obsidian-vault/Notes/Projects/` if moving directly) using standard Project formatting. Conforms to the `note-creation.mdc` standard (capitalized, spaced titles; no lowercase kebab-case). - **Append to existing note:** *"Add to <note title>"*, *"Append to <note title>"*, or *"Add [content] to the <note title> note"* → Find the closest match vault-wide (the user may say "in my inbox" or guess the wrong folder, but the note often resides in its correct MECE directory like `personal/trips/` or `projects/`) and append a dated bullet point (format: `- YYYY-MM-DD | Ingest — <context/details>`). - **Calendar scheduling:** *"Schedule this"* or *"Add this to my calendar"* → Parse event details and call `gws_multi.py --account personal-main|work calendar create`. --- ## Process Workflow For each message ID detected by the poller: 1. **Load context:** Run `load_context.py <MESSAGE_ID>` to get the JSON email payload. 2. **Determine intents:** Scan the `instruction` string for trigger keywords to decide if you need to: - File the email to Vault (`File this` / `save` / empty) - Create a Todoist task (`Task` / `TODO` / `to do`) - Or both! 3. **Execute actions:** - If filing: Generate and write the markdown note to `Inbox/` or `Inputs/Emails/`. - After filing, run `llm-wiki` integrate-light: append `Utilities/log.md` only (daily note wikilink as last field). - If creating a task: Call `mcp_todoist_add_tasks` and then `mcp_todoist_add_comments` with the email summary. - If both: Do both operations. 4. **Report back:** Output a single concise line per email in your final response (for Telegram delivery): - `✅ <subject snippet> → filed to vault and created Todoist task "<task_name>"` - `✅ <subject snippet> → filed to vault as <filename>.md` - `✅ <subject snippet> → created Todoist task "<task_name>"` - `❌ <subject snippet> → load_context failed (is_real=false). Skipping.` --- ## Guidelines & Rules - **Verify Writes:** After writing any note to the vault, verify the file exists and is non-empty before reporting success. - **Avoid Loop Guards:** Skip any emails sent by Apollo himself to prevent infinite loops. - **Rate Limit:** Limit processing to at most 5 emails per cron tick. If there are more, report that the rest will be picked up next tick. ## Troubleshooting & Pitfalls - **Empty plaintext parts in HTML emails:** Some systems/senders (like Expedia transactional emails) send `multipart/alternative` messages with a nearly empty text/plain part containing only `\r\n` (whitespace). Because `google_api.py` previously checked `if not body:`, this truthy whitespace caused it to skip extracting the actual HTML content. Always ensure `google_api.py` checks `if not body.strip():` when extracting bodies to correctly fall back to the rich HTML content.
GitHubで見る