---
id: 90121d61-86ed-48ff-8c57-b04fb8a3ce5d
title: 'Cloudflare: implement Pages Functions + R2 inbox (current deploy has no inbox)'
state: closed
labels:
- area:inbox
assignees: []
milestone: null
created: 2026-10-27T17:24:52.330249308Z
updated: 2026-10-27T17:45:41.613480095Z
---
## What

The Cloudflare target is still incomplete: the repo has no `functions/`, no R2 code and no Pages deploy path. `dist/_headers` is emitted, but on Pages the forms POST to `/inbox/issue` and `/inbox/pr-prompt` with no Function behind them, so the inbox is dead. Docker/`hu13 serve` is the only complete target today.

## Required parity

Implement Pages Functions for both endpoints with the existing inbox contract:

- urlencoded only (`application/x-www-form-urlencoded`; reject multipart/JSON), 32 KiB whole-request cap, strict UTF-8, NUL → 400
- strip controls; title ≤300, body/prompt ≤20000; license required; honeypot fake-200 without storing
- same-origin Origin gate, ~500 ms fixed delay, no echo, `X-Content-Type-Options: nosniff` + `Cache-Control: no-store`
- ack uuid exactly once
- R2 object per entry with the schema: `type/title/body/handle/contact/license_version/received_at/salted_ip_hash`
- per-IP rate limit and global quota (or document a verified Cloudflare-native equivalent, e.g. WAF/rate rules + R2 quota check) with 429/503

## Deliverables

- `functions/inbox/issue.js` + `functions/inbox/pr-prompt.js` (or one shared handler)
- R2 binding docs (`INBOX`, `INBOX_SALT` secret) and `wrangler`/Pages deploy recipe; `_routes.json` limited to `/inbox/*`
- Node tests for validation/ack/R2 writes using a fake bucket (no network)
- update `.forge/wiki/deploy` to describe the Pages path honestly (what works, what needs dashboard config)
- clone URL: document `--public-origin` so the user-facing `git clone <origin>/repo.git` is real on Pages

Acceptance: local Node tests pass; the function code is deployable with a documented binding; docs no longer imply the static-only deploy has a working inbox.
