Email Channel Setup — Staging (h852.work) [ARCHIVED 2026-06-05]
ARCHIVED 2026-06-05. This pre-#1722 CF-Worker-as-primary setup is no longer accurate. Specialist email is now Gmail API polling + DWD impersonation (PR #1722, commit
d8333558). CF Worker retained as fallback during 2-week soak (#1441) only. For current architecture seedocs/features/channels.md§System 4. Pointer stub atdocs/ops/EMAIL-CHANNEL-SETUP.md. The content below is preserved verbatim for historical reference — do not follow these steps to provision a new environment.
Status (2026-06-02): This Cloudflare Email Routing path is the legacy / staging-fallback inbound mechanism. The primary production path is per-OSA Google Workspace mailbox impersonation, shipped via ADR-0002 Phase 2 on 2026-05-29. See
docs/features/channels.md§System 3 (Google Workspace) for the live architecture. Keep this doc as the staging fallback reference — Cloudflare-relay still serves@h852.workfor non-impersonated staging tests.
Updated 2026-05-19: Cloudflare email worker is deployed and live for the legacy relay path. Dev (
@dev.h852.work) and staging (@h852.work) specialist emails route through this worker for fallback. Email threading fallback is implemented. See changelog entry for v0.3.5.
Architecture
Client → email → alex@h852.work
→ Cloudflare Email Routing (MX record on h852.work)
→ Cloudflare Email Worker (forwards to HTTP)
→ POST https://api-dev-b36f.up.railway.app/channels/email/inbound
→ Whitelist lookup: FROM email → email_whitelists → orgId
→ Conversation created (orgId set), routed to Expert queue
→ Expert approves reply
→ Resend sends reply from alex@h852.work back to client
Prerequisites
1. Resend Domain Verification
h852.work must be verified in Resend before outbound emails work.
- Login: https://resend.com/domains
- Add domain:
h852.work - Add the DNS records Resend provides (DKIM TXT records, SPF, etc.)
- Status must be "verified" before sending from
@h852.work
Note: The staging Resend key (
re_DgYrhpBH_...) is restricted to sending only (domain list API requires a different key). Verify the domain in the Resend dashboard.
2. Cloudflare Email Routing + Worker
Cloudflare Email Routing does not forward directly to HTTP endpoints. It requires a Cloudflare Worker to relay emails to our webhook.
Steps:
- Login: https://dash.cloudflare.com → Select
h852.work - Email → Email Routing → Enable Email Routing
- Email → Email Workers → Create Worker
- Deploy the minimal worker code below
- Add catch-all rule:
*@h852.work→ Route to Worker
Minimal Cloudflare Worker (deploy in Cloudflare dashboard):
export default {
async email(message, env, ctx) {
// Collect raw email body
const chunks = [];
const reader = message.raw.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const rawBytes = new Uint8Array(chunks.reduce((a, b) => a + b.length, 0));
let offset = 0;
for (const chunk of chunks) { rawBytes.set(chunk, offset); offset += chunk.length; }
const rawText = new TextDecoder().decode(rawBytes);
const body = {
from: message.from,
to: message.to,
subject: message.headers.get('subject') || '',
text: rawText, // raw MIME; controller extracts text field
headers: Array.from(message.headers.entries())
.map(([k, v]) => `${k}: ${v}`)
.join('\r\n'),
message_id: message.headers.get('message-id') || ''
};
await fetch('https://api-dev-b36f.up.railway.app/channels/email/inbound', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
// Optional: forward a copy to an archive address
// await message.forward('archive@h852.work');
}
};
3. Sender Whitelist
For each org, the client's email address (or domain) must be added to the whitelist. Without this, inbound emails are silently dropped (logged as warning).
API:
# Add exact email address
POST /ops/orgs/:orgId/email-whitelist
{ "emailAddress": "client@example.com" }
# Add entire domain
POST /ops/orgs/:orgId/email-whitelist
{ "domain": "example.com" }
# List entries
GET /ops/orgs/:orgId/email-whitelist
# Remove entry
DELETE /ops/orgs/:orgId/email-whitelist/:id
Requires account_manager or superadmin role. Legacy /am/* paths are server-side redirects to /ops/* — do not use them in new tooling.
Railway Environment Variables (dev)
| Variable | Value | Status |
|---|---|---|
RESEND_API_KEY | re_DgYrhpBH_... | ✅ Already set |
HWORK_DOMAIN | h852.work | ✅ Set (replaces SPECIALIST_EMAIL_DOMAIN) |
Note:
HWORK_DOMAINreplaces the oldSPECIALIST_EMAIL_DOMAINvariable. RemoveSPECIALIST_EMAIL_DOMAINfrom any environment where it still appears.The transactional From address is constructed at runtime as
h.work <onboarding@${HWORK_DOMAIN}>inapi/src/email/email.service.tsandchannels/channels.controller.ts— it is not read from an env var. The Specialist reply From is therouting_address(the TO address of the inbound email, e.g.eleanora@h852.work).
Specialist Email Addresses
Each Specialist has an email in the format: {firstname}`@`{HWORK_DOMAIN}
| Address | Org (staging) |
|---|---|
eleanora@h852.work | Acme Financial |
oliver@h852.work | TechCorp Solutions |
adelaide@h852.work | Meridian Logistics |
suki@h852.work | Bloom People Co |
kai@h852.work | Nexus Capital |
alex@h852.work | Test / any specialist |
Routing Logic (Fixed in dev branch)
The routing uses the SENDER (FROM) address to determine the org, not the TO address.