---
name: substack-import
description: "Import a Substack 'Export your data' archive into the Maxy Neo4j graph. Skill-only plugin owned by the archive-ingest-operator specialist. Opt-in per brand — not enabled by default."
tools: []
always: false
embed: false
specialist: archive-ingest-operator
metadata: {"platform":{"optional":true,"pluginKey":"substack-import"}}
---

# Substack Import

Ingests a Substack "Export your data" archive (extracted directory or zip — `posts.csv` + per-post HTML bodies under `posts/`, `subscribers.csv`, an engagement events file when present, and an `images/` tree referenced from post bodies) into the Maxy Neo4j graph. Skill-only plugin — no MCP server, no admin tools added. The skill runs under the `specialists:archive-ingest-operator` specialist, which owns bulk external-archive ingestion.

## When this applies

The admin agent delegates to `specialists:archive-ingest-operator` when the operator drops an extracted Substack archive directory (or a `<publication>.zip` path) into chat and asks to import it. The specialist runs the skill's owner + publication confirmation flow before any CSV or HTML body is read, then dispatches the two existing write surfaces:

- **Posts** → per-post Task-tool dispatch to `librarian`, which loads `document-ingest` and writes one `:KnowledgeDocument {source:'substack', kind:'substack-post', substackPostId}` per essay. `:Section` chunking is owned by `document-ingest`'s in-turn typed-section classification, not this plugin.
- **Subscribers** → `archive-ingest-operator` directly MERGEs a single `:KnowledgeDocument {kind:'substack-subscriber-roster'}` via `memory-write`, then per-row `memory-write` for each subscriber `:Person` (MERGEd on `(accountId, email)`), then `:MENTIONS {mentionContext:'substack-subscription', tier, subscribedAt, unsubscribedAt?, totalOpens?, totalClicks?, lastOpenedAt?, lastClickedAt?, engagementWindowDays?}` from the roster KD to each `:Person`. Engagement aggregates come from `email_activity.csv` (filename varies by export — the skill tries a known list).

## What this plugin is not

- **Not a new graph writer.** Every write routes through existing surfaces — `document-ingest` (via librarian) for posts, `memory-write` for subscribers, roster KD, and images. No new MCP tool, no new archive-write handler.
- **Not a new label.** No `:SubstackPublication` anchor; `publicationName` is a short-string property on every post KD and the roster KD. No bespoke `:Post`, `:Subscriber`, `:Subscription`. Posts are `:KnowledgeDocument`, subscribers are `:Person`, images are `:DigitalDocument`.
- **Not a new edge type.** The closed-edge rule (`archive-ingest-operator.md`) forbids inventing `:SUBSCRIBED_TO`. Subscription is `:MENTIONS {mentionContext:'substack-subscription', tier, ...engagement}` from the roster KD to each `:Person`. Per-event detail is intentionally aggregated to per-edge totals.
- **Not a live Substack client.** Archive-only — no RSS poll, no API, no scraper. Comments are not in the export and not in scope.

## Relationship to other plugins

- **memory** — the underlying Cypher-write surface used by the skill (`memory-write`, `memory-update`, `memory-search`). Every node carries `source='substack'` + `createdByAgent='substack-import'` for provenance, plus `importId` (UUID per skill run) so a future dream-cycle pass can detect orphans or selectively re-ingest.
- **document-ingest** (memory skill, run by librarian) — owns the per-post `:KnowledgeDocument` writes and `:Section` chunking. Dispatched from `archive-ingest-operator` via the Task tool with `sourceType: 'substack'` and a synthetic stable `attachmentId = "substack-post-${substackPostId}"`.
- **archive-ingest-operator specialist** — owns execution. See the delegation clause in [archive-ingest-operator.md](../../templates/specialists/agents/archive-ingest-operator.md).
- **x-import** — sibling skill-only ingest plugin under the same specialist. The two share no code; the pattern (provenance confirmation → walk → bulk gate → write through existing surfaces) is the same.
