hcengineering/platform · critical

err.message

Error message

err.message

What it means

The pod-mail error-handling middleware's final fallback responds HTTP 500 with { message: err.message } for any thrown error that is NOT an ApiError. This is an unexpected/unhandled exception inside an endpoint handler (bug, network failure to SMTP, unhandled null, etc.) surfaced as a 500. The message is the raw error message, which may be opaque or expose internals.

Source

Thrown at services/mail/pod-mail/src/server.ts:57

  endpoints.forEach((endpoint) => {
    if (endpoint.type === 'get') {
      app.get(endpoint.endpoint, catchError(endpoint.handler))
    } else if (endpoint.type === 'post') {
      app.post(endpoint.endpoint, catchError(endpoint.handler))
    }
  })

  app.use((_req, res, _next) => {
    res.status(404).send({ message: 'Not found' })
  })

  app.use((err: any, _req: any, res: any, _next: any) => {
    if (err instanceof ApiError) {
      res.status(400).send({ code: err.code, message: err.message })
      return
    }

    res.status(500).send({ message: err.message })
  })

  return app
}

export function listen (e: Express, port: number, host?: string): Server {
  const cb = (): void => {
    console.log(`Mail service has been started at ${host ?? '*'}:${port}`)
  }

  return host !== undefined ? e.listen(port, host, cb) : e.listen(port, cb)
}

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Check the mail pod's server logs at the time of the 500 — the response only carries err.message, the stack is on the server.
  2. Fix the underlying exception in the handler (validate inputs, null-check, wrap SMTP calls with proper error handling).
  3. Convert expected failure modes into ApiError throws so clients receive 400 with a meaningful code instead of a 500.
  4. Add retry-with-backoff in the client for transient SMTP/network failures before re-raising.

Example fix

// before
async function handler(req, res) {
  const info = await transporter.sendMail(opts) // throws raw SMTP errors
}
// after
async function handler(req, res) {
  try {
    const info = await transporter.sendMail(opts)
  } catch (e) {
    throw new ApiError('MAIL_DELIVERY_FAILED', (e as Error).message)
  }
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const res = await fetch(mailUrl, opts)
  if (res.status === 500) {
    const { message } = await res.json()
    throw new Error(`mail pod crashed: ${message} (check pod logs for stack)`)
  }
} catch (e) { /* alert/retry with backoff for transient SMTP/network failures */ }

Prevention

When it happens

Trigger: A handler throws a non-ApiError (TypeError, nodemailer SMTP failure, JSON parse error, network error to an upstream service) and catchError forwards it to the error middleware, which fails the `err instanceof ApiError` check.

Common situations: SMTP host unreachable or credentials wrong causing nodemailer to throw; null/undefined dereference in handler logic; transient network failures; dependency versions changed so an internal call signature broke.

Related errors


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