Executive conclusion
The production workflow viska-fastmail-ingest-transform (37ZscHy7SyDYx1CC) is active and fit for its final n8n scope: process each newly arriving Substack publication email into a deterministic OKF parcel, deliver it through a signed upload, and preserve its body, substantive links, images, and canonical video/social URLs for downstream retrieval.
The workflow is not a historical mailbox crawler. That is not an unfinished n8n feature. The native IMAP Trigger reacts to new-mail events and does not enumerate messages already present in a mailbox. Historical roster reconciliation has therefore been transferred to ViskaRes as seat-operated research ingestion through its existing Fastmail MCP surface: ViskaRes #225.
Final verdict: production-ready for forward ingestion. Historical reconciliation is explicitly outside this workflow and outside ViskaN8N ownership.
Production identity
| Property | Verified value |
|---|---|
| Workflow | viska-fastmail-ingest-transform |
| n8n ID | 37ZscHy7SyDYx1CC |
| Status | Active, not archived |
| Active version | 35768a96-003d-4c70-b322-9c3df72b23bc |
| Production update | 2026-08-12T10:03:49.511Z |
| Trigger count | 1 |
| Error workflow | Z6tlnXRbPCqyZqlf |
| Workflow nodes | 6 |
End-to-end flow
Email Trigger (IMAP)
→ Build OKF Parcel
→ Assert Contract
→ Sign Parcel Upload
→ Handle Sign Outcome
→ PUT Parcel to Signed URL
1. Intake
The IMAP Trigger watches the Substack mailbox using the bound n8n IMAP credential. It requests resolved MIME content, which is required because the live multipart publication messages returned empty bodies under format:simple.
The mailbox-side search is:
["ALL", ["HEADER", "LIST-ID", "substack.com"], ["HEADER", "X-MAILGUN-TAG", "post"]]
This accepts Substack publication posts generically and excludes transactional messages such as subscription receipts. It is not a sender or newsletter allowlist. Messages are left unread (postProcessAction: nothing).
2. Deterministic transformation
Build OKF Parcel runs once per item. It does not call an LLM. For each email it:
- removes forwarding prefixes from the title;
- uses the original bare Substack Message-ID as the ingest origin and deduplication identity;
- resolves known publication aliases and otherwise derives a stable publication ID from the canonical post URL;
- isolates the Substack article body rather than treating the full email chrome as content;
- converts supported HTML structure into Markdown;
- unwraps Substack redirects;
- strips capability and tracking query parameters by allowlist;
- removes subscription, account, sharing, and footer chrome while retaining substantive links;
- preserves cleaned image URLs and canonical YouTube, Vimeo, Substack video, X, and Twitter URLs without mirroring external binaries;
- distinguishes a genuine paid-content gate from a footer notice and emits explicit completeness metadata;
- emits first-class
linksandmediafields alongside the Markdown body; - derives a deterministic parcel ID and
parcels/substack/*.mdobject key from source identity, publication time, and a content hash.
The resulting parcel is OKF v0.2 Markdown with Viska schema metadata, per-field provenance, source metadata, and the original article body.
3. Fail-closed contract gate
Assert Contract runs once per item and refuses delivery when:
- required identity or payload fields are missing;
- the Message-ID still carries angle brackets and would bypass the deduplication index;
- the object key is outside
parcels/substack/; - the extracted body is shorter than 50 characters.
This gate caught the live zero-body multipart failure before a malformed parcel could be written.
4. Signed delivery and idempotency
The workflow requests a signed upload for the parcels lane and then PUTs the Markdown body to that signed URL. No bucket credential is stored in the workflow.
Signer outcomes are explicit:
- signed URL returned: upload the parcel;
object already exists: treat the content-addressed collision as already delivered and stop cleanly;- any other response: fail the execution.
This prevents retries from turning an idempotent collision into an operational error while preserving failure visibility for every other signer problem.
Live evidence
Execution 48250 established that the n8n Substack mailbox contained three publication messages and one transactional receipt in one batch. That execution exposed two defects subsequently fixed by PR #137: multipart bodies were empty under format:simple, and $input.first() discarded all but the first item.
After the fixes and canonical re-emission, ViskaDB independently verified the current three-post roster:
| Publication post | L1 object | Body | Links/media and retrieval |
|---|---|---|---|
| Inflation Risk In The Global Economy | 6,317 bytes | 4,056 characters | 7 links and 2 media URLs; body plus all 7 links retrievable through viska_mimir_ro |
| Currency Wars and The Repricing Of FX Volatility | 11,901 bytes | 10,050 characters | 2 YouTube media URLs; its older L1 object predates the first-class links field |
| Global Liquidity Watch: Weekly Update, Aug 11 | 2,655 bytes | 849 characters | Canonical capital-wars L2 row with 1 retrievable link |
ViskaDB commit 23975cd added okf_parcels.links, projected producer links into L2, and left a positive/negative retrieval control. At verification time, production held 95 Substack parcels, two parcels with first-class indexed links, and eight indexed link URLs. Retired source aliases were absent from L2.
Regression controls
The focused production-contract suite passed all six tests on 2026-08-12:
- new signed items survive while idempotent collisions are dropped cleanly;
- IMAP uses resolved publication bodies and excludes transactional mail;
- every item in a publication batch is transformed and validated independently;
- canonical YouTube and X URLs remain in searchable links and Markdown;
- extracted links are written into parcel frontmatter for L2 indexing;
- independent mail items retain their own bodies and links.
Result: 6 passed, 0 failed.
Security and data handling
- Credential values do not enter workflow JSON or parcel content.
- The workflow holds credential bindings only.
- Bucket writes use a short-lived signed URL; n8n has no direct bucket credential.
- External images and videos remain canonical URLs; their binaries are not mirrored.
- Unknown query parameters are removed by default to prevent subscriber or capability tokens from leaking into the corpus.
- Transactional Substack mail is rejected at the mailbox query using publication headers.
- Parcel transformation is deterministic and contains no LLM step.
Explicit limits
Historical mail
The native n8n IMAP Trigger searches only in response to an onMail event. Activation and reconnect do not perform an initial mailbox scan. Consequently, the workflow cannot guarantee recovery of mail that predates activation or arrived while the listener was unavailable.
That work is now owned by ViskaRes in issue #225, using its seat-native Fastmail MCP access. ViskaN8N will not add JMAP polling, MCP polling, or another historical-reader path to this workflow.
Historic link completeness
Parcels emitted before the producer gained first-class links cannot acquire missing non-media links from an L2-only reprojection because the URLs were never stored at L1. ViskaRes owns any source-email re-emission needed during historical reconciliation.
X/Twitter production control
The workflow has a passing regression test proving canonical X/Twitter URL preservation. At the last database census, no production parcel carried a positive X/Twitter instance, so production retrieval for that URL class has not yet received a live positive control.
Ownership after closure
| Surface | Owner |
|---|---|
| New-mail IMAP trigger, transformation, contract gate, signer call, signed PUT | ViskaN8N |
| Signer behavior, bucket contract, L2 projection, agent-role database retrieval | ViskaDB |
| Historical mailbox roster and source-email reconciliation | ViskaRes, issue #225 |
| Credential custody and bindings | ViskaOps |
Final acceptance matrix
| Requirement | Result |
|---|---|
| Process all newly arriving Substack publication messages | Met |
| Exclude transactional Substack mail without sender allowlists | Met |
| Preserve full available Markdown body | Met |
| Preserve substantive links and cleaned media URLs | Met |
| Preserve canonical YouTube and X/Twitter URLs | Met in regression suite; YouTube proven live; X/Twitter awaits a live source instance |
| Deterministic processing without an LLM | Met |
| No direct bucket credential in n8n | Met |
| Idempotent replay behavior | Met |
| L2 links projection and agent retrieval | Met for current parcels carrying links |
| Historical mailbox enumeration/backfill | Transferred to ViskaRes #225; not an n8n workflow responsibility |
Closure statement
The n8n Substack workflow is complete for its production responsibility. It is active, batch-safe, deterministic, fail-closed before storage, idempotent at delivery, and proven to produce retrievable source bodies with links. Historical mailbox reconciliation is a separate research-ingestion operation and has been handed to ViskaRes with its own issue, board item, and acceptance contract.