Skip to content

fix: surface email attachments on tickets#376

Open
akash2017sky wants to merge 4 commits into
mainfrom
fix/362-email-attachments
Open

fix: surface email attachments on tickets#376
akash2017sky wants to merge 4 commits into
mainfrom
fix/362-email-attachments

Conversation

@akash2017sky
Copy link
Copy Markdown
Collaborator

Summary

  • Inbound email attachments now land on the resulting Azure DevOps work item across all three Microsoft Graph attachment types (fileAttachment, referenceAttachment, itemAttachment), on both new-ticket and thread-reply paths.
  • cid: references in pasted screenshots are rewritten to point at the uploaded DevOps attachment URL so images render inline in the description / comment instead of showing a broken-image icon.
  • Per-attachment failures log work item id + filename and never block ticket creation.

Fixes

Fixes #362

What this covers

Scenario Before After
Pasted screenshot in body (fileAttachment + isInline: true + cid:) Body fetched as text — <img cid:> reference stripped, image silently disappeared from the description Body fetched as HTML; file is uploaded; cid: ref is rewritten to the DevOps attachment URL — image renders inline
Explicit file attachment (paperclip, not inline) Worked Still works
OneDrive / SharePoint link (referenceAttachment) — Outlook auto-converts files >35 MB or when "Modern Attachments" is enabled Skipped entirely Tries @microsoft.graph.downloadUrl first; if 200 the file is uploaded as a normal attachment. Otherwise the source URL appears in a "Cloud attachments" section in the description
Forwarded .eml (itemAttachment) Skipped entirely Recorded in a "Forwarded messages" section as Forwarded message: <subject> (not extracted) so it doesn't vanish silently
Attachment upload failure (PAT issue, network, DevOps 5xx) Caught but logged with no work item id Caught + logged with work item id + filename; ticket still created
Thread-reply ([ZapDesk #N] in subject) with attachments Attachments dropped — only the body was added as a comment Same upload + cid: rewrite + link flow runs; comment HTML renders inline images, files appear under Attachments

Files changed

  • src/lib/email-poll.ts — switched body Prefer header to HTML; rewrote fetchAttachments to handle all three Graph attachment types; passes bodyType through to ingest.
  • src/lib/email-clean.ts — added sanitizeEmailHtml, stripHtmlSignature, rewriteCidReferences, renderEmailBodyHtml.
  • src/lib/email-ingest.ts — split uploadAttachment into uploadAttachmentBlob + linkAttachment; build cid → url map; rewrite body before storage; thread-reply path resolves project from work item id and runs the same flow.
  • src/types/index.ts — extended EmailWebhookPayload with optional bodyType, contentId, isInline, referenceUrl, itemSubject (backward compatible).

Test plan

Local (manual)

  1. Configure .env: MAIL_POLL_MAILBOX, EMAIL_WEBHOOK_SECRET, AZURE_DEVOPS_PAT, MAIL_* Graph creds.
  2. In a DevOps project, set the description to include Email: <your-sender-domain> (e.g. Email: gmail.com).
  3. bun install && bun run dev — wait for Ready on http://localhost:3102.
  4. Send an email to the configured mailbox from the mapped domain, including:
    • a screenshot pasted directly into the compose window (this is the bug case),
    • a small PDF attached via the paperclip,
    • a OneDrive link attached via "Attach using cloud" (or any file >35 MB),
    • an existing email dragged into the compose window (forwarded .eml).
  5. Trigger the poll:
    $h = @{ "x-webhook-secret" = "<EMAIL_WEBHOOK_SECRET>" }
    Invoke-RestMethod -Method Post -Uri http://localhost:3102/api/email/poll -Headers $h | ConvertTo-Json -Depth 6
  6. Expected response: fetched: 1, ingested: 1, failed: 0 with action: "ticket_created" and a ticketId.
  7. Open https://dev.azure.com/<org>/<project>/_workitems/edit/<ticketId>. Expected:
    • Description shows the email body with the screenshot rendered inline (not a broken-image icon).
    • Attachments tab lists both the screenshot and the PDF.
    • "Cloud attachments" section in the description has a clickable OneDrive link.
    • "Forwarded messages" section shows Forwarded message: <subject> (not extracted).

Thread-reply path

  1. Reply to the confirmation email keeping the [ZapDesk #N] subject tag, with another pasted screenshot + attachment.
  2. Trigger the poll again. Expected: response shows action: "comment_added" for the same ticket id; the new Discussion comment renders the inline screenshot and the file appears under Attachments.

Failure path (graceful degradation)

  1. Temporarily break AZURE_DEVOPS_PAT, send a test email, trigger the poll.
  2. Expected: ticket is still created; dev-server logs include [Ingest] ticket #N attachment failed (filename): .... Restore PAT after.

Verified end-to-end

  • Tested locally against supporttest@knowall.ai polling into the Internal project — produced ticket #5813 with the test email's attachments surfaced as expected.

Automated checks

  • bun run typecheck — passes
  • bun run lint — passes (only pre-existing warnings in unrelated files)
  • prettier --check on the four changed files — passes

Out of scope (per issue)

  • Encrypted / S/MIME attachments — separate decryption story.
  • Per-tenant attachment size limits — DevOps caps at 60 MB per file already.

🤖 Generated with Claude Code

Ayush and others added 3 commits May 9, 2026 15:43
Inbound email attachments now land on the resulting Azure DevOps work
item across all three Microsoft Graph attachment types, on both new
ticket and thread-reply paths.

- Switch poll body fetch from text to HTML so `<img src="cid:...">` refs
  survive for rewriting.
- Handle `referenceAttachment` (OneDrive / SharePoint): try the Graph
  download URL, fall back to a clickable link in the description.
- Handle `itemAttachment` (forwarded .eml): record a tagged
  "Forwarded message: <subject> (not extracted)" note so it doesn't
  vanish silently.
- Split upload + link so attachments are uploaded first; build a
  `cid -> DevOps URL` map and rewrite inline image refs in the body.
- Run the same upload + rewrite + link flow on thread replies, not just
  new tickets.
- Per-attachment failures log the work item id + filename and never
  block ticket creation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inbound email attachments / screenshots not landing on the created ticket

2 participants