{
  "updatedAt": "2026-05-23T16:44:43.718Z",
  "createdAt": "2026-05-23T08:03:31.386Z",
  "id": "WYrrjxvvNjxmj4JF",
  "name": "Mailhog Automation",
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "seconds",
              "secondsInterval": 10
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        0,
        0
      ],
      "id": "79aa5209-2002-49a9-afb1-5af291ab7032",
      "name": "Schedule Trigger"
    },
    {
      "parameters": {
        "url": "http://172.20.0.1:8025/api/v1/messages",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        208,
        0
      ],
      "id": "60ddab96-7894-441a-998a-841c1edfe3ad",
      "name": "Get messages"
    },
    {
      "parameters": {
        "jsCode": "return JSON.parse($input.first().json.data);"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        416,
        0
      ],
      "id": "349c5664-53d0-4ce2-b780-e7cf697861fa",
      "name": "Parse data json string"
    },
    {
      "parameters": {
        "jsCode": "return JSON.parse($input.first().json.data);"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1072,
        128
      ],
      "id": "cd8fab24-bf06-428d-afe4-760d61a56abf",
      "name": "Parse data json string1"
    },
    {
      "parameters": {
        "url": "=http://172.20.0.1:8025/api/v1/messages/{{ $json.ID }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        624,
        0
      ],
      "id": "ff0287c0-983e-430e-9924-90edf4933e5d",
      "name": "Get message details"
    },
    {
      "parameters": {
        "jsCode": "// Compose message \u2014 Code node (Run Once for All Items)\n// Input:  MailHog message detail (parsed JSON)\n// Output: json { subject, from, toRecipients, bodyHtml, bodyText, attachments[], attachmentCount }\n//         attachments[] entries: { fileName, mimeType, data (base64 string), contentId, isInline }\n\nfunction decodeQuotedPrintable(s) {\n  return s\n    .replace(/=\\r?\\n/g, '')\n    .replace(/=([0-9A-Fa-f]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));\n}\n\nfunction header(headers, name) {\n  if (!headers) return '';\n  const k = Object.keys(headers).find(x => x.toLowerCase() === name.toLowerCase());\n  return k ? (headers[k][0] || '') : '';\n}\n\nfunction getCharset(ct) {\n  const m = /charset=([^;\\s]+)/i.exec(ct || '');\n  return m ? m[1].replace(/^\"|\"$/g, '').toLowerCase() : 'utf-8';\n}\n\nfunction decodeText(part) {\n  const enc = header(part.Headers, 'Content-Transfer-Encoding').toLowerCase();\n  const charset = getCharset(header(part.Headers, 'Content-Type'));\n  if (enc === 'quoted-printable') {\n    return Buffer.from(decodeQuotedPrintable(part.Body || ''), 'binary').toString(charset);\n  }\n  if (enc === 'base64') {\n    return Buffer.from((part.Body || '').replace(/\\s+/g, ''), 'base64').toString(charset);\n  }\n  return part.Body || '';\n}\n\nfunction flattenParts(mime) {\n  if (!mime || !Array.isArray(mime.Parts)) return [];\n  const out = [];\n  for (const p of mime.Parts) {\n    if (p.MIME && Array.isArray(p.MIME.Parts) && p.MIME.Parts.length > 0) {\n      out.push(...flattenParts(p.MIME));\n    } else {\n      out.push(p);\n    }\n  }\n  return out;\n}\n\nconst MIME_EXT = {\n  'image/jpeg': 'jpg',\n  'image/jpg': 'jpg',\n  'image/png': 'png',\n  'image/gif': 'gif',\n  'image/webp': 'webp',\n  'image/svg+xml': 'svg',\n  'image/bmp': 'bmp',\n  'image/tiff': 'tiff',\n  'image/x-icon': 'ico',\n  'application/pdf': 'pdf',\n  'application/zip': 'zip',\n  'application/json': 'json',\n  'application/xml': 'xml',\n  'application/msword': 'doc',\n  'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',\n  'application/vnd.ms-excel': 'xls',\n  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',\n  'application/vnd.ms-powerpoint': 'ppt',\n  'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',\n  'text/plain': 'txt',\n  'text/html': 'html',\n  'text/csv': 'csv',\n  'text/calendar': 'ics',\n  'application/octet-stream': 'bin',\n};\n\nfunction extFromMime(mimeType) {\n  if (!mimeType) return 'bin';\n  if (MIME_EXT[mimeType]) return MIME_EXT[mimeType];\n  const sub = (mimeType.split('/')[1] || 'bin').replace(/\\+.*$/, '');\n  return sub.replace(/[^a-z0-9]/gi, '').slice(0, 8) || 'bin';\n}\n\nfunction ensureExt(name, ext) {\n  return /\\.[a-z0-9]{1,8}$/i.test(name) ? name : `${name}.${ext}`;\n}\n\nfunction pickFilename(part, mimeType, fallback) {\n  const ext = extFromMime(mimeType);\n  const cd = header(part.Headers, 'Content-Disposition');\n  let m = /filename\\*?=(?:\"([^\"]+)\"|([^;\\s]+))/i.exec(cd);\n  if (m) return ensureExt(m[1] || m[2], ext);\n  const ct = header(part.Headers, 'Content-Type');\n  m = /name=(?:\"([^\"]+)\"|([^;\\s]+))/i.exec(ct);\n  if (m) return ensureExt(m[1] || m[2], ext);\n  const cid = header(part.Headers, 'Content-ID').replace(/^<|>$/g, '').trim();\n  if (cid) return ensureExt(cid, ext);\n  return `${fallback}.${ext}`;\n}\n\nfunction addr(box) {\n  return box && box.Mailbox && box.Domain ? `${box.Mailbox}@${box.Domain}` : '';\n}\n\nconst SKIP_TYPES = new Set([\n  'application/pkcs7-signature',\n  'application/x-pkcs7-signature',\n  'application/pgp-signature',\n  'application/pgp-encrypted',\n  'application/ms-tnef',\n  'application/vnd.ms-tnef',\n  'application/applefile',\n  'message/delivery-status',\n  'message/disposition-notification',\n  'message/feedback-report',\n  'text/rfc822-headers',\n]);\n\nfunction hasNamingHint(part) {\n  const cd = header(part.Headers, 'Content-Disposition');\n  if (/filename\\*?=/i.test(cd)) return true;\n  const ct = header(part.Headers, 'Content-Type');\n  if (/name=/i.test(ct)) return true;\n  if (header(part.Headers, 'Content-ID').trim()) return true;\n  return false;\n}\n\nconst results = [];\nconst items = $input.all();\n\nfor (let i = 0; i < items.length; i++) {\n  const m = items[i].json || {};\n  const hdrs = m.Content?.Headers || {};\n  const subject = hdrs.Subject?.[0] || '(no subject)';\n  const from = addr(m.From);\n  const toRecipients = (m.To || []).map(addr).filter(Boolean).join(',');\n  const parts = flattenParts(m.MIME);\n\n  let bodyHtml = '';\n  let bodyText = '';\n  const attachments = [];\n\n  for (const part of parts) {\n    const ct = header(part.Headers, 'Content-Type').toLowerCase();\n    const enc = header(part.Headers, 'Content-Transfer-Encoding').toLowerCase();\n    const cd = header(part.Headers, 'Content-Disposition').toLowerCase();\n    const mimeType = ct.split(';')[0].trim() || 'application/octet-stream';\n    const isAttachmentDisposition = cd.startsWith('attachment') || cd.startsWith('inline');\n\n    if (ct.startsWith('text/html') && !bodyHtml && !isAttachmentDisposition) {\n      bodyHtml = decodeText(part);\n      continue;\n    }\n    if (ct.startsWith('text/plain') && !bodyText && !isAttachmentDisposition) {\n      bodyText = decodeText(part);\n      continue;\n    }\n    // Drop extra text/* alternatives that aren't real attachments.\n    if ((ct.startsWith('text/plain') || ct.startsWith('text/html')) && !isAttachmentDisposition) {\n      continue;\n    }\n    if (SKIP_TYPES.has(mimeType)) continue;\n    if (mimeType.startsWith('multipart/') || mimeType.startsWith('message/')) continue;\n    if (!part.Body || !part.Body.trim()) continue;\n    // Final guard: nameless + type-less blobs are almost always junk MIME scaffolding.\n    if (!hasNamingHint(part) && (mimeType === 'application/octet-stream' || mimeType === '')) continue;\n\n    let buf;\n    if (enc === 'base64') {\n      buf = Buffer.from((part.Body || '').replace(/\\s+/g, ''), 'base64');\n    } else if (enc === 'quoted-printable') {\n      buf = Buffer.from(decodeQuotedPrintable(part.Body || ''), 'binary');\n    } else {\n      buf = Buffer.from(part.Body || '', 'binary');\n    }\n    if (!buf.length) continue;\n\n    const fileName = pickFilename(part, mimeType, `attachment-${attachments.length}`);\n    const contentId = header(part.Headers, 'Content-ID').replace(/^<|>$/g, '').trim();\n    const isInline = cd.startsWith('inline') || !!contentId;\n    attachments.push({\n      fileName,\n      mimeType,\n      data: buf.toString('base64'),\n      contentId: contentId || null,\n      isInline,\n    });\n  }\n\n  results.push({\n    json: {\n      subject,\n      from,\n      toRecipients,\n      bodyHtml,\n      bodyText,\n      attachments,\n      attachmentCount: attachments.length,\n    },\n    pairedItem: { item: i },\n  });\n}\n\nreturn results;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1280,
        128
      ],
      "id": "db257c62-8d60-41ab-a196-4748a38f98a1",
      "name": "Compose message"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        848,
        0
      ],
      "id": "93821958-7257-48be-9833-1f0bdd1f97c7",
      "name": "Loop Over Items"
    },
    {
      "parameters": {
        "resource": "draft",
        "subject": "={{ $json.subject }}",
        "bodyContent": "={{ $json.bodyHtml }}",
        "additionalFields": {
          "from": "={{ $json.from }}",
          "bodyContentType": "html",
          "toRecipients": "={{ $json.toRecipients }}"
        }
      },
      "type": "n8n-nodes-base.microsoftOutlook",
      "typeVersion": 2,
      "position": [
        1488,
        128
      ],
      "id": "406306fc-1d59-4df2-9b4d-302049091a09",
      "name": "Create a draft",
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Split out attachments \u2014 Code node (Run Once for All Items)\n// Reads attachments[] from Compose message json + draft id from Create a draft.\n// Emits one item per attachment, with the FULL Graph request body in json.\n// No binary \u2014 downstream HTTP Request node POSTs json directly to Graph.\n\nconst composeItem = $('Compose message').item;\nconst draftItem = $('Create a draft').item;\n\nconst messageId = draftItem.json.id;\nconst attachments = composeItem.json.attachments || [];\nconst outputItems = [];\n\nfor (const att of attachments) {\n  const body = {\n    '@odata.type': '#microsoft.graph.fileAttachment',\n    name: att.fileName,\n    contentType: att.mimeType,\n    contentBytes: att.data,\n    isInline: !!att.isInline,\n  };\n  if (att.contentId) body.contentId = att.contentId;\n\n  outputItems.push({\n    json: {\n      messageId,\n      url: `https://graph.microsoft.com/v1.0/me/messages/${messageId}/attachments`,\n      body,\n    },\n  });\n}\n\nreturn outputItems;\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        128
      ],
      "id": "e7f504aa-61cc-4b18-9b9f-a4122910bcbd",
      "name": "Split out attachments"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.url }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOutlookOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.body }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1888,
        128
      ],
      "id": "bd50f60c-c7f3-4ed5-b072-b64387c4736e",
      "name": "Add attachment",
      "retryOnFail": true,
      "maxTries": 5,
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "resource": "draft",
        "operation": "send",
        "draftId": {
          "__rl": true,
          "value": "={{ $('Create a draft').item.json.id }}",
          "mode": "id"
        }
      },
      "type": "n8n-nodes-base.microsoftOutlook",
      "typeVersion": 2,
      "position": [
        2288,
        128
      ],
      "id": "28b36940-c2a0-4845-a22b-66b413fd1f43",
      "name": "Send a draft",
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "DELETE",
        "url": "=http://172.20.0.1:8025/api/v1/messages/{{ $('Parse data json string1').item.json.ID }}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        2480,
        128
      ],
      "id": "f4eb3856-6a5e-4a0f-8399-7e8beddcac22",
      "name": "Delete message in Mailhog"
    },
    {
      "parameters": {
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {}
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        2096,
        128
      ],
      "id": "42becb63-8ff6-46c2-95c1-642c0fafdf52",
      "name": "Aggregate",
      "alwaysOutputData": true
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get messages": {
      "main": [
        [
          {
            "node": "Parse data json string",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse data json string": {
      "main": [
        [
          {
            "node": "Get message details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get message details": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse data json string1": {
      "main": [
        [
          {
            "node": "Compose message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compose message": {
      "main": [
        [
          {
            "node": "Create a draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Parse data json string1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create a draft": {
      "main": [
        [
          {
            "node": "Split out attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split out attachments": {
      "main": [
        [
          {
            "node": "Add attachment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add attachment": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send a draft": {
      "main": [
        [
          {
            "node": "Delete message in Mailhog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete message in Mailhog": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "Send a draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "staticData": {
    "node:Schedule Trigger": {
      "recurrenceRules": []
    }
  },
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "versionId": "4eaf38d1-9503-4cbc-a8f9-9c6c33243ecd",
  "activeVersionId": "4eaf38d1-9503-4cbc-a8f9-9c6c33243ecd",
  "triggerCount": 1,
  "shared": [
    {
      "updatedAt": "2026-05-23T08:03:31.386Z",
      "createdAt": "2026-05-23T08:03:31.386Z",
      "role": "workflow:owner",
      "workflowId": "WYrrjxvvNjxmj4JF",
      "projectId": "awdxw69L9IK2yvbS"
    }
  ],
  "activeVersion": {
    "updatedAt": "2026-05-23T16:44:45.680Z",
    "createdAt": "2026-05-23T16:44:43.719Z",
    "versionId": "4eaf38d1-9503-4cbc-a8f9-9c6c33243ecd",
    "workflowId": "WYrrjxvvNjxmj4JF",
    "nodes": [
      {
        "parameters": {
          "rule": {
            "interval": [
              {
                "field": "seconds",
                "secondsInterval": 10
              }
            ]
          }
        },
        "type": "n8n-nodes-base.scheduleTrigger",
        "typeVersion": 1.3,
        "position": [
          0,
          0
        ],
        "id": "79aa5209-2002-49a9-afb1-5af291ab7032",
        "name": "Schedule Trigger"
      },
      {
        "parameters": {
          "url": "http://172.20.0.1:8025/api/v1/messages",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.4,
        "position": [
          208,
          0
        ],
        "id": "60ddab96-7894-441a-998a-841c1edfe3ad",
        "name": "Get messages"
      },
      {
        "parameters": {
          "jsCode": "return JSON.parse($input.first().json.data);"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          416,
          0
        ],
        "id": "349c5664-53d0-4ce2-b780-e7cf697861fa",
        "name": "Parse data json string"
      },
      {
        "parameters": {
          "jsCode": "return JSON.parse($input.first().json.data);"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1072,
          128
        ],
        "id": "cd8fab24-bf06-428d-afe4-760d61a56abf",
        "name": "Parse data json string1"
      },
      {
        "parameters": {
          "url": "=http://172.20.0.1:8025/api/v1/messages/{{ $json.ID }}",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.4,
        "position": [
          624,
          0
        ],
        "id": "ff0287c0-983e-430e-9924-90edf4933e5d",
        "name": "Get message details"
      },
      {
        "parameters": {
          "jsCode": "// Compose message \u2014 Code node (Run Once for All Items)\n// Input:  MailHog message detail (parsed JSON)\n// Output: json { subject, from, toRecipients, bodyHtml, bodyText, attachments[], attachmentCount }\n//         attachments[] entries: { fileName, mimeType, data (base64 string), contentId, isInline }\n\nfunction decodeQuotedPrintable(s) {\n  return s\n    .replace(/=\\r?\\n/g, '')\n    .replace(/=([0-9A-Fa-f]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));\n}\n\nfunction header(headers, name) {\n  if (!headers) return '';\n  const k = Object.keys(headers).find(x => x.toLowerCase() === name.toLowerCase());\n  return k ? (headers[k][0] || '') : '';\n}\n\nfunction getCharset(ct) {\n  const m = /charset=([^;\\s]+)/i.exec(ct || '');\n  return m ? m[1].replace(/^\"|\"$/g, '').toLowerCase() : 'utf-8';\n}\n\nfunction decodeText(part) {\n  const enc = header(part.Headers, 'Content-Transfer-Encoding').toLowerCase();\n  const charset = getCharset(header(part.Headers, 'Content-Type'));\n  if (enc === 'quoted-printable') {\n    return Buffer.from(decodeQuotedPrintable(part.Body || ''), 'binary').toString(charset);\n  }\n  if (enc === 'base64') {\n    return Buffer.from((part.Body || '').replace(/\\s+/g, ''), 'base64').toString(charset);\n  }\n  return part.Body || '';\n}\n\nfunction flattenParts(mime) {\n  if (!mime || !Array.isArray(mime.Parts)) return [];\n  const out = [];\n  for (const p of mime.Parts) {\n    if (p.MIME && Array.isArray(p.MIME.Parts) && p.MIME.Parts.length > 0) {\n      out.push(...flattenParts(p.MIME));\n    } else {\n      out.push(p);\n    }\n  }\n  return out;\n}\n\nconst MIME_EXT = {\n  'image/jpeg': 'jpg',\n  'image/jpg': 'jpg',\n  'image/png': 'png',\n  'image/gif': 'gif',\n  'image/webp': 'webp',\n  'image/svg+xml': 'svg',\n  'image/bmp': 'bmp',\n  'image/tiff': 'tiff',\n  'image/x-icon': 'ico',\n  'application/pdf': 'pdf',\n  'application/zip': 'zip',\n  'application/json': 'json',\n  'application/xml': 'xml',\n  'application/msword': 'doc',\n  'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',\n  'application/vnd.ms-excel': 'xls',\n  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',\n  'application/vnd.ms-powerpoint': 'ppt',\n  'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',\n  'text/plain': 'txt',\n  'text/html': 'html',\n  'text/csv': 'csv',\n  'text/calendar': 'ics',\n  'application/octet-stream': 'bin',\n};\n\nfunction extFromMime(mimeType) {\n  if (!mimeType) return 'bin';\n  if (MIME_EXT[mimeType]) return MIME_EXT[mimeType];\n  const sub = (mimeType.split('/')[1] || 'bin').replace(/\\+.*$/, '');\n  return sub.replace(/[^a-z0-9]/gi, '').slice(0, 8) || 'bin';\n}\n\nfunction ensureExt(name, ext) {\n  return /\\.[a-z0-9]{1,8}$/i.test(name) ? name : `${name}.${ext}`;\n}\n\nfunction pickFilename(part, mimeType, fallback) {\n  const ext = extFromMime(mimeType);\n  const cd = header(part.Headers, 'Content-Disposition');\n  let m = /filename\\*?=(?:\"([^\"]+)\"|([^;\\s]+))/i.exec(cd);\n  if (m) return ensureExt(m[1] || m[2], ext);\n  const ct = header(part.Headers, 'Content-Type');\n  m = /name=(?:\"([^\"]+)\"|([^;\\s]+))/i.exec(ct);\n  if (m) return ensureExt(m[1] || m[2], ext);\n  const cid = header(part.Headers, 'Content-ID').replace(/^<|>$/g, '').trim();\n  if (cid) return ensureExt(cid, ext);\n  return `${fallback}.${ext}`;\n}\n\nfunction addr(box) {\n  return box && box.Mailbox && box.Domain ? `${box.Mailbox}@${box.Domain}` : '';\n}\n\nconst SKIP_TYPES = new Set([\n  'application/pkcs7-signature',\n  'application/x-pkcs7-signature',\n  'application/pgp-signature',\n  'application/pgp-encrypted',\n  'application/ms-tnef',\n  'application/vnd.ms-tnef',\n  'application/applefile',\n  'message/delivery-status',\n  'message/disposition-notification',\n  'message/feedback-report',\n  'text/rfc822-headers',\n]);\n\nfunction hasNamingHint(part) {\n  const cd = header(part.Headers, 'Content-Disposition');\n  if (/filename\\*?=/i.test(cd)) return true;\n  const ct = header(part.Headers, 'Content-Type');\n  if (/name=/i.test(ct)) return true;\n  if (header(part.Headers, 'Content-ID').trim()) return true;\n  return false;\n}\n\nconst results = [];\nconst items = $input.all();\n\nfor (let i = 0; i < items.length; i++) {\n  const m = items[i].json || {};\n  const hdrs = m.Content?.Headers || {};\n  const subject = hdrs.Subject?.[0] || '(no subject)';\n  const from = addr(m.From);\n  const toRecipients = (m.To || []).map(addr).filter(Boolean).join(',');\n  const parts = flattenParts(m.MIME);\n\n  let bodyHtml = '';\n  let bodyText = '';\n  const attachments = [];\n\n  for (const part of parts) {\n    const ct = header(part.Headers, 'Content-Type').toLowerCase();\n    const enc = header(part.Headers, 'Content-Transfer-Encoding').toLowerCase();\n    const cd = header(part.Headers, 'Content-Disposition').toLowerCase();\n    const mimeType = ct.split(';')[0].trim() || 'application/octet-stream';\n    const isAttachmentDisposition = cd.startsWith('attachment') || cd.startsWith('inline');\n\n    if (ct.startsWith('text/html') && !bodyHtml && !isAttachmentDisposition) {\n      bodyHtml = decodeText(part);\n      continue;\n    }\n    if (ct.startsWith('text/plain') && !bodyText && !isAttachmentDisposition) {\n      bodyText = decodeText(part);\n      continue;\n    }\n    // Drop extra text/* alternatives that aren't real attachments.\n    if ((ct.startsWith('text/plain') || ct.startsWith('text/html')) && !isAttachmentDisposition) {\n      continue;\n    }\n    if (SKIP_TYPES.has(mimeType)) continue;\n    if (mimeType.startsWith('multipart/') || mimeType.startsWith('message/')) continue;\n    if (!part.Body || !part.Body.trim()) continue;\n    // Final guard: nameless + type-less blobs are almost always junk MIME scaffolding.\n    if (!hasNamingHint(part) && (mimeType === 'application/octet-stream' || mimeType === '')) continue;\n\n    let buf;\n    if (enc === 'base64') {\n      buf = Buffer.from((part.Body || '').replace(/\\s+/g, ''), 'base64');\n    } else if (enc === 'quoted-printable') {\n      buf = Buffer.from(decodeQuotedPrintable(part.Body || ''), 'binary');\n    } else {\n      buf = Buffer.from(part.Body || '', 'binary');\n    }\n    if (!buf.length) continue;\n\n    const fileName = pickFilename(part, mimeType, `attachment-${attachments.length}`);\n    const contentId = header(part.Headers, 'Content-ID').replace(/^<|>$/g, '').trim();\n    const isInline = cd.startsWith('inline') || !!contentId;\n    attachments.push({\n      fileName,\n      mimeType,\n      data: buf.toString('base64'),\n      contentId: contentId || null,\n      isInline,\n    });\n  }\n\n  results.push({\n    json: {\n      subject,\n      from,\n      toRecipients,\n      bodyHtml,\n      bodyText,\n      attachments,\n      attachmentCount: attachments.length,\n    },\n    pairedItem: { item: i },\n  });\n}\n\nreturn results;\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1280,
          128
        ],
        "id": "db257c62-8d60-41ab-a196-4748a38f98a1",
        "name": "Compose message"
      },
      {
        "parameters": {
          "options": {}
        },
        "type": "n8n-nodes-base.splitInBatches",
        "typeVersion": 3,
        "position": [
          848,
          0
        ],
        "id": "93821958-7257-48be-9833-1f0bdd1f97c7",
        "name": "Loop Over Items"
      },
      {
        "parameters": {
          "resource": "draft",
          "subject": "={{ $json.subject }}",
          "bodyContent": "={{ $json.bodyHtml }}",
          "additionalFields": {
            "from": "={{ $json.from }}",
            "bodyContentType": "html",
            "toRecipients": "={{ $json.toRecipients }}"
          }
        },
        "type": "n8n-nodes-base.microsoftOutlook",
        "typeVersion": 2,
        "position": [
          1488,
          128
        ],
        "id": "406306fc-1d59-4df2-9b4d-302049091a09",
        "name": "Create a draft",
        "webhookId": "2ba93f50-73ab-4664-ab31-c27fede2db45",
        "credentials": {
          "microsoftOutlookOAuth2Api": {
            "id": "a7S1I18rJkgZ0wqY",
            "name": "Microsoft Outlook account"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "// Split out attachments \u2014 Code node (Run Once for All Items)\n// Reads attachments[] from Compose message json + draft id from Create a draft.\n// Emits one item per attachment, with the FULL Graph request body in json.\n// No binary \u2014 downstream HTTP Request node POSTs json directly to Graph.\n\nconst composeItem = $('Compose message').item;\nconst draftItem = $('Create a draft').item;\n\nconst messageId = draftItem.json.id;\nconst attachments = composeItem.json.attachments || [];\nconst outputItems = [];\n\nfor (const att of attachments) {\n  const body = {\n    '@odata.type': '#microsoft.graph.fileAttachment',\n    name: att.fileName,\n    contentType: att.mimeType,\n    contentBytes: att.data,\n    isInline: !!att.isInline,\n  };\n  if (att.contentId) body.contentId = att.contentId;\n\n  outputItems.push({\n    json: {\n      messageId,\n      url: `https://graph.microsoft.com/v1.0/me/messages/${messageId}/attachments`,\n      body,\n    },\n  });\n}\n\nreturn outputItems;\n"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1680,
          128
        ],
        "id": "e7f504aa-61cc-4b18-9b9f-a4122910bcbd",
        "name": "Split out attachments"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "={{ $json.url }}",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "microsoftOutlookOAuth2Api",
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ $json.body }}",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.4,
        "position": [
          1888,
          128
        ],
        "id": "bd50f60c-c7f3-4ed5-b072-b64387c4736e",
        "name": "Add attachment",
        "retryOnFail": true,
        "maxTries": 5,
        "credentials": {
          "microsoftOutlookOAuth2Api": {
            "id": "a7S1I18rJkgZ0wqY",
            "name": "Microsoft Outlook account"
          }
        }
      },
      {
        "parameters": {
          "resource": "draft",
          "operation": "send",
          "draftId": {
            "__rl": true,
            "value": "={{ $('Create a draft').item.json.id }}",
            "mode": "id"
          }
        },
        "type": "n8n-nodes-base.microsoftOutlook",
        "typeVersion": 2,
        "position": [
          2288,
          128
        ],
        "id": "28b36940-c2a0-4845-a22b-66b413fd1f43",
        "name": "Send a draft",
        "webhookId": "d4625674-3cce-4c25-a4f2-8f0e4b60205b",
        "credentials": {
          "microsoftOutlookOAuth2Api": {
            "id": "a7S1I18rJkgZ0wqY",
            "name": "Microsoft Outlook account"
          }
        }
      },
      {
        "parameters": {
          "method": "DELETE",
          "url": "=http://172.20.0.1:8025/api/v1/messages/{{ $('Parse data json string1').item.json.ID }}",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.4,
        "position": [
          2480,
          128
        ],
        "id": "f4eb3856-6a5e-4a0f-8399-7e8beddcac22",
        "name": "Delete message in Mailhog"
      },
      {
        "parameters": {
          "fieldsToAggregate": {
            "fieldToAggregate": [
              {}
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.aggregate",
        "typeVersion": 1,
        "position": [
          2096,
          128
        ],
        "id": "42becb63-8ff6-46c2-95c1-642c0fafdf52",
        "name": "Aggregate",
        "alwaysOutputData": true
      }
    ],
    "connections": {
      "Schedule Trigger": {
        "main": [
          [
            {
              "node": "Get messages",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Get messages": {
        "main": [
          [
            {
              "node": "Parse data json string",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Parse data json string": {
        "main": [
          [
            {
              "node": "Get message details",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Get message details": {
        "main": [
          [
            {
              "node": "Loop Over Items",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Parse data json string1": {
        "main": [
          [
            {
              "node": "Compose message",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Compose message": {
        "main": [
          [
            {
              "node": "Create a draft",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Loop Over Items": {
        "main": [
          [],
          [
            {
              "node": "Parse data json string1",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Create a draft": {
        "main": [
          [
            {
              "node": "Split out attachments",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Split out attachments": {
        "main": [
          [
            {
              "node": "Add attachment",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Add attachment": {
        "main": [
          [
            {
              "node": "Aggregate",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Send a draft": {
        "main": [
          [
            {
              "node": "Delete message in Mailhog",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Delete message in Mailhog": {
        "main": [
          [
            {
              "node": "Loop Over Items",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Aggregate": {
        "main": [
          [
            {
              "node": "Send a draft",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "authors": "Shepherd Zhu",
    "name": "Version 4eaf38d1",
    "description": "",
    "autosaved": true
  },
  "tags": []
}