{"record":{"id":"6fb3b05110c676a2","repo":"hcengineering/platform","slug":"text-and-html-are-missing","errorCode":null,"errorMessage":"'text' and 'html' are missing","messagePattern":"'text' and 'html' are missing","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"services/mail/pod-mail/src/main.ts","lineNumber":97,"sourceCode":"export async function handleSendMail (\n  client: MailClient,\n  req: Request,\n  res: Response,\n  ctx: MeasureContext\n): Promise<void> {\n  const { from, to, subject, text, html, attachments, headers, apiKey, password } = req.body\n  if (process.env.API_KEY !== undefined && process.env.API_KEY !== apiKey) {\n    ctx.warn('Unauthorized access attempt to send email', {\n      from,\n      to\n    })\n    res.status(401).send({ err: 'Unauthorized' })\n    return\n  }\n  const fromAddress = from ?? config.source\n  if (text === undefined && html === undefined) {\n    ctx.warn('Text and html are missing in email request', { from, to })\n    res.status(400).send({ err: \"'text' and 'html' are missing\" })\n    return\n  }\n  if (subject === undefined) {\n    ctx.warn('Subject is missing in email request', { from, to })\n    res.status(400).send({ err: \"'subject' is missing\" })\n    return\n  }\n  if (to === undefined) {\n    ctx.warn('To address is missing in email request', { from })\n    res.status(400).send({ err: \"'to' is missing\" })\n    return\n  }\n  if (fromAddress === undefined) {\n    ctx.warn('From address is missing in email request', { to })\n    res.status(400).send({ err: \"'from' is missing\" })\n    return\n  }\n  const message: SendMailOptions = {","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/mail/pod-mail/src/main.ts#L79-L115","documentation":"handleSendMail returns HTTP 400 { err: \"'text' and 'html' are missing\" } when neither text nor html is present in the request body. The mail service requires at least one body representation for the email and refuses the request before contacting the mail backend.","triggerScenarios":"POST to the send-mail endpoint with a body lacking both text and html fields — e.g. { to, subject } only, or fields misspelled (body/message instead of text), or an empty JSON body.","commonSituations":"Building the payload dynamically and skipping body fields when content is empty; sending a template id instead of rendered content; an API client wrapper that drops undefined fields, leaving neither text nor html.","solutions":["Include at least one of text or html in the request body.","Check for typos/field renaming in the client payload (text, html must be top-level body fields).","Default to a non-empty fallback body when the generated content is empty."],"exampleFix":"// before\nconst body = { to, subject } // 400: text and html missing\n// after\nconst body = { to, subject, text: text ?? '', html }\nif (body.text === '' && body.html === undefined) throw new Error('email body required')","handlingStrategy":"validation","validationCode":"function assertMailBody(m: { text?: string; html?: string }): void {\n  if (m.text === undefined && m.html === undefined) {\n    throw new Error(\"email requires at least one of 'text' or 'html'\")\n  }\n}","typeGuard":"function hasEmailBody(m: { text?: unknown; html?: unknown }): boolean {\n  return m.text !== undefined || m.html !== undefined\n}","tryCatchPattern":"assertMailBody(mail)\nconst res = await fetch(mailUrl, { method: 'POST', body: JSON.stringify(mail) })\nif (res.status === 400) {\n  const { err } = await res.json()\n  throw new Error(`mail rejected: ${err}`)\n}","preventionTips":["Always include text or html when constructing mail payloads.","Beware serializers that strip undefined fields, silently removing the body.","Fall back to an empty-string text body when generated content is blank.","Unit-test payload builders to assert required fields are present."],"tags":["http-400","request-validation","mail"],"backgroundTag":"missing-request-parameter","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}