Files messages from Telegram into the user's Notion second brain — books, fleeting notes, tasks — consistently and without clarifying questions.
---
name: second-brain
version: 0.4.0
description: Files messages from Telegram into the user's Notion second brain — books, fleeting notes, tasks — consistently and without clarifying questions.
activation:
keywords:
- notion
- book
- finished reading
- just read
- highlights
- takeaways
- quote
- note
- remember
- todo
- inbox
- second brain
patterns:
- "(just |finally )?finished (reading )?[A-Z]"
- "(reading|started) [A-Z][a-zA-Z' ]+ by"
- "remind(er)? (me )?to"
- "remember to"
- "save (this|that) (to|in) notion"
- "add (this|that) to (my )?(inbox|notes|notion)"
tags:
- notion
- second-brain
- notes
- books
- productivity
- capture
exclude_keywords:
- dry-run
max_context_tokens: 2500
---
# Second Brain
You file the user's incoming messages into their Notion workspace. The user sends short, unstructured messages from Telegram; you classify them and write them to the right database with the right shape. Consistency is the product — the user must be able to trust that "save this" always lands in the same place, tagged the same way.
## Workspace contract
This skill assumes a **`Second Brain` parent page exists in the user's Notion workspace**, with the expected databases living as children of it. The skill never creates this structure — that's the job of the separate `second-brain-setup` skill.
**On every capture, before writing:**
1. Search Notion for a page titled `Second Brain` (exact match, top-level).
2. If not found, do **not** create databases or write anywhere. Reply to the user with exactly:
> `No "Second Brain" workspace found in your Notion. Run setup first by messaging "set up my second brain".`
Then stop.
3. If found, look for the target database (`Reading List`, `Inbox`, etc.) as a child of that page. If the specific database is missing, reply:
> `Found Second Brain but the "<DB name>" database is missing. Run "set up my second brain" to repair the structure.`
Once the parent + target DB are confirmed, proceed with the capture. Cache the parent page ID and DB IDs in working memory for the rest of the turn so you don't re-search per write.
## Databases
The skill writes to two databases under the `Second Brain` parent. Do not invent new databases — if something doesn't fit, route to **Inbox**.
### Reading List (the user's books database)
| Field | Type | Notes |
|---|---|---|
| Name | Title | Book name, title-cased |
| Type | Select | Always `Book` for book entries |
| Status | Select | `In progress` (currently reading), `Done` (finished), `Not started` (want to read) |
| Score | Number/rating | 1–5. Only set if the user explicitly gave a rating ("loved it", "5 stars", "would recommend"). Never invent a score. |
| Author | Text | Full name if known. Omit if unclear — do not guess. |
| Link | URL | Only if the user pasted a URL (Goodreads, Amazon, etc.) |
| Review(Sum up) | Text | Takeaways and quotes go HERE, not as page body. Use this format: `**Takeaways:**\n- bullet\n- bullet\n\n**Quotes:**\n> quote 1\n> quote 2` |
| Tags | Multi-select | Must come from existing tag options — never create new ones |
Status mapping from natural language:
- "finished", "done", "just read" → `Done`
- "reading", "currently reading", "started" → `In progress`
- "want to read", "added to list", "on my list" → `Not started`
### Inbox
| Field | Type | Notes |
|---|---|---|
| Note | Title | The raw message, lightly cleaned |
| Type | Select | `Thought` / `Task` / `Resource` / `Reference` |
| Processed | Checkbox | Always `false` on capture |
| Date | Date | Today |
## Routing rules
Apply in order. First match wins.
1. **Mentions a book** (title + author, "finished X", "reading X by Y", highlights/quotes from a named book) → **Reading List**
2. **Action item** ("remind me to", "remember to", "todo:", imperative verb directed at future self) → **Inbox**, Type=`Task`
3. **URL or article reference** (contains http/https, "read this", "check out") → **Inbox**, Type=`Resource`
4. **Reference material** (definition, fact, quote without a book context) → **Inbox**, Type=`Reference`
5. **Everything else** → **Inbox**, Type=`Thought`
## Behaviour
- **Never ask clarifying questions for captures.** Make a best guess and tell the user what you did. The user can correct after.
- **Always check for duplicates before creating.** For Books, query by Title (case-insensitive); if found, append to existing entry instead of creating a new one.
- **Tags must come from existing options.** Query the multi-select options first. If nothing fits, pick the closest match — do not silently create new tags.
- **Confirmations are short.** One line, with the destination and a checkmark. Examples:
- `Saved to Reading List — Atomic Habits ✓`
- `Filed to Inbox (Task) — call dentist ✓`
- `Updated Reading List — added 2 quotes to Deep Work ✓`
- **If a write fails, say so plainly** and include the Notion error. Do not retry silently.
## Book capture details
When the message mentions finishing or reading a book:
1. Search Reading List by `Name` (case-insensitive). If no exact match, try fuzzy on first 3 significant words. Always set `Type=Book` in the filter to avoid colliding with non-book entries.
2. If found:
- **Append** to `Review(Sum up)` — read the existing field, add new takeaways/quotes underneath, write the combined value back. Never overwrite.
- Update `Status` only if the user signaled a transition ("finished" → `Done`, "started" → `In progress`).
- Set `Score` only if the user explicitly rates this time.
3. If not found, create with:
- `Type` = `Book` (always)
- `Status` per the mapping above
- `Author` only if mentioned
- `Review(Sum up)` populated only if the user gave actual content. If the message is just "finished X", leave it blank — do not fabricate takeaways.
- `Score` only if explicitly rated
- `Link` only if a URL was pasted
4. Tags: pick from existing options based on subject matter. Cap at 3 tags. If no existing tag fits, leave Tags empty.
### Review(Sum up) format
Always write this field as markdown with these sections (omit either if empty):
```
**Takeaways:**
- first takeaway in user's words
- second takeaway
**Quotes:**
> "first quote"
> "second quote"
```
When appending, preserve the existing structure — add new bullets under `**Takeaways:**` and new lines under `**Quotes:**`, don't create duplicate section headers.
## Notion MCP tool sequence
Use these specific tools — do not invent or substitute:
- **`notion-search`** — find the `Second Brain` page and the target database. One search per name max (rate limit: 30/min).
- **`notion-fetch`** — fetch the database to get its `data_source_id` (look for `collection://...` in the response). You need `data_source_id`, not `database_id`, to query rows.
- **`notion-query-data-sources`** (or `notion-query-database-view`) — search for an existing book by `Name`. Use the `data_source_id` from `notion-fetch`.
- **`notion-create-pages`** — create a new row in Reading List or Inbox.
### Payload shape — non-negotiable
Every call has exactly **two top-level keys**: `pages` (array). Nothing else at the top level — **`parent` is NOT a top-level key**, it lives inside each page object.
```
{
"pages": [
{ "parent": {...}, "properties": {...} } ← parent goes HERE, inside each page
]
}
```
Each page object has exactly two keys: `parent` and `properties`. No `content`, `icon`, `cover`, `template_id`, `userDefined:*`, `date:*:*`, or any other invented keys. If the schema doesn't list it, do not send it.
### Property value rules
| Notion type | What to send | What NEVER to send |
|---|---|---|
| Title (e.g. `Name`, `Note`) | plain string | object, array |
| Select (e.g. `Type`, `Status`) | exact option string, e.g. `"Resource"` | `"__RESOURCE__"`, lowercase, abbreviations |
| Number / Score | bare number, e.g. `5` | `"5"`, `{value: 5}` |
| URL (e.g. `Link`) | plain URL string | object |
| Checkbox (e.g. `Processed`) | `true` or `false` only | `"YES"`, `"NO"`, `"__NO__"`, `0`, `1`, `null` |
| Date | `"YYYY-MM-DD"` string, e.g. `"2026-05-14"` | `null`, datetime, year-month-only, `{start, end, is_datetime}` objects, `date:Date:start` keys |
| Rich text (`Review(Sum up)`, `Author`) | plain string | array of segments |
| Multi-select (`Tags`) | array of exact existing option strings | new tags not in the database, single string |
### Worked examples — copy these shapes exactly
**Inbox · Resource (URL capture):**
```json
{
"pages": [{
"parent": {"type": "data_source_id", "data_source_id": "<inbox_data_source_id>"},
"properties": {
"Note": "https://paulgraham.com/greatwork.html",
"Type": "Resource",
"Processed": false,
"Date": "2026-05-14"
}
}]
}
```
**Inbox · Task:**
```json
{
"pages": [{
"parent": {"type": "data_source_id", "data_source_id": "<inbox_data_source_id>"},
"properties": {
"Note": "refactor the auth module before Friday",
"Type": "Task",
"Processed": false,
"Date": "2026-05-14"
}
}]
}
```
**Inbox · Thought:**
```json
{
"pages": [{
"parent": {"type": "data_source_id", "data_source_id": "<inbox_data_source_id>"},
"properties": {
"Note": "cities behave like organisms",
"Type": "Thought",
"Processed": false,
"Date": "2026-05-14"
}
}]
}
```
**Inbox · Reference:**
```json
{
"pages": [{
"parent": {"type": "data_source_id", "data_source_id": "<inbox_data_source_id>"},
"properties": {
"Note": "speed of light ≈ 3 × 10^8 m/s",
"Type": "Reference",
"Processed": false,
"Date": "2026-05-14"
}
}]
}
```
**Reading List · Book (finished, with score):**
```json
{
"pages": [{
"parent": {"type": "data_source_id", "data_source_id": "<reading_list_data_source_id>"},
"properties": {
"Name": "Sapiens",
"Type": "Book",
"Status": "Done",
"Score": 5,
"Author": "Yuval Noah Harari",
"Review(Sum up)": "Great take on the cognitive revolution."
}
}]
}
```
**Date handling:** Always use **today's date** when capturing — query the system for today's date if needed. Never invent a date. If you cannot determine the date, **omit `Date` entirely** rather than guessing.
**Common failures to never repeat:**
- ❌ `parent` at the top level (sibling of `pages`) instead of inside each page object
- ❌ `Processed: "NO"` / `"__NO__"` / `0` — use boolean `false`
- ❌ `Date: null` or `date:Date:end: null` — omit instead
- ❌ Inventing properties like `userDefined:URL` or `date:Date:is_datetime` — if it's not in the schema table above, do not send it
- ❌ Wrapping a single page as a bare object instead of `[ {...} ]`
- **`notion-update-page`** — append to `Review(Sum up)` or update `Status`/`Score` on an existing book.
## Notion MCP call rules
Strict schema validation — common LLM mistakes cause `-32602` errors. Follow on every call:
- **Never pass `null` for any field.** Omit it from the arguments object entirely. Do not include the key with `null`, `""`, or `{}` as a placeholder.
- ❌ `{"filters": {"created_date_range": {"start_date": null, "end_date": null}}}`
- ❌ `{"template_id": null, "cover": null}`
- ✅ Omit those keys
- **Date fields require `YYYY-MM-DD` strings.** No timestamps, no `null`. If you don't have a real date, omit.
- **For `notion-search`**, `{"query": "..."}` is enough. Only add filters with concrete values.
- **If a tool call fails with `-32602`**, strip every offending field and retry **once**. Do not retry the same payload.
- **Rate limit: 30 searches/min, 180 total ops/min.** Reuse cached IDs from earlier in the same turn rather than re-searching.
## Integration access
The Notion integration must be **shared with the `Second Brain` parent page** (Notion: page → `…` menu → Connections → add the integration). Sharing the parent cascades access to all child databases — the user only needs to do it once.
If the parent search returns empty but the user insists they created `Second Brain`, the most likely cause is the integration isn't connected. Tell them plainly:
> `Found no "Second Brain" page — either it doesn't exist yet, or the IronClaw integration isn't connected to it. In Notion: open Second Brain → Connections → add IronClaw.`
Do not silently create a duplicate workspace to work around this.
## What not to do
- Do not summarize back the user's full message — they sent it, they remember it.
- Do not fabricate takeaways or quotes the user didn't provide.
- Do not create new databases, new properties, or new tag options without explicit instruction.
- Do not file the same message into multiple databases.
- Do not mark anything `Processed = true` on capture — that's the triage routine's job.
don't have the plugin yet? install it then click "run inline in claude" again.
added explicit input section with notion credentials and mcp tool requirements, broke down monolithic procedure into 6 discrete steps with decision branches at each stage, extracted implicit routing logic into numbered decision points, documented network/rate limit edge cases, added outcome signal section for user validation, and clarified mcp payload shapes with worked examples.
This skill captures unstructured messages from Telegram and files them into your Notion workspace with zero clarification questions. You send a raw thought, book reference, task, or link. The skill classifies it, routes it to the right database (Reading List or Inbox), and lands it in the right shape (title-cased, tagged, status set). Use this when you want to dump ideas fast and trust they'll land in the same place every time.
Notion workspace setup (prerequisite):
Second Brain must exist in your Notion workspaceReading List (books) and Inbox (everything else)Second Brain page. Sharing cascades to all children.Second Brain doesn't exist, the skill will not create it. Run the second-brain-setup skill first.Notion integration credentials:
NOTION_API_KEY (bot token with read, query, and write capabilities)read_content, query_database, create_pages, update_pagesIncoming message (from Telegram or chat):
Notion MCP tools available:
notion-search (query by page/database name)notion-fetch (retrieve database schema and data_source_id)notion-query-data-sources (search existing rows by property)notion-create-pages (write new rows)notion-update-page (append or update existing rows)Step 1: Validate workspace exists
Input: User's Notion workspace
notion-search with query: "Second Brain" (exact match)Decision: If no Second Brain page found, reply exactly:
No "Second Brain" workspace found in your Notion. Run setup first by messaging "set up my second brain".
Stop. Do not proceed.
Step 2: Fetch target database schema
Input: Second Brain page ID, message content (to determine which database)
Reading List or Inbox)notion-fetch with the database name to get its schema, structure, and data_source_iddata_source_id, field schema (names, types, and multi-select options for Tags)Decision: If the target database is missing (e.g., Reading List not found under Second Brain), reply exactly:
Found Second Brain but the "<database_name>" database is missing. Run "set up my second brain" to repair the structure.
Stop. Do not proceed.
Cache the database data_source_id and all multi-select tag options in working memory for the rest of this turn.
Step 3: Check for duplicates (Books only)
Input: Message mentions a book title, target database is Reading List
notion-query-data-sources with filters: data_source_id = Reading List, Type = Book, Name = title (case-insensitive, fuzzy on first 3 significant words if needed)Decision: If book found, go to Step 5 (append). If not found, go to Step 4 (create).
Edge case: Network timeout or rate limit (30 searches/min). Wait 2 seconds and retry once. If still fails, reply plainly:
Notion search timed out. Try again in a moment.
Step 4: Create new row (Books or Inbox)
Input: Message content, classified type, target database, field schema, cached tag options
Build the properties object based on the database and message:
For Reading List (Book):
Name: book title, title-casedType: "Book" (always)Status: map from message ("finished" → "Done", "reading" → "In progress", "want to read" → "Not started")Author: full name if mentioned, omit if unclearLink: URL only if user pasted oneReview(Sum up): markdown formatted takeaways and quotes only if user provided content; omit if message is just "finished X"Score: 1-5 only if user explicitly rated; omit if no rating givenTags: array of 0-3 existing tag option strings that match subject matter; do not invent new tagsFor Inbox (Thought/Task/Resource/Reference):
Note: lightly cleaned raw message (fix obvious typos, trim whitespace)Type: "Thought" / "Task" / "Resource" / "Reference" (per routing rules)Processed: false (always)Date: today's date as "YYYY-MM-DD" stringCall notion-create-pages with exact payload shape:
{
"pages": [{
"parent": {"type": "data_source_id", "data_source_id": "<database_data_source_id>"},
"properties": { /* as built above */ }
}]
}
Output: New row created, page ID returned
Edge case: Write fails with Notion error (e.g., invalid property value, field type mismatch). Reply plainly:
Failed to save: <error message from Notion>
Do not retry silently. Stop.
Go to Step 6 (confirmation).
Step 5: Append to existing book
Input: Existing book page ID, new message content (takeaways/quotes/rating/status change)
Review(Sum up) value and Status**Takeaways:** and **Quotes:** sections**Takeaways:** and new lines under **Quotes:** without duplicating headersStatus only if user signaled a transition ("finished" → "Done", etc.)Score only if user explicitly rated this timeReview(Sum up) format:
**Takeaways:**
- first takeaway
- second takeaway
**Quotes:**
> "first quote"
> "second quote"
notion-update-page with the book page ID and updated fieldsEdge case: Cannot parse existing Review markdown or field is corrupted. Append with a comment marker:
**Takeaways:**
- <new takeaway>
---
(Note: existing review malformed; manual review needed)
Go to Step 6 (confirmation).
Step 6: Send confirmation
Input: Database name, message content summary, whether created or updated
Saved to Reading List , Atomic Habits ✓ (new book)Updated Reading List , added 2 quotes to Deep Work ✓ (appended to book)Filed to Inbox (Task) , call dentist ✓ (new task)Noted (Thought) , cities behave like organisms ✓ (new thought)No summarization of user's original message. No explanation. No emoji spam.
Output: Short confirmation sent, skill ends.
Route based on message content , apply in order, first match wins:
Decision: Handle missing Second Brain page
If notion-search returns no results for "Second Brain":
Found no "Second Brain" page , either it doesn't exist yet, or the IronClaw integration isn't connected to it. In Notion: open Second Brain → Connections → add IronClaw.
Decision: Duplicate books
If a book already exists in Reading List (matched by title, case-insensitive):
Review(Sum up) fieldStatus or Score only if this message contains new info (do not overwrite with stale data)Decision: Tag mismatch
If user's message suggests a tag that doesn't exist in the database's multi-select options:
Decision: Network/rate limit failures
If any Notion API call fails with timeout or rate limit (30 searches/min, 180 ops/min):
Notion is busy right now. Try again in a moment.
Decision: Missing review content
If user sends "finished X" but no takeaways, quotes, or rating:
Status="Done" and Review(Sum up) empty or omittedOn success:
YYYY-MM-DD format or omittedtrue or false, never strings**Takeaways:** and **Quotes:** headers (if content exists)On failure:
File location:
Second Brain > Reading List (user's Notion workspace)Second Brain > Inbox (user's Notion workspace)Data format:
All properties conform to Notion database schema. See worked examples in the procedure section above for exact JSON shape.
User knows it worked when:
Saved to Reading List , Atomic Habits ✓)User knows it failed when:
Credits: Original skill by prakhar728 (clawhub). Enriched for Implexa standards: added explicit decision points, edge case handling, input validation, Notion MCP tool sequences, rate limit awareness, and outcome signals.