hcengineering/platform · error

Content-Type header not found

Error message

Content-Type header not found

What it means

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.

Source

Thrown at services/mail/pod-mail-worker/src/utils.ts:33

import { randomUUID } from 'crypto'
import { readEml, ReadedEmlJson } from 'eml-parse-js'
import TurndownService from 'turndown'
import sanitizeHtml from 'sanitize-html'
import { MeasureContext } from '@hcengineering/core'
import { type Attachment } from '@hcengineering/mail-common'

import { MtaMessage } from './types'
import { getDecodedContent } from './decode'

export async function parseContent (
  ctx: MeasureContext,
  mta: MtaMessage
): Promise<{ content: string, attachments: Attachment[] }> {
  // TODO: UBERF-11029 - remove this logging after testing
  ctx.info('Parsing email content', { mta })
  const contentType = getHeader(mta, 'Content-Type')
  if (contentType === undefined) {
    throw new Error('Content-Type header not found')
  }

  if (contentType.toLowerCase().startsWith('text/plain')) {
    return { content: getDecodedContent(ctx, mta), attachments: [] }
  }

  const email = await getEmailContent(ctx, mta)

  let content = email.text ?? ''
  let isMarkdown = false
  if (email.html !== undefined) {
    try {
      const html = sanitizeHtml(email.html)
      const tds = new TurndownService()
      content = tds.turndown(html)

      isMarkdown = true
    } catch (error) {

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Ensure the MTA/relay forwards the full header set, especially Content-Type
  2. Fix the message producer/test fixture to include a Content-Type header (e.g. 'text/plain; charset=utf-8')
  3. Wrap parseContent and fall back to treating the body as plain text when the header is missing

Example fix

// before
const contentType = getHeader(mta, 'Content-Type')
if (contentType === undefined) {
  throw new Error('Content-Type header not found')
}
// after (caller-side fallback)
const result = await parseContent(ctx, mta).catch(() => ({ content: getDecodedContent(ctx, mta), attachments: [] }))
Defensive patterns

Strategy: type-guard

Validate before calling

function hasContentType(mta: MtaMessage): boolean {
  return mta.message.headers.some(([name]) => name.trim().toLowerCase() === 'content-type')
}
if (!hasContentType(mta)) throw new Error('MTA message missing Content-Type header')

Type guard

function hasHeader(mta: MtaMessage, name: string): boolean {
  return getHeader(mta, name) !== undefined
}

Try / catch

try {
  const { content, attachments } = await parseContent(ctx, mta)
} catch (e) {
  if (e.message === 'Content-Type header not found') {
    // treat as plain text fallback
  } else throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/06f55722ee824444. Report an issue: GitHub.