cloudflare-email 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +73 -1
- data/README.md +265 -388
- data/SECURITY.md +51 -0
- data/app/controllers/cloudflare/email/ingress_controller.rb +62 -6
- data/docs/activerecord-tenanted.md +135 -0
- data/docs/architecture.md +95 -0
- data/docs/delivery-events.md +177 -0
- data/docs/features.md +107 -0
- data/docs/getting-started.md +298 -0
- data/docs/mailboxes.md +299 -0
- data/docs/outbox.md +175 -0
- data/docs/tenant-mailboxes-plan.md +149 -0
- data/docs/thread-correlation.md +27 -0
- data/docs/troubleshooting.md +103 -0
- data/docs/upgrading-0.2.md +96 -0
- data/docs/verification/2026-09-10-followup.md +49 -0
- data/docs/verification/2026-09-10-install-upgrade.md +86 -0
- data/docs/verification/2026-09-10-live.md +102 -0
- data/docs/verification/2026-09-10.md +175 -0
- data/docs/verification/2026-09-11-extraction.md +32 -0
- data/docs/verification/2026-09-11-outbound-ledger.md +60 -0
- data/docs/verification/2026-09-11-security.md +76 -0
- data/docs/verification/2026-09-11-tenant-mailboxes.md +75 -0
- data/examples/plain_ruby.rb +24 -0
- data/lib/cloudflare/email/active_record/base.rb +48 -0
- data/lib/cloudflare/email/active_record/delivery_events.rb +72 -0
- data/lib/cloudflare/email/active_record/event_inbox.rb +77 -0
- data/lib/cloudflare/email/active_record/event_receipt.rb +17 -0
- data/lib/cloudflare/email/active_record/mail_snapshot.rb +24 -0
- data/lib/cloudflare/email/active_record/outbound_delivery.rb +22 -0
- data/lib/cloudflare/email/active_record/outbound_recipient.rb +13 -0
- data/lib/cloudflare/email/active_record/outbound_reconciliation.rb +15 -0
- data/lib/cloudflare/email/active_record/outbox.rb +219 -0
- data/lib/cloudflare/email/active_record/outbox_notifications.rb +34 -0
- data/lib/cloudflare/email/active_record.rb +5 -0
- data/lib/cloudflare/email/client.rb +51 -11
- data/lib/cloudflare/email/consume_events_task.rb +22 -0
- data/lib/cloudflare/email/delivery_event.rb +70 -0
- data/lib/cloudflare/email/delivery_method.rb +3 -0
- data/lib/cloudflare/email/deploy_worker_task.rb +3 -0
- data/lib/cloudflare/email/dev_ingress_guard.rb +27 -0
- data/lib/cloudflare/email/dev_tunnel.rb +31 -5
- data/lib/cloudflare/email/doctor.rb +16 -38
- data/lib/cloudflare/email/endpoint.rb +24 -0
- data/lib/cloudflare/email/engine.rb +28 -0
- data/lib/cloudflare/email/envelope.rb +58 -0
- data/lib/cloudflare/email/event_consumer.rb +86 -0
- data/lib/cloudflare/email/mailboxes/configuration.rb +58 -0
- data/lib/cloudflare/email/mailboxes/events.rb +112 -0
- data/lib/cloudflare/email/mailboxes/inbound_retention.rb +14 -0
- data/lib/cloudflare/email/mailboxes/jobs.rb +34 -0
- data/lib/cloudflare/email/mailboxes/models.rb +148 -0
- data/lib/cloudflare/email/mailboxes/provider_correlation.rb +11 -0
- data/lib/cloudflare/email/mailboxes/service.rb +288 -0
- data/lib/cloudflare/email/mailboxes/shared_event_receipt.rb +14 -0
- data/lib/cloudflare/email/mailboxes.rb +11 -0
- data/lib/cloudflare/email/message_id.rb +12 -0
- data/lib/cloudflare/email/provision_catchall_task.rb +4 -4
- data/lib/cloudflare/email/provision_route_task.rb +2 -1
- data/lib/cloudflare/email/replay_events_job.rb +18 -0
- data/lib/cloudflare/email/response.rb +21 -3
- data/lib/cloudflare/email/routing_provisioner.rb +71 -45
- data/lib/cloudflare/email/send_job.rb +41 -0
- data/lib/cloudflare/email/send_test.rb +5 -29
- data/lib/cloudflare/email/signing.rb +2 -2
- data/lib/cloudflare/email/tenancy.rb +78 -0
- data/lib/cloudflare/email/tenant_job_context.rb +90 -0
- data/lib/cloudflare/email/verification.rb +30 -8
- data/lib/cloudflare/email/version.rb +1 -1
- data/lib/cloudflare/email/worker_deployer.rb +5 -3
- data/lib/cloudflare-email.rb +4 -1
- data/lib/generators/cloudflare/email/install_generator.rb +48 -33
- data/lib/generators/cloudflare/email/mailboxes/mailboxes_generator.rb +55 -0
- data/lib/generators/cloudflare/email/mailboxes/templates/create_cloudflare_email_mailboxes.rb +54 -0
- data/lib/generators/cloudflare/email/mailboxes/templates/create_cloudflare_email_receiving_domains.rb +16 -0
- data/lib/generators/cloudflare/email/mailboxes/templates/create_cloudflare_email_shared_events.rb +35 -0
- data/lib/generators/cloudflare/email/outbox/outbox_generator.rb +28 -0
- data/lib/generators/cloudflare/email/outbox/templates/create_cloudflare_email_outbox.rb +48 -0
- data/lib/generators/cloudflare/email/templates/initializer.rb +3 -3
- data/lib/generators/cloudflare/email/templates/main_mailbox.rb +2 -7
- data/lib/generators/cloudflare/email/tracking/templates/create_cloudflare_email_event_receipts.rb +22 -0
- data/lib/generators/cloudflare/email/tracking/tracking_generator.rb +26 -0
- data/lib/tasks/cloudflare_email.rake +41 -4
- data/templates/worker/README.md +61 -11
- data/templates/worker/package-lock.json +2627 -0
- data/templates/worker/package.json +8 -5
- data/templates/worker/scripts/wrangler.mjs +16 -0
- data/templates/worker/src/index.js +83 -4
- data/templates/worker/test/index.test.ts +117 -5
- data/templates/worker/wrangler.toml +13 -4
- metadata +107 -6
- data/lib/cloudflare/email/secure_message_id.rb +0 -89
data/templates/worker/README.md
CHANGED
|
@@ -1,37 +1,87 @@
|
|
|
1
1
|
# cloudflare-email-ingress
|
|
2
2
|
|
|
3
3
|
A Cloudflare Email Worker that forwards inbound mail to a Rails ActionMailbox
|
|
4
|
-
ingress shipped with the [`cloudflare-email`](https://github.com/cole/cloudflare-email)
|
|
4
|
+
ingress shipped with the [`cloudflare-email`](https://github.com/cole-robertson/cloudflare-email)
|
|
5
5
|
gem.
|
|
6
6
|
|
|
7
7
|
## Deploy
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm
|
|
11
|
-
wrangler secret put INGRESS_SECRET
|
|
12
|
-
wrangler secret put RAILS_INGRESS_URL
|
|
13
|
-
|
|
10
|
+
npm ci
|
|
11
|
+
npx wrangler secret put INGRESS_SECRET --env production
|
|
12
|
+
npx wrangler secret put RAILS_INGRESS_URL --env production
|
|
13
|
+
npm run deploy -- --env production
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
Use Node 22 (at least 22.12), 24, or 26+. `INGRESS_SECRET` must match Rails' ingress secret;
|
|
17
|
+
`RAILS_INGRESS_URL` is your public HTTPS endpoint, for example
|
|
18
|
+
`https://your-app.com/rails/action_mailbox/cloudflare/inbound_emails`.
|
|
19
|
+
The deploy and dev scripts require an explicit `development`, `staging`, or
|
|
20
|
+
`production` environment. Each has its own Worker and secrets; set both secrets
|
|
21
|
+
for each environment you use. For local development, run
|
|
22
|
+
`npm run dev -- --env development`, with secrets in `.dev.vars.development`.
|
|
23
|
+
Never put secrets into `wrangler.toml` or version control.
|
|
24
|
+
|
|
25
|
+
The Worker refuses non-HTTPS destinations, URL credentials, and fragments.
|
|
26
|
+
HTTP is allowed only for `localhost`, `127.0.0.1`, or `[::1]` local verification.
|
|
27
|
+
The Worker and Rails ingress each default to a 25 MiB MIME size limit. Set
|
|
28
|
+
`MAX_EMAIL_BYTES` to the same positive integer in both environments to change
|
|
29
|
+
that limit. Both check actual body bytes; declared sizes alone are not trusted.
|
|
30
|
+
Rails returns HTTP 413 for oversized mail and rejects malformed or stale signing
|
|
31
|
+
headers before reading the body. Configure request size/rate limits and read
|
|
32
|
+
timeouts at your reverse proxy as well: application limits do not bound earlier
|
|
33
|
+
server buffering or the time spent receiving an HTTP request.
|
|
34
|
+
|
|
16
35
|
Then in the Cloudflare dashboard:
|
|
17
36
|
|
|
18
37
|
1. **Email Routing → Routes**
|
|
19
38
|
2. Add a route for the address you want to receive on (e.g. `support@yourdomain.com`)
|
|
20
|
-
3. Action: **Send to a Worker** → `cloudflare-email-ingress`
|
|
39
|
+
3. Action: **Send to a Worker** → `cloudflare-email-ingress-production`
|
|
40
|
+
|
|
41
|
+
Select the corresponding suffix when deploying another environment. Configure
|
|
42
|
+
Email Routing DNS for the exact domain/subdomain first. Deploying this Worker
|
|
43
|
+
does not change DNS or create routing rules.
|
|
21
44
|
|
|
22
45
|
## How it works
|
|
23
46
|
|
|
24
47
|
For each inbound message, the Worker:
|
|
25
48
|
|
|
26
49
|
1. Reads the raw RFC822 bytes from `message.raw`.
|
|
27
|
-
2.
|
|
50
|
+
2. Encodes `{"from": message.from, "to": message.to}` as unpadded base64url JSON,
|
|
51
|
+
then computes `HMAC-SHA256(INGRESS_SECRET, "v2.{unix_timestamp}.{encoded_envelope}.{raw_body}")`.
|
|
28
52
|
3. POSTs the raw bytes to `RAILS_INGRESS_URL` with:
|
|
29
53
|
- `Content-Type: message/rfc822`
|
|
30
54
|
- `X-CF-Email-Timestamp: <unix seconds>`
|
|
31
55
|
- `X-CF-Email-Signature: <hex digest>`
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
- `X-CF-Email-Signature-Version: 2`
|
|
57
|
+
- `X-CF-Email-Envelope: <encoded envelope>`
|
|
58
|
+
4. If Rails responds non-2xx, the network fails, or the request exceeds 15 seconds,
|
|
59
|
+
the Worker calls `message.setReject`. Redirects are refused to prevent sending
|
|
60
|
+
message content and the signature to a different endpoint. The Worker does
|
|
61
|
+
not retry delivery automatically. A timeout can occur after Rails accepted
|
|
62
|
+
the message, so consumers should handle duplicate deliveries.
|
|
35
63
|
|
|
36
64
|
The Rails controller verifies the signature in constant time and rejects
|
|
37
|
-
timestamps
|
|
65
|
+
timestamps outside its 5-minute acceptance window. A signature authenticates the
|
|
66
|
+
Worker request, not the original email sender. Captured requests remain valid
|
|
67
|
+
inside that window; Rails deduplicates messages through Action Mailbox.
|
|
68
|
+
|
|
69
|
+
Deploy the Rails gem and this Worker together with ingress paused during the
|
|
70
|
+
transition. Rails requires v2 signatures and rejects v1/missing versions. It stores authenticated
|
|
71
|
+
SMTP metadata separately from MIME; applications read it using
|
|
72
|
+
`Cloudflare::Email::Envelope.for(inbound_email)`. Duplicate detection includes the
|
|
73
|
+
exact SMTP recipient, preserving separate To/Cc/Bcc deliveries. The format accepts
|
|
74
|
+
ASCII dot-atom addresses up to 254 bytes (local part up to 64 bytes), plus an empty
|
|
75
|
+
sender for bounce messages. Other address forms are rejected before forwarding.
|
|
76
|
+
|
|
77
|
+
## Validate changes
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
npm ci
|
|
81
|
+
npm test
|
|
82
|
+
npm run check
|
|
83
|
+
npm audit
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Tooling dependencies are development-only; the deployed Worker imports no npm
|
|
87
|
+
packages. `check` bundles a development deployment locally without publishing.
|