{"locator":"npm:@northbound-run/channel-cloudflare-email","path":"@northbound-run/channel-cloudflare-email","npm":"@northbound-run/channel-cloudflare-email","github":"Northbound-Run/open-mercato-cloudflare-email","repoPath":null,"kind":"module","type":"3rd-party","enterprise":false,"title":"Cloudflare Email Channel","summary":"Cloudflare Email Service channel: inbound mail through Email Routing and a Worker, outbound through the REST API.","whenToUse":"Use when your domain mail runs on Cloudflare and threads should land in the app's communication channels with real delivery status.","whenNotToUse":"There is no history import: the channel sees mail only from the moment its routing rule goes live.","category":"communication","tags":["email","cloudflare"],"providers":[{"name":"cloudflare-email","label":"Cloudflare Email Service"}],"moduleIds":["channel_cloudflare_email"],"requires":[{"raw":"communication_channels","resolvesTo":"core:communication_channels"},{"raw":"integrations","resolvesTo":"core:integrations"}],"replaces":[],"slotConflicts":[],"renamedFrom":[],"supersededBy":null,"alternatives":[],"status":"active","listed":true,"featured":false,"license":{"spdx":"MIT","kind":"osi","mismatch":false},"version":{"latest":"0.2.0","preview":null,"publishedAt":"2026-08-07","integrity":"sha512-wu2QIhNqx+t/mXY0ZX+pmt6ImqzUvt57udRkGivM+UU2jHCntm+BRM8YzmYiN6ovSnScLmq3FN3s1ZYTa9zZlQ==","installableFrom":"2026-08-12","gitRef":"ceb01b278cef873a58e00d97c781d82ead5ac8ef","recommended":{"0.9":"0.2.0","0.8":null,"next":null}},"identity":{"builtFrom":"verified","claimed":false,"source":"npm"},"compat":{"builtFor":{"range":">=0.6.6 <0.7.0","kind":"bounded","lines":[]},"lines":{"0.9":{"status":"verified","step":null,"omVersion":"0.9.0","moduleVersion":"0.2.0","at":"2026-10-29T08:29:32Z","receipt":"r_20261029_a17f99586f63","since":"2026-10-29","precheck":null,"patches":[]}},"verifiedOn":["0.9"]},"anatomy":{"version":"0.2.0","coverage":"full","counts":{"entities":0,"tables":0,"migrations":0,"routes":1,"publicRoutes":1,"aclFeatures":2,"eventsEmitted":0,"eventsConsumed":0,"backendPages":0,"frontendPages":0,"widgets":1,"workers":0,"cli":0,"aiTools":0,"aiAgents":0,"extends":2},"lists":{"aclFeatures":["channel_cloudflare_email.view","channel_cloudflare_email.configure"],"eventsEmitted":[],"eventsConsumed":[],"env":[],"hosts":["api.cloudflare.com"],"tables":[],"publicRoutes":["POST /api/channel_cloudflare_email/delivery-events"],"credentials":[{"key":"accountId","type":"text","required":true},{"key":"apiToken","type":"secret","required":true},{"key":"sendingDomain","type":"text","required":true},{"key":"fromAddress","type":"text","required":true},{"key":"fromName","type":"text","required":false},{"key":"inboundAddress","type":"text","required":true},{"key":"ingestSecret","type":"secret","required":true},{"key":"r2Bucket","type":"text","required":true},{"key":"r2AccessKeyId","type":"text","required":true},{"key":"r2SecretAccessKey","type":"secret","required":true}]},"callout":"ADDS 1 ROUTE (1 PUBLIC) · 2 PERMISSIONS","diff":{"from":"0.1.2","added":{"tables":0,"routes":0,"publicRoutes":0,"aclFeatures":0,"env":[],"hosts":[]},"removed":{"tables":0,"routes":0,"aclFeatures":0}}},"signals":{"receipt":{"0.9":"verified","0.8":"untested","next":"untested"},"fit":{"0.9":"ok","0.8":"drift","next":"drift"},"anatomy":"full","supplyChain":{"status":"ok","reasons":[]},"hygiene":{"status":"ok","failing":[]},"maintenance":{"status":"current","lastRelease":"2026-08-07","catchUpDays":null},"declaredVsObserved":{"status":"mismatch","notes":["Built for no tested line; verified on 0.9."]}},"tier":"verified","quality":null,"agentPolicy":{"0.9":{"decision":"ask","reasons":[{"code":"THIRD_PARTY","text":"Third-party code runs with access to all tenants' data."}],"consentText":"Install @northbound-run/channel-cloudflare-email@0.2.0"},"0.8":{"decision":"ask","reasons":[{"code":"THIRD_PARTY","text":"Third-party code runs with access to all tenants' data."},{"code":"NOT_VERIFIED","text":"Not yet tested in a new app on 0.8."}],"consentText":"Install @northbound-run/channel-cloudflare-email@0.2.0"},"next":{"decision":"ask","reasons":[{"code":"THIRD_PARTY","text":"Third-party code runs with access to all tenants' data."},{"code":"NOT_VERIFIED","text":"Not yet tested in a new app on next."}],"consentText":"Install @northbound-run/channel-cloudflare-email@0.2.0"}},"install":{"cli":"yarn mercato module add @northbound-run/channel-cloudflare-email@0.2.0 --allow-third-party","cmm":"npx create-mercato-module install @northbound-run/channel-cloudflare-email@0.2.0","needsAllowThirdParty":true,"moduleFlag":null},"stats":{"downloads30d":37,"stars":0},"sigil":{"glyph":"0110002","phase":352,"publicRoutes":true},"links":{"page":"https://modules.openmercato.com/m/@northbound-run/channel-cloudflare-email","md":"https://modules.openmercato.com/m/@northbound-run/channel-cloudflare-email.md","json":"https://modules.openmercato.com/m/@northbound-run/channel-cloudflare-email.json","facts":"https://modules.openmercato.com/m/@northbound-run/channel-cloudflare-email/v/0.2.0/facts.json","npm":"https://www.npmjs.com/package/@northbound-run/channel-cloudflare-email","repo":"https://github.com/Northbound-Run/open-mercato-cloudflare-email","docs":null},"addedAt":"2026-10-23","updatedAt":"2026-10-29T09:22:28Z","_meta":{"com.openmercato.modules/installable":{"0.9":"0.2.0","0.8":"0.2.0","next":"0.2.0"}},"facts":{"factsVersion":1,"extractor":{"engine":"@open-mercato/cli@0.9.0 extractModuleFacts","factsContractVersion":2,"catalog":"om-modules rentgen 1","durationMs":134},"package":{"locator":"npm:@northbound-run/channel-cloudflare-email","name":"@northbound-run/channel-cloudflare-email","version":"0.2.0","integrity":"sha512-wu2QIhNqx+t/mXY0ZX+pmt6ImqzUvt57udRkGivM+UU2jHCntm+BRM8YzmYiN6ovSnScLmq3FN3s1ZYTa9zZlQ==","source":"npm-tarball","gitRef":"ceb01b278cef873a58e00d97c781d82ead5ac8ef"},"gate":{"installable":true,"needsModuleFlag":false,"reason":null,"modules":[{"id":"channel_cloudflare_email","src":true,"dist":true}]},"kind":"module","license":{"value":"MIT","kind":"osi","mismatch":false,"sources":{"packageJson":"MIT","licenseFile":"MIT","moduleInfo":"MIT","integrationTs":"MIT"}},"omPeers":{"@open-mercato/core":">=0.6.6 <0.7.0","@open-mercato/shared":">=0.6.0 <0.7.0","@open-mercato/ui":">=0.6.0 <0.7.0"},"dependencies":{"awilix":"^12.1.1"},"agentsMd":false,"readmeExcerpt":"# @northbound-run/channel-cloudflare-email\n\nCloudflare Email Service channel for [Open Mercato](https://docs.openmercato.com) — inbound mail via Email Routing and a thin Email Worker, outbound via the Email Sending REST API, with real delivery status from Queues event subscriptions.\n\nA peer of `@open-mercato/channel-imap` and `@open-mercato/channel-gmail`, implementing the same `ChannelAdapter` contract against `communication_channels`.\n\n---\n\n## Read this first\n\nThree properties of this channel differ from the IMAP and Gmail adapters, and all three are easier to learn here than in production.\n\n### There is no backfill\n\nThe channel sees mail from the moment its Email Routing rule goes live, **forward**. There is no history import, and `fetchHistory` / `importHistory` are deliberately not implemented — Cloudflare delivers on receipt and there is no mailbox to page backwards through. `POST /channels/{id}/import-history` returns a clean \"feature not supported\".\n\n**Consequence:** if you intend to accumulate a corpus, create the routing rule as early as possible. Every week of delay is a week of mail that cannot be recovered later.\n\n### Inbound is 25 MiB; outbound is 5 MiB\n\nThese are different limits and conflating them is easy. Cloudflare rejects **inbound** messages over 25 MiB at the edge, before the Worker runs — unrecoverable, no retry. **Outbound** total message size (body plus encoded attachments) is 5 MiB; the 25 MiB figure applies to outbound only when sending to verified destination addresses.\n\nThe adapter enforces the outbound ceiling before calling the API, so an oversize send fails with a message naming the file rather than a bare 400.\n\n### It is the first email channel here with working outbound attachments\n\n`channel_imap` returns a failed result when handed attachments; `channel_gmail` does not stitch attachment URLs into its MIME body. This adapter fetches, validates and base64-encodes them, so `capabilities.fileSharing` is `true`.\n\nThat flag is a **persisted snapshot** — it is written into the channel row at connect time — so it ships `true` from 0.1.0 rather than being flipped later, which would strand already-connected channels with a stale value.\n\n---\n\n## Architecture\n\n```\nsender ─→ your-address@your-domain.com\n            │\n       Cloudflare Email Routing\n            ↓\n      Email Worker  (worker/)\n        ├─ buffers message.raw ONCE, parses with postal-mime\n        ├─ raw MIME + attachment blobs ──→ R2\n        └─ HMAC-signed envelope ──→ POST\n                 ↓\n   /api/communication_channels/webhook/cloudflare_email     (Open Mercato core)\n                 ↓\n        adapter.verifyWebhook()   ← HMAC, fail-closed\n        adapter.normalizeInbound()\n                 ↓\n        core: dedup → thread match → contact resolution → Message\n        MessageChannelLink.channelPayload = the full envelope\n                 ↓\n        event: communication_channels.message.received\n```\n\nOutbound is the mirror: `convertOutbound` fetches and encodes attachments, `sendMessage` posts to the Email Sending REST API, and a Queues event subscription feeds delivery status back through the Worker to this module's own endpoint.\n\n---\n\n## Installation\n\n```bash\nyarn mercato module add @northbound-run/channel-cloudflare-email --allow-third-party\nyarn generate\n```\n\n`--allow-third-party` is required because the package is outside the `@open-mercato/*` scope. The module ships **no entities and no migrations**, so there is nothing to migrate.\n\nRequires `communication_channels` and `integrations` to be enabled — both are declared in `requires`, so `yarn generate` fails with a clear message naming the missing module rather than dying at runtime.\n\nTo take local ownership of the source:\n\n```bash\nyarn mercato module add @northbound-run/channel-cloudflare-email --allow-third-party --eject\n```\n\n---\n\n## Cloudflare setup\n\nDeploy the Worker before connecting the channel — the connect form asks for values the Worker deployment produces.\n\n```bash\n# 1. Scaffold ","modules":[{"id":"channel_cloudflare_email","title":"Cloudflare Email Channel","description":"Inbound email via Cloudflare Email Routing and a thin Email Worker; outbound via the Email Sending REST API, with real delivery status from Queues event subscriptions.","requires":[{"raw":"communication_channels","resolvesTo":"core:communication_channels"},{"raw":"integrations","resolvesTo":"core:integrations"}],"ejectable":true,"data":{"entities":[],"migrations":{"count":0,"first":null,"last":null},"customEntities":0},"http":{"routes":[{"method":"POST","path":"/api/channel_cloudflare_email/delivery-events","requireAuth":false,"requireFeatures":[],"hasOpenApi":true,"src":{"file":"src/modules/channel_cloudflare_email/api/post/delivery-events/route.ts","line":46}}],"legacyRouteFiles":1,"authResolved":1},"security":{"aclFeatures":["channel_cloudflare_email.view","channel_cloudflare_email.configure"],"setupDefaultRoleFeatures":true},"events":{"emits":[],"subscribes":[]},"ui":{"backendPages":[],"frontendPages":[],"widgets":1,"extensionHosts":0},"background":{"workers":[],"cli":[]},"ai":{"tools":[],"agents":[]},"config":{"credentials":[{"key":"accountId","type":"text","required":true},{"key":"apiToken","type":"secret","required":true},{"key":"sendingDomain","type":"text","required":true},{"key":"fromAddress","type":"text","required":true},{"key":"fromName","type":"text","required":false},{"key":"inboundAddress","type":"text","required":true},{"key":"ingestSecret","type":"secret","required":true},{"key":"r2Bucket","type":"text","required":true},{"key":"r2AccessKeyId","type":"text","required":true},{"key":"r2SecretAccessKey","type":"secret","required":true}],"env":[]},"network":{"hosts":[{"host":"api.cloudflare.com","src":{"file":"src/modules/channel_cloudflare_email/lib/validate-credentials.ts","line":8},"confidence":"high"}],"undeclared":[]},"extends":[{"kind":"specialized-registry","target":"integration:channel_cloudflare_email"},{"kind":"widget","target":"profile:communication-channels:connect"}],"omImports":{"@open-mercato/core":{"modules/attachments/data/entities":["Attachment","AttachmentPartition"],"modules/attachments/lib/access":["assertAttachmentScopeInvariant"],"modules/attachments/lib/drivers":["StorageDriver","StorageDriverFactory"],"modules/attachments/lib/imageUrls":["buildAttachmentFileUrl"],"modules/attachments/lib/ocrService":["OcrService","shouldUseLlmOcr"],"modules/attachments/lib/partitions":["ensureDefaultPartitions","resolveDefaultPartitionCode"],"modules/attachments/lib/security":["sanitizeUploadedFileName"],"modules/attachments/lib/textExtraction":["extractAttachmentContent"],"modules/auth/data/entities":["User"],"modules/communication_channels/data/entities":["CommunicationChannel","MessageChannelLink"],"modules/communication_channels/lib/adapter":["ChannelAdapter","ChannelCapabilities","ChannelNativeContent","ContactHint","ConvertOutboundInput","GetMessageStatusInput","InboundMessage","MessageStatus","NormalizedAttachment","NormalizedInboundMessage","ResolveContactInput","SendMessageInput","SendMessageResult","ValidateCredentialsInput","ValidateCredentialsResult","VerifyWebhookInput"],"modules/communication_channels/lib/adapter-registry-singleton":["hasChannelAdapter","registerChannelAdapter"],"modules/communication_channels/lib/email-capabilities":["baseEmailCapabilities"],"modules/communication_channels/lib/email-contact":["emailResolveContact"],"modules/communication_channels/lib/pg-errors":["isUniqueViolation"],"modules/inbox_ops/data/entities":["InboxEmail"],"modules/inbox_ops/events":["emitInboxOpsEvent"],"modules/inbox_ops/lib/htmlToPlainText":["htmlToPlainText"],"modules/integrations/lib/credentials-service":["CredentialsService"]},"@open-mercato/shared":{"lib/di/container":["AppContainer","createRequestContainer"],"lib/encryption/find":["findWithDecryption"],"lib/i18n/context":["useT"],"lib/logger":["createLogger"],"modules/integrations/types":["IntegrationBundle","IntegrationDefinition","IntegrationScope","buildIntegrationDetailWidgetSpotId"],"modules/registry":["ModuleInfo"],"modules/setup":["ModuleSetupConfig"],"modules/widgets/injection":["InjectionWidgetComponentProps","InjectionWidgetModule","ModuleInjectionTable"]},"@open-mercato/ui":{"backend/FlashMessages":["flash"],"backend/injection/useGuardedMutation":["useGuardedMutation"],"backend/utils/apiCall":["apiCall"],"primitives/button":["Button"],"primitives/dialog":["Dialog","DialogContent","DialogFooter","DialogHeader","DialogTitle"],"primitives/input":["Input"],"primitives/label":["Label"],"primitives/password-input":["PasswordInput"]}},"collisionKeys":{"moduleId":"channel_cloudflare_email","tables":[],"aclFeatures":["channel_cloudflare_email.view","channel_cloudflare_email.configure"],"eventIds":[],"backendRoutes":[]},"publicRoutes":1,"glyph":"0110002","lint":[{"code":"no-agents-md","severity":"info","message":"No AGENTS.md in the package.","anchor":"g3 §3","src":null},{"code":"peer-range-behind","severity":"info","message":"Built for >=0.6.6 <0.7.0 does not include Open Mercato 0.9.0.","anchor":"w4 §4.2","src":null},{"code":"public-routes","severity":"info","message":"1 of 1 routes do not require authentication.","anchor":null,"src":{"file":"src/modules/channel_cloudflare_email/api/post/delivery-events/route.ts","line":46}},{"code":"ships-tests","severity":"info","message":"The tarball ships test files.","anchor":null,"src":null},{"code":"version-drift","severity":"info","message":"integration.ts says 0.1.0, package.json says 0.2.0.","anchor":null,"src":{"file":"src/modules/channel_cloudflare_email/integration.ts","line":25}}]}],"coverage":{"routesAuthResolved":1,"integrationTs":"expanded"},"diagnostics":[]},"receipts":[{"schema":1,"id":"r_20261029_a17f99586f63","cellKey":"a17f99586f6389b2b2216f264c74337cf482fe3b72366675dfe57a6364beec15","module":{"locator":"npm:@northbound-run/channel-cloudflare-email","version":"0.2.0","integrity":"sha512-wu2QIhNqx+t/mXY0ZX+pmt6ImqzUvt57udRkGivM+UU2jHCntm+BRM8YzmYiN6ovSnScLmq3FN3s1ZYTa9zZlQ=="},"om":{"line":"0.9","version":"0.9.0","createApp":"create-mercato-app@0.9.0","preset":"classic"},"harness":{"version":"c349007c2b5b","node":"24.21.0","yarn":"4.17.1","runner":"ubuntu-latest","mode":"realistic","patches":[]},"baseline":{"status":"verified","receipt":"r_20261029_e5f612bd10e0"},"requiresInstalled":[],"steps":[{"name":"scaffold","status":"pass","sec":2,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"install","status":"pass","sec":57,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"add","status":"pass","sec":27,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"generate","status":"pass","sec":16,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"typecheck","status":"pass","sec":78,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"initialize","status":"pass","sec":30,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"build","status":"pass","sec":150,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"start","status":"pass","sec":4,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"smoke","status":"pass","sec":2,"signature":null,"excerpt":null,"file":null,"line":null}],"result":{"status":"verified","failedStep":null,"alsoFailed":[],"classification":"pass","quarantineUntil":null},"transition":{"from":null,"to":"verified","confirmed":true},"run":{"id":40842502780,"attempt":1,"url":"https://github.com/open-mercato/modules/actions/runs/40842502780/job/123342268849","at":"2026-10-29T08:29:32Z","durationSec":419},"attestation":null}],"rules":[{"id":"LST-1","pass":true,"detail":"Descriptor is valid."},{"id":"LST-2","pass":true,"detail":"Stable version exists and is not deprecated."},{"id":"LST-3","pass":true,"detail":"src/modules and dist/modules pair found."},{"id":"LST-4","pass":true,"detail":"License: MIT."},{"id":"LST-5","pass":true,"detail":"No known malware or open critical advisory."},{"id":"LST-6","pass":true,"detail":"Name reviewed at merge."},{"id":"VER-1","pass":true,"detail":"Receipt on 0.9: verified (0.2.0)."},{"id":"VER-2","pass":true,"detail":"Fits statically on 0.9: ok."},{"id":"VER-3","pass":true,"detail":"Supply chain: ok."},{"id":"VER-4","pass":true,"detail":"No lint errors."},{"id":"VER-5","pass":true,"detail":"Tarball repository.url matches the descriptor."}],"maintainers":["northboundrun"]}
