The AgenticMail Enterprise API runs on Hono, a lightweight web framework that works everywhere (Node, Bun, Deno, Cloudflare Workers). On top of Hono sits a nine layer middleware stack. Every request passes through all nine layers before reaching a route handler. Here’s what each layer does and why it exists.
Layer 1: Request ID
Every request gets a unique identifier. If one already exists in the headers from an upstream load balancer, the system reuses it. Otherwise it generates a UUIDv4. This ID propagates through every log entry, database query, and downstream call.
When something breaks at 2am and you’re digging through logs, request IDs are the thread connecting everything. Without them, correlating a failed response to the query that caused it is a nightmare.
Layer 2: Transport Encryption Verification
This layer verifies the request arrived over TLS. It checks the forwarded protocol header (behind a reverse proxy) or the connection’s native protocol. Plaintext HTTP requests get a hard rejection. No redirects, no gentle suggestions. API traffic carrying auth tokens should never travel unencrypted.
Layer 3: Security Headers
The response gets security headers before anything else touches it. Strict Transport Security, Content Type nosniff, Frame Options deny, XSS Protection, and a restrictive Content Security Policy. Even for a JSON API, these headers prevent misuse if a browser hits the endpoint directly.
Layer 4: CORS
The middleware checks each request’s origin against a per organization allowlist. No wildcard origins. No “allow everything in development.” Every allowed origin is explicitly listed. The admin dashboard’s origin gets added automatically during setup. Everything else requires manual configuration.
Layer 5: Rate Limiting
Requests are limited per API key, per endpoint, per time window. Defaults are 100 per minute for most endpoints, higher for health checks, lower for expensive operations like agent creation. The sliding window algorithm includes response headers showing remaining quota and reset time.
Rate limiting catches both intentional abuse and accidental tight loops. A misconfigured integration hammering the API won’t take down the system for everyone else.
Layer 6: IP Firewall
The firewall checks requesting IPs against allowlists and blocklists, both supporting CIDR notation for subnet ranges. Blocklist wins, then allowlist is checked. If an allowlist is configured, only listed IPs pass. This matters for enterprise deployments where API access should only come from specific corporate networks or VPN exit points.
Layer 7: Audit Logging
Every request past the firewall gets an audit entry: timestamp, request ID, source IP, authenticated identity, method, path, request body hash (not the body itself), response status, and latency. Logs are append only and written to a separate store. They exist for compliance: proving who accessed what, when, from where.
Layer 8: RBAC
After authentication (which sits between firewall and RBAC), this layer checks whether the identity has permission for the requested operation. Four roles: admin, operator, agent, readonly. Each maps to specific allowed methods and endpoint patterns.
An agent can read its own config and post results but can’t touch other agents or the vault. An operator manages agents and reads audit logs but can’t change org settings. Admin gets full access. By this point we know the request is encrypted, from an allowed origin and IP, within rate limits, and authorized.
Layer 9: Org Scoping
The final layer injects the authenticated user’s organization context. Every subsequent database query, vault access, and agent lookup is automatically scoped to that org. Route handlers don’t opt in; the middleware enforces it.
This is the last line of tenant isolation. Even a buggy route handler that forgets to filter by organization can’t leak data across tenants because the database context itself is scoped.
Why Nine Layers
Each layer addresses a specific threat. Request IDs enable debugging. TLS verification prevents eavesdropping. Security headers block browser attacks. CORS restricts cross origin access. Rate limiting prevents abuse. The IP firewall restricts network access. Audit logging enables compliance. RBAC enforces authorization. Org scoping ensures tenant isolation.
The ordering matters. No point rate limiting a request you’ll reject at the firewall. No point auditing a request that fails CORS. Each layer narrows the funnel so subsequent layers process fewer requests.
Nine layers adds about 2 milliseconds of latency. That’s a small price for knowing every request is thoroughly validated before your application code ever sees it.
Source Code
Here is the request logger, one of the nine middleware layers. Each layer follows a similar pattern: wrap the handler, do its work, call next(), then optionally inspect the response.
export function requestLogger(): MiddlewareHandler {
return async (c: Context, next: Next) => {
const start = Date.now();
await next();
const elapsed = Date.now() - start;
const level = c.res.status >= 500 ? 'ERROR' : c.res.status >= 400 ? 'WARN' : 'INFO';
console.log(
`[${new Date().toISOString()}] ${level} ${c.req.method} ${c.req.path} ${c.res.status} ${elapsed}ms`,
);
};
}