{"record":{"id":"06f55722ee824444","repo":"hcengineering/platform","slug":"content-type-header-not-found","errorCode":null,"errorMessage":"Content-Type header not found","messagePattern":"Content-Type header not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/mail/pod-mail-worker/src/utils.ts","lineNumber":33,"sourceCode":"import { randomUUID } from 'crypto'\nimport { readEml, ReadedEmlJson } from 'eml-parse-js'\nimport TurndownService from 'turndown'\nimport sanitizeHtml from 'sanitize-html'\nimport { MeasureContext } from '@hcengineering/core'\nimport { type Attachment } from '@hcengineering/mail-common'\n\nimport { MtaMessage } from './types'\nimport { getDecodedContent } from './decode'\n\nexport async function parseContent (\n  ctx: MeasureContext,\n  mta: MtaMessage\n): Promise<{ content: string, attachments: Attachment[] }> {\n  // TODO: UBERF-11029 - remove this logging after testing\n  ctx.info('Parsing email content', { mta })\n  const contentType = getHeader(mta, 'Content-Type')\n  if (contentType === undefined) {\n    throw new Error('Content-Type header not found')\n  }\n\n  if (contentType.toLowerCase().startsWith('text/plain')) {\n    return { content: getDecodedContent(ctx, mta), attachments: [] }\n  }\n\n  const email = await getEmailContent(ctx, mta)\n\n  let content = email.text ?? ''\n  let isMarkdown = false\n  if (email.html !== undefined) {\n    try {\n      const html = sanitizeHtml(email.html)\n      const tds = new TurndownService()\n      content = tds.turndown(html)\n\n      isMarkdown = true\n    } catch (error) {","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/mail/pod-mail-worker/src/utils.ts#L15-L51","documentation":"parseContent extracts the body and attachments from an incoming MTA email message. It first looks up the 'Content-Type' header via getHeader; RFC-822 messages should always carry one, so if it is absent the parser refuses to guess and throws 'Content-Type header not found'. Without the content type it cannot decide between text/plain handling, multipart parsing, etc.","triggerScenarios":"An email arrives via the MTA whose headers array lacks a 'Content-Type' entry — e.g. a stripped-down raw message forwarded by a relay, a hand-crafted MtaMessage in tests, or a mail pipeline that drops non-standard headers before calling parseContent.","commonSituations":"Upstream MTA or sanitizer rewriting/removing headers; test fixtures building MtaMessage objects with only a few headers; exotic gateways (SMS-to-email, fax-to-email) that emit minimal headers; TODO UBERF-11029 logging shows the mta object missing contentType in its headers array.","solutions":["Ensure the MTA/relay forwards the full header set, especially Content-Type","Fix the message producer/test fixture to include a Content-Type header (e.g. 'text/plain; charset=utf-8')","Wrap parseContent and fall back to treating the body as plain text when the header is missing"],"exampleFix":"// before\nconst contentType = getHeader(mta, 'Content-Type')\nif (contentType === undefined) {\n  throw new Error('Content-Type header not found')\n}\n// after (caller-side fallback)\nconst result = await parseContent(ctx, mta).catch(() => ({ content: getDecodedContent(ctx, mta), attachments: [] }))","handlingStrategy":"type-guard","validationCode":"function hasContentType(mta: MtaMessage): boolean {\n  return mta.message.headers.some(([name]) => name.trim().toLowerCase() === 'content-type')\n}\nif (!hasContentType(mta)) throw new Error('MTA message missing Content-Type header')","typeGuard":"function hasHeader(mta: MtaMessage, name: string): boolean {\n  return getHeader(mta, name) !== undefined\n}","tryCatchPattern":"try {\n  const { content, attachments } = await parseContent(ctx, mta)\n} catch (e) {\n  if (e.message === 'Content-Type header not found') {\n    // treat as plain text fallback\n  } else throw e\n}","preventionTips":["Verify upstream MTA/relay forwards the full header set","Include Content-Type in all test MtaMessage fixtures","Reject malformed messages at ingestion before parsing","Monitor parse failures per source to catch header-stripping gateways"],"tags":["email","parsing","mime","headers"],"backgroundTag":"missing-mime-header","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}