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
- Set body.apiKey in the request to exactly the value of the pod-mail API_KEY env var.
- Verify no whitespace/quote differences between the configured env value and the sent key.
- If the deployment should be open, remove the API_KEY env var from the pod-mail service.
- 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
- Keep client MAIL_API_KEY and server API_KEY in sync; update both during rotation.
- Send apiKey as a top-level body field, not a header.
- Trim env values to avoid whitespace mismatches.
- If no auth is wanted, unset API_KEY on the pod-mail service instead of sending an empty key.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/7e780be47ac858d0.
Report an issue: GitHub.