hcengineering/platform · error

Unauthorized

Error message

Unauthorized

What it means

handleSendMail in pod-mail rejects with HTTP 401 { err: 'Unauthorized' } when the deployment defines the API_KEY environment variable and the request's apiKey field does not match it. The service is protected by a shared API key; sending without (or with a wrong) key is refused before any mail processing.

Source

Thrown at services/mail/pod-mail/src/main.ts:91

  })
  process.on('unhandledRejection', (e: any) => {
    measureCtx.error(e.message)
  })
}

export async function handleSendMail (
  client: MailClient,
  req: Request,
  res: Response,
  ctx: MeasureContext
): Promise<void> {
  const { from, to, subject, text, html, attachments, headers, apiKey, password } = req.body
  if (process.env.API_KEY !== undefined && process.env.API_KEY !== apiKey) {
    ctx.warn('Unauthorized access attempt to send email', {
      from,
      to
    })
    res.status(401).send({ err: 'Unauthorized' })
    return
  }
  const fromAddress = from ?? config.source
  if (text === undefined && html === undefined) {
    ctx.warn('Text and html are missing in email request', { from, to })
    res.status(400).send({ err: "'text' and 'html' are missing" })
    return
  }
  if (subject === undefined) {
    ctx.warn('Subject is missing in email request', { from, to })
    res.status(400).send({ err: "'subject' is missing" })
    return
  }
  if (to === undefined) {
    ctx.warn('To address is missing in email request', { from })
    res.status(400).send({ err: "'to' is missing" })
    return
  }

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Set body.apiKey in the request to exactly the value of the pod-mail API_KEY env var.
  2. Verify no whitespace/quote differences between the configured env value and the sent key.
  3. If the deployment should be open, remove the API_KEY env var from the pod-mail service.
  4. If the key was rotated, redeploy/reconfigure clients with the new key.

Example fix

// before
await fetch(mailUrl, { method: 'POST', body: JSON.stringify({ to, subject, text }) }) // 401
// after
await fetch(mailUrl, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ to, subject, text, apiKey: process.env.MAIL_API_KEY })
})
Defensive patterns

Strategy: validation

Validate before calling

const apiKey = process.env.MAIL_API_KEY
if (apiKey == null || apiKey === '') throw new Error('MAIL_API_KEY not configured on client; server has API_KEY set and will 401')

Type guard

function hasApiKey(b: unknown): b is { apiKey: string } {
  return typeof (b as any)?.apiKey === 'string' && (b as any).apiKey.length > 0
}

Try / catch

const res = await fetch(mailUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...mail, apiKey }) })
if (res.status === 401) throw new Error('mail API key rejected: check API_KEY on server vs apiKey in body')

Prevention

When it happens

Trigger: POST to the mail endpoint while process.env.API_KEY is set on the pod-mail service and req.body.apiKey is undefined or different — e.g. client never configured the key, key rotated on the server, or the key is sent in a header instead of the body.

Common situations: New deployment set API_KEY env var but clients were not updated; key rotation propagated to server before clients; trailing whitespace/newline in the env value; client sends apiKey in an Authorization header but the service only reads it from the JSON body.

Understand the failure class

Related errors


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