WFDASH-WEBAPP/docs/quotes-outbound-attachments-deploy.md
Dave cd359b75a9 feat(quotes): split attachments into internal vs outbound with operator upload UI
Splits the quote attachment model along the seam that actually matters:
'operator-only references' (drawings the operator consulted, supplier
emails they filed away) vs 'customer-facing files' (supplier quotes,
drawings, NDAs that need to ride along on the email).

Schema (PB): new `internal_attachments` + `outbound_attachments` fields
on the `quotes` collection, maxSelect 10 each. Existing `attachments`
field kept for back-compat but is no longer the source of truth for
outbound emails.

API:
- GET/POST/DELETE /api/v1/quotes/[id]/internal-attachments[/{filename}]
- GET/POST/DELETE /api/v1/quotes/[id]/outbound-attachments[/{filename}]
- POST /api/v1/quotes/[id]/attachments/upload kept for legacy `attachments`

MCP: `quotes.send_email` description now correctly states
`selectedAttachmentIds` are PB file tokens from `outbound_attachments`
(supplier quotes, drawings, NDAs), not IDs of the legacy `attachments`
collection.

Frontend: `ProcessOutboundAttachmentsCard` component lets the operator
upload/remove customer-facing files during the process page; approval +
process pages now read the new fields.

Outbound Send workflow (deployed as `PIEAkPYYYZLGl6Pj`):
- Old workflow (id `WF_Email_Send_SMTP`) and stale repo file
  (`WFDASH — Quote Outbound Send.json`) replaced.
- New revision intersects `quotes.outbound_attachments` with
  `email_outbound_attachment_ids`, downloads each from
  `/api/files/quotes/<id>/<token>`, and emits
  `options.attachments: [{name, content, type}]` consumed by
  `Send Email (SMTP)` (PO Send pattern).
- Failure path flipped from `send_failed` to `mail_failed` so
  `retryQuoteEmail` can find failed sends (it only accepts the
  latter). Previously failed sends were stranded.

Email Monitor (v5) workflow fixes:
- Reply-branch auth: any authenticated request now passes through;
  pre-fix bug rejected branches that the IMAP trigger hadn't seen yet.
- Reply detection: bulletproof subject regex
  (/Q-?\d{4}-?\d{2}-?\d{3}/) accepts both legacy `Q-2026-09-246`
  and current `Q202609246` formats; canonicalised digits-only for
  PB `~` substring match.

Delivery notes: new pricing endpoint at
/api/v1/delivery-notes/items/[id]/pricing + service-layer methods.

Snapshots kept for forensics: PRE-FIX-REPLY-{BRANCH-AUTH,DETECTION}.json
and push-payload.baseline.json are the previously-deployed workflow
shapes, retained so we can diff/rollback if regression hits.

Verified:
- PUT workflow PIEAkPYYYZLGl6Pj -> 200, active, 25 nodes
- quotes-service.test.ts: 117 pass (4 pre-existing failures unchanged)
- mcp/route.test.ts: 118 pass (2 pre-existing failures unchanged)
- node --check on new Code node JS passes

Docs: docs/quotes-outbound-attachments-deploy.md covers the deploy
procedure; docs/api-mcp-layer.md description sync.
2026-09-09 13:43:56 +02:00

4.5 KiB

Quote Outbound Send — operator-uploaded attachments fix

Deployed 2026-09-09 alongside the quotes.outbound_attachments / quotes.internal_attachments storage split (the migration that moved operator-uploaded files off the legacy quote_attachments collection's direction enum).

What the deployed workflow does now

The active n8n workflow is WFDASH — Quote Outbound Send (push-payload + outbound-attachments) (id PIEAkPYYYZLGl6Pj, previously named …(push-payload)).

The chain still ends in Send Email (SMTP) with the auto-generated PDF attached — same as before — plus a new Build attachments list Code node that runs between HTTP Request (PDF download) and the SMTP node. It:

  1. Reads quotes.outbound_attachments (the PB file field on the parent quote row) and email_outbound_attachment_ids (the JSON array of operator-selected tokens persisted by sendQuoteEmail).
  2. Intersects the two so only files the operator explicitly marked for the customer get attached. Defends against drift between the modal snapshot and the send.
  3. Downloads each token from https://<PB>/api/files/quotes/<quoteId>/<token> via this.helpers.httpRequest.
  4. Returns a single item with attachments: [{name, content (base64), type}, …] on the JSON — same shape the deployed WFDASH — Purchase Order Send workflow consumes.
  5. Per-download failures are skipped (not raised) so a single bad token doesn't abort the whole send. Operator sees the final count via attachmentCount.

The SMTP node now consumes the array via options.attachments={{ $json.attachments && $json.attachments.length ? $json.attachments : [] }} — matching the PO Send pattern. The previous options.fileAttachments=data (PDF only) is removed.

Bonus fix: failure status send_failed → mail_failed

The deployed Update quote - Send Failed node previously flipped a failed send to status send_failed. The dashboard's retryQuoteEmail only accepts mail_failed, so failed sends were stranded — operator couldn't retry from the UI. The failure path now flips to mail_failed. Stale send_failed rows (if any pre-existed) can be re-categorised with a one-shot PB update if the operator wants the tab badge to count them.

Why the repo file was deleted

n8n_workfows/WFDASH — Quote Outbound Send.json (the pre-existing repo file) was stale — it referenced a non-existent MCP tool (quotes.get_send_payload), the old row-ID model (selected_attachment_ids + pdf_attachment_id), $env.PB_ADMIN_TOKEN (which the docker-compose blocks via N8N_BLOCK_ENV_ACCESS_IN_NODE=true), and field names that had been renamed (email_message_id, email_delivered, …). It did not match the deployed workflow and would have misled anyone debugging the system. Deleted.

A clean snapshot of the previously-deployed workflow is preserved at n8n_workfows/WFDASH — Quote Outbound Send (push-payload).baseline.json for diff/rollback reference. The post-fix deployed workflow is in n8n_workfows/WFDASH — Quote Outbound Send (with-outbound-attachments).json.

MCP quotes.send_email description

Updated to spell out the new field semantics: "selectedAttachmentIds is a list of PB file tokens from quotes.outbound_attachments (operator-uploaded customer-facing files — supplier quotes, drawings, NDAs). The PDF is always attached separately from quote_attachments kind="pdf". Also added a per-property description on the input schema and updated the docs/api-mcp-layer.md` table to match.

Verification

Layer Result
Deployed workflow via n8n REST PUT returned HTTP 200, active=true, node count 24 → 25, new connections verified
quotes-service.test.ts 117 pass, 4 pre-existing failures (stale-PDF auto-regen — unrelated, baseline fails too)
mcp/route.test.ts 118 pass, 2 pre-existing failures (unrelated, baseline fails too)
Code node JS syntax node --check passes

Files touched

  • Deleted: n8n_workfows/WFDASH — Quote Outbound Send.json
  • Added:
    • n8n_workfows/WFDASH — Quote Outbound Send (push-payload).baseline.json
    • n8n_workfows/WFDASH — Quote Outbound Send (with-outbound-attachments).json
    • docs/quotes-outbound-attachments-deploy.md
  • Modified:
    • src/app/api/mcp/route.ts (tool description + selectedAttachmentIds schema property)
    • src/app/api/v1/quotes/[id]/send-email/route.ts (docstring only)
    • docs/api-mcp-layer.md (table row)
  • Deployed: workflow PIEAkPYYYZLGl6Pj in the live n8n instance