{"record":{"id":"d93ccb6489048bef","repo":"hcengineering/platform","slug":"to-is-missing","errorCode":null,"errorMessage":"'to' is missing","messagePattern":"'to' is missing","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"services/mail/pod-mail/src/main.ts","lineNumber":107,"sourceCode":"      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 = {\n    from: fromAddress,\n    to,\n    subject,\n    text\n  }\n  // When sending system message, ensure we enable replying to a different domain as needed\n  if (config.replyTo !== undefined && fromAddress === config.source) {\n    message.replyTo = config.replyTo\n  }\n  if (html !== undefined) {","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/mail/pod-mail/src/main.ts#L89-L125","documentation":"handleSendMail returns HTTP 400 { err: \"'to' is missing\" } when the to field is absent from the request body. The recipient is mandatory; unlike from (which can fall back to config.source), to has no default, so the request is rejected.","triggerScenarios":"POST to the send-mail endpoint with from/subject/body present but to undefined — e.g. missing recipients array in the payload, a client wrapper dropping the field, or sending recipients in a different field name like recipients or email.","commonSituations":"Bulk-mail code that forgot to populate the recipient list; renaming to -> recipients in a shared client; serialization stripping undefined fields; sending single-recipient mail with the address in the wrong property.","solutions":["Include the to field (recipient address or array) in the request body.","Check for field-name mismatches (recipients/email) between client and pod-mail's expected to.","Validate the payload has to before calling the endpoint (see validationCode in defense section)."],"exampleFix":"// before\nconst body = { from, subject, text } // 400: to missing\n// after\nconst body = { from, to: 'user@example.com', subject, text }","handlingStrategy":"validation","validationCode":"function assertRecipient(m: { to?: string | string[] }): void {\n  const to = m.to\n  const ok = typeof to === 'string' ? to.length > 0 : Array.isArray(to) && to.length > 0\n  if (!ok) throw new Error(\"email requires a 'to' recipient\")\n}","typeGuard":"function hasRecipient(m: { to?: unknown }): m is { to: string | string[] } {\n  return typeof m.to === 'string' || Array.isArray(m.to)\n}","tryCatchPattern":"assertRecipient(mail)\nconst res = await fetch(mailUrl, { method: 'POST', body: JSON.stringify(mail) })\nif (res.status === 400) throw new Error(`mail rejected: ${(await res.json()).err}`)","preventionTips":["Unlike from, 'to' has no server-side default — always supply it.","Check for field-name drift (recipients/email) in client wrappers.","Validate recipients exist before calling bulk-mail routines.","Add schema validation (e.g. zod) on outbound mail payloads."],"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"}