hcengineering/platform · error · ApiError

err.code

err.code

Error message

err.message

What it means

The pod-mail error-handling middleware catches an ApiError thrown (or forwarded via catchError) by an endpoint handler and responds HTTP 400 with { code: err.code, message: err.message }. The message text is whatever the ApiError carried — often a field-validation message like "'from' is missing". It signals a client-side bad request rather than a server fault.

Source

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

  app.use(cors())
  app.use(express.json())

  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. Read the 'code' and 'message' fields in the 400 response body — they identify exactly which validation failed; fix the request payload accordingly.
  2. In client code, parse the response body and branch on code instead of treating every non-2xx as a generic failure.
  3. If the ApiError originates from your own handler code, correct the thrown condition or throw a more specific ApiError code.
  4. Ensure catchError wraps handlers so ApiErrors surface as 400 rather than becoming 500s.

Example fix

// before
const res = await fetch(url, opts)
const data = await res.json()
use(data)
// after
if (!res.ok) {
  const err = await res.json()
  if (err.code) throw new Error(`ApiError ${err.code}: ${err.message}`)
}
const data = await res.json()
use(data)
Defensive patterns

Strategy: try-catch

Validate before calling

function isApiErrorBody(b: unknown): b is { code: string; message: string } {
  return typeof (b as any)?.code === 'string' && typeof (b as any)?.message === 'string'
}

Type guard

function isApiErrorBody(v: unknown): v is { code: string; message: string } {
  return v !== null && typeof v === 'object' && 'code' in v && 'message' in v
}

Try / catch

const res = await fetch(url, opts)
if (!res.ok) {
  const body = await res.json()
  if (isApiErrorBody(body)) throw new Error(`ApiError ${body.code}: ${body.message}`)
  throw new Error(`HTTP ${res.status}`)
}

Prevention

When it happens

Trigger: Any endpoint handler throws ApiError (directly or via the catchError wrapper) with a 4xx-class code — e.g. malformed request parameters, invalid token format, or payload that fails domain validation inside pod-mail.

Common situations: Client sending payloads that pass the top-level undefined checks but fail deeper validation inside the mail handler; code reusing ApiError from the shared server library for business-rule violations; SDK wrappers surfacing the raw { code, message } body to application code.

Related errors


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