cloudflare-email 0.4.0 → 0.4.2
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 +59 -2
- data/README.md +63 -379
- data/SECURITY.md +1 -9
- data/docs/activerecord-tenanted.md +0 -11
- data/docs/custom-ingress.md +1 -15
- data/docs/delivery-events.md +2 -5
- data/docs/features.md +49 -123
- data/docs/getting-started.md +90 -172
- data/docs/mailboxes.md +24 -60
- data/docs/management-engine.md +1 -3
- data/docs/outbox.md +5 -6
- data/docs/reference.md +190 -0
- data/docs/routing-diagnostics.md +0 -2
- data/docs/thread-correlation.md +2 -4
- data/docs/troubleshooting.md +4 -4
- data/lib/cloudflare/email/client.rb +23 -5
- data/lib/cloudflare/email/ingress.rb +16 -4
- data/lib/cloudflare/email/version.rb +1 -1
- data/templates/deploy-to-cloudflare/README.md +8 -10
- data/templates/deploy-to-cloudflare/docs/domain-setup.md +86 -166
- data/templates/worker/README.md +6 -8
- data/templates/worker/docs/domain-setup.md +86 -166
- data/templates/worker/docs/durable-inbound.md +4 -16
- metadata +4 -19
- data/docs/architecture.md +0 -105
- data/docs/tenant-mailboxes-plan.md +0 -149
- data/docs/upgrading-0.2.md +0 -101
- data/docs/upgrading-0.3.md +0 -55
- data/docs/verification/2026-09-10-followup.md +0 -49
- data/docs/verification/2026-09-10-install-upgrade.md +0 -86
- data/docs/verification/2026-09-10-live.md +0 -102
- data/docs/verification/2026-09-10.md +0 -175
- data/docs/verification/2026-09-11-custom-ingress.md +0 -49
- data/docs/verification/2026-09-11-extraction.md +0 -32
- data/docs/verification/2026-09-11-outbound-ledger.md +0 -60
- data/docs/verification/2026-09-11-security.md +0 -76
- data/docs/verification/2026-09-11-tenant-mailboxes.md +0 -75
- data/docs/verification/2026-09-12-destination-context.md +0 -46
- data/docs/verification/2026-09-13-action-mailbox-core.md +0 -96
- data/docs/verification/2026-09-13-durable-inbound-and-routing.md +0 -71
|
@@ -1,198 +1,118 @@
|
|
|
1
|
-
# Set up
|
|
1
|
+
# Set up addresses and customer subdomains
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Choose the address pattern for your app:
|
|
4
4
|
|
|
5
|
-
| Pattern | Example |
|
|
5
|
+
| Pattern | Example | Rails setup |
|
|
6
6
|
| --- | --- | --- |
|
|
7
|
-
| One receiving domain | `acme@in.example.com` |
|
|
8
|
-
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## What is verified, and what Cloudflare documents
|
|
16
|
-
|
|
17
|
-
**Rebulk's existing deployment uses the second pattern:**
|
|
18
|
-
`<site>@<organization>.rebulk.com`, wildcard MX, a catch-all Worker rule, and Rails
|
|
19
|
-
organization/mailbox lookup. New organizations do not need a Worker enrollment
|
|
20
|
-
list or a routine per-organization Cloudflare approval. Its September 2026
|
|
21
|
-
rehearsal checked exact-domain public MX answers and archived SMTP-envelope
|
|
22
|
-
delivery evidence, alongside the global catch-all rule. The apex keeps its
|
|
23
|
-
separate mail provider. Sources (Rebulk repository access required):
|
|
24
|
-
|
|
25
|
-
- [Worker architecture and organization setup](https://github.com/Rebulk/rebulk-system/blob/1cc26e0076ba0e9157e89f5879604df8c94430bc/cloudflare/README.md).
|
|
26
|
-
- [Wildcard MX observations and limits of zone-level API evidence](https://github.com/Rebulk/rebulk-system/blob/1cc26e0076ba0e9157e89f5879604df8c94430bc/docs/cloudflare-mailbox-onboarding-rehearsal-2026-09-12.md).
|
|
27
|
-
|
|
28
|
-
This is deployment evidence, not a Cloudflare-wide service guarantee. Cloudflare's
|
|
29
|
-
[subdomain onboarding guide](https://developers.cloudflare.com/email-service/configuration/subdomains/)
|
|
30
|
-
describes adding subdomains explicitly and currently lists a 30-domain combined
|
|
31
|
-
Routing/Sending limit. It does not document arbitrary wildcard Email Routing as
|
|
32
|
-
a way to bypass that limit. Cloudflare separately documents
|
|
33
|
-
[wildcard DNS and exact-record precedence](https://developers.cloudflare.com/dns/manage-dns-records/reference/wildcard-dns-records/).
|
|
34
|
-
Working wildcard DNS alone does not establish Email Routing acceptance. If your
|
|
35
|
-
account does not accept unlisted subdomains, use documented explicit onboarding
|
|
36
|
-
within its limits, or the single-domain pattern. Do not assume that an existing
|
|
37
|
-
Rebulk setup proves a fresh account's behavior.
|
|
38
|
-
|
|
39
|
-
A fresh [public DNS observation](https://github.com/cole-robertson/cloudflare-email/blob/main/docs/verification/2026-09-13-wildcard-dns.json)
|
|
40
|
-
also records Cloudflare MX answers for `test.rebulk.com` and two random subdomains
|
|
41
|
-
without creating DNS records or adding those labels in Cloudflare. That report
|
|
42
|
-
is public and deliberately marks `delivery_verified: false`; no email was sent
|
|
43
|
-
by this DNS-only check.
|
|
44
|
-
|
|
45
|
-
## 1. Fill in your setup worksheet
|
|
46
|
-
|
|
47
|
-
Use a dedicated receiving namespace when the apex already serves Workspace or
|
|
48
|
-
Microsoft 365. Example values:
|
|
49
|
-
|
|
50
|
-
| Setting | Your example value |
|
|
51
|
-
| --- | --- |
|
|
52
|
-
| Cloudflare zone | `example.com` |
|
|
53
|
-
| Receiving base | `in.example.com` |
|
|
54
|
-
| Organization domain | `acme.in.example.com` |
|
|
55
|
-
| Worker | `cloudflare-email-ingress` |
|
|
56
|
-
| Rails ingress URL | `https://app.example.com/rails/action_mailbox/cloudflare/inbound_emails` |
|
|
57
|
-
| Shared ingress secret | Generate with `openssl rand -hex 32`; save privately in Rails and the Worker |
|
|
58
|
-
|
|
59
|
-
Keep staging and production Workers, buckets, queues, secrets, and receiving
|
|
60
|
-
namespaces separate. The standalone deploy-button template takes one environment
|
|
61
|
-
per copy; the regular CLI template uses `--env production` or another explicit env.
|
|
62
|
-
|
|
63
|
-
## 2. Deploy the shared infrastructure
|
|
7
|
+
| One receiving domain | `acme@in.example.com` | Register the domain once; create a mailbox for each customer |
|
|
8
|
+
| Customer subdomains | `invoices@acme.in.example.com` | Register each exact customer domain and its mailboxes |
|
|
9
|
+
|
|
10
|
+
One Worker can serve all your accepted addresses. Rails looks up the mailbox;
|
|
11
|
+
you do not need to maintain an address list in the Worker.
|
|
12
|
+
Database multi-tenancy is optional.
|
|
13
|
+
|
|
14
|
+
## 1. Deploy the Worker
|
|
64
15
|
|
|
65
16
|
[](https://deploy.workers.cloudflare.com/?url=https://github.com/cole-robertson/cloudflare-email/tree/main/templates/deploy-to-cloudflare)
|
|
66
17
|
|
|
67
|
-
Deploy Rails
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
The
|
|
18
|
+
Deploy Rails first, then enter its HTTPS ingress URL and shared secret in the
|
|
19
|
+
Worker deployment form. Cloudflare provisions private R2 storage and a Queue.
|
|
20
|
+
Use separate deployments for development and production.
|
|
21
|
+
|
|
22
|
+
The [setup guide](https://github.com/cole-robertson/cloudflare-email/tree/main/templates/deploy-to-cloudflare)
|
|
23
|
+
covers the form and required resources.
|
|
24
|
+
|
|
25
|
+
## 2. Connect your receiving domain
|
|
26
|
+
|
|
27
|
+
Use a subdomain such as `in.example.com` if your main domain already receives
|
|
28
|
+
mail through Google Workspace or Microsoft 365.
|
|
29
|
+
|
|
30
|
+
In Cloudflare Email Routing:
|
|
31
|
+
|
|
32
|
+
1. [Add the receiving domain or subdomain](https://developers.cloudflare.com/email-service/configuration/subdomains/).
|
|
33
|
+
2. Add the DNS records Cloudflare provides, preserving your main domain's mail records.
|
|
34
|
+
3. Create a route with **Send to a Worker** and select your deployed Worker.
|
|
35
|
+
4. To accept many local parts, configure the receiving domain's
|
|
36
|
+
[catch-all rule](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/#catch-all-rule)
|
|
37
|
+
to use that Worker.
|
|
72
38
|
|
|
73
|
-
|
|
39
|
+
A Cloudflare catch-all gets email to your Worker. Rails still decides which
|
|
40
|
+
addresses are accepted. To put unknown local parts into an inbox, also enable
|
|
41
|
+
the gem's [mailbox catch-all](https://github.com/cole-robertson/cloudflare-email/blob/main/docs/mailboxes.md#receive-unregistered-local-parts-with-an-optional-catch-all).
|
|
74
42
|
|
|
75
|
-
|
|
76
|
-
[Email Routing onboarding](https://developers.cloudflare.com/email-service/get-started/route-emails/)
|
|
77
|
-
and configure its [catch-all action](https://developers.cloudflare.com/email-service/configuration/email-routing-addresses/#catch-all-rule)
|
|
78
|
-
as **Send to a Worker**, selecting your deployed Worker. This avoids one provider
|
|
79
|
-
rule per local part. Review the actual zone/domain scope and any explicit rules
|
|
80
|
-
that take precedence before saving.
|
|
43
|
+
## 3. Customer subdomains
|
|
81
44
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
45
|
+
Cloudflare documents explicit subdomain onboarding. If you want new customer
|
|
46
|
+
subdomains to work without adding each one in Cloudflare, verify wildcard
|
|
47
|
+
receiving on your account before relying on it.
|
|
85
48
|
|
|
86
|
-
|
|
49
|
+
For a receiving namespace under `in.example.com`, the DNS pattern is:
|
|
50
|
+
|
|
51
|
+
| Type | Name in zone `example.com` | Value |
|
|
87
52
|
| --- | --- | --- |
|
|
88
|
-
| MX | `*.in` | Each
|
|
53
|
+
| MX | `*.in` | Each receiving MX target and priority assigned by Cloudflare |
|
|
89
54
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
and verify that it actually handles your intended namespace. A Cloudflare Worker
|
|
95
|
-
HTTP route such as `*.example.com/*` is unrelated to email routing.
|
|
55
|
+
Wildcard DNS alone does not guarantee Email Routing accepts an unlisted
|
|
56
|
+
subdomain. Test with a fresh customer subdomain and confirm email reaches the
|
|
57
|
+
Worker and the correct Rails inbox. If your account requires explicit onboarding,
|
|
58
|
+
add those subdomains in Cloudflare or use the single-domain pattern.
|
|
96
59
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
ones. A wildcard also does not supply MX for the receiving base itself.
|
|
60
|
+
[Explicit DNS records override wildcard inheritance](https://developers.cloudflare.com/dns/manage-dns-records/reference/wildcard-dns-records/),
|
|
61
|
+
including names with A or TXT records. Check existing customer names as well as
|
|
62
|
+
new ones. The receiving base `in.example.com` needs its own records.
|
|
101
63
|
|
|
102
|
-
|
|
64
|
+
### Check DNS
|
|
103
65
|
|
|
104
|
-
From either
|
|
66
|
+
From either Worker template:
|
|
105
67
|
|
|
106
68
|
```sh
|
|
107
69
|
npm run check:subdomains -- --base in.example.com --labels acme,globex
|
|
108
70
|
```
|
|
109
71
|
|
|
110
|
-
This
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
lookup uncertainty. Exit zero means only that all queried names resolved to
|
|
114
|
-
Cloudflare MX; `delivery_verified` remains false. Compare returned targets with
|
|
115
|
-
your account's assigned targets. Existing explicit records can explain differences.
|
|
116
|
-
The command ships in the repository templates; gem 0.3.0's already-published
|
|
117
|
-
template predates it, so refresh your template copy to use it.
|
|
72
|
+
This checks your chosen labels and two fresh labels without changing DNS.
|
|
73
|
+
It needs Node and DNS access, with no API key. A passing result confirms MX
|
|
74
|
+
answers; send a real email to verify delivery.
|
|
118
75
|
|
|
119
|
-
For
|
|
76
|
+
For configured Worker rules, use the
|
|
77
|
+
[Rails routing diagnostic](https://github.com/cole-robertson/cloudflare-email/blob/main/docs/routing-diagnostics.md).
|
|
78
|
+
That check inspects exact DNS records, so inherited wildcard MX may appear missing.
|
|
120
79
|
|
|
121
|
-
|
|
122
|
-
bin/rails cloudflare:email:check_route \
|
|
123
|
-
ADDRESS=invoices@acme.in.example.com \
|
|
124
|
-
WORKER_NAME=cloudflare-email-ingress \
|
|
125
|
-
ACCOUNT_ID=your-cloudflare-account-id
|
|
126
|
-
```
|
|
80
|
+
## 4. Create the mailbox in Rails
|
|
127
81
|
|
|
128
|
-
|
|
129
|
-
That diagnostic inspects exact-domain configured records; it does not resolve
|
|
130
|
-
wildcard DNS inheritance. A working inherited-MX setup can therefore report
|
|
131
|
-
missing exact records. Keep public DNS, provider rule inspection, and actual
|
|
132
|
-
delivery evidence separate; do not force every organization through Cloudflare
|
|
133
|
-
onboarding merely to make that diagnostic green.
|
|
134
|
-
|
|
135
|
-
## 5. Prove the dynamic path before adopting it
|
|
136
|
-
|
|
137
|
-
Use two new organization labels that have not been added individually in the
|
|
138
|
-
Cloudflare dashboard:
|
|
139
|
-
|
|
140
|
-
1. Check their public MX answers with the command above.
|
|
141
|
-
2. Through trusted provisioning code, register both **exact** domains in Rails,
|
|
142
|
-
each against the correct stable organization key. Create a test mailbox for
|
|
143
|
-
each. Record your independently checked configuration evidence and activate
|
|
144
|
-
those test addresses so the gem can receive the verification messages.
|
|
145
|
-
3. Send real email to both addresses from an external mailbox. Confirm the
|
|
146
|
-
Worker receives each, Rails retains the original message, and each appears
|
|
147
|
-
only in the correct organization's inbox. Record the message IDs and result.
|
|
148
|
-
4. Repeat with a newly generated label without changing Cloudflare. If delivery
|
|
149
|
-
fails before the Worker, resolve the Cloudflare acceptance/onboarding issue;
|
|
150
|
-
changing Rails cannot fix a provider SMTP rejection.
|
|
151
|
-
5. Rehearse Rails being unavailable, then verify retained R2 mail drains after
|
|
152
|
-
recovery without duplicate app records. Follow the
|
|
153
|
-
[durable recovery guide](https://github.com/cole-robertson/cloudflare-email/blob/main/templates/worker/docs/durable-inbound.md).
|
|
154
|
-
|
|
155
|
-
These are deliberate test addresses; no real-email test is sent by the DNS checker.
|
|
156
|
-
Successful tests establish evidence for your receiving namespace, not a guarantee
|
|
157
|
-
that every future DNS change or provider policy will preserve it.
|
|
158
|
-
|
|
159
|
-
## 6. Create organizations in Rails
|
|
160
|
-
|
|
161
|
-
Once your namespace policy is verified, use the same gem APIs on organization
|
|
162
|
-
creation. For example, from trusted app code after authorizing the organization:
|
|
82
|
+
After verifying the receiving route, run this from trusted application setup code:
|
|
163
83
|
|
|
164
84
|
```ruby
|
|
165
85
|
registry = Cloudflare::Email::Mailboxes
|
|
166
|
-
organization_key = "organization-123" # Trusted stable app identity, not request input.
|
|
167
86
|
domain = registry.register_domain(
|
|
168
87
|
domain: "acme.in.example.com",
|
|
169
|
-
tenant_key:
|
|
170
|
-
account_id: Cloudflare::Email::Credentials.account_id
|
|
88
|
+
tenant_key: "organization-123"
|
|
171
89
|
)
|
|
172
|
-
registry.activate_domain!(domain.id, evidence:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
90
|
+
registry.activate_domain!(domain.id, evidence: "Receiving route verified")
|
|
91
|
+
|
|
92
|
+
registry.for_tenant("organization-123") do |inboxes|
|
|
93
|
+
mailbox = inboxes.create(
|
|
94
|
+
name: "Invoices",
|
|
95
|
+
address: "invoices@acme.in.example.com",
|
|
96
|
+
owner_ref: "Organization:123"
|
|
97
|
+
)
|
|
98
|
+
inboxes.activate_address!(mailbox.addresses.first.id,
|
|
99
|
+
evidence: "Receiving route verified")
|
|
178
100
|
end
|
|
179
101
|
```
|
|
180
102
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
are separate.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
[
|
|
197
|
-
is separate; keep a verified shared sending domain unless you have explicitly
|
|
198
|
-
onboarded the customer sending domain.
|
|
103
|
+
Use your application's stable organization identity and validate customer slugs.
|
|
104
|
+
Activation records the verification you performed; it does not configure DNS.
|
|
105
|
+
Registration takes exact domains, not `*` patterns.
|
|
106
|
+
|
|
107
|
+
Once wildcard receiving is verified, create new customer domains and mailboxes
|
|
108
|
+
through these same APIs. Send a test message and confirm it appears only in the
|
|
109
|
+
intended inbox. Monitor pending mail so unregistered or suspended destinations
|
|
110
|
+
can be resolved.
|
|
111
|
+
|
|
112
|
+
## Sending from customer addresses
|
|
113
|
+
|
|
114
|
+
Receiving and sending are separate. Cloudflare requires
|
|
115
|
+
[verification of each sending domain](https://developers.cloudflare.com/email-service/configuration/subdomains/#add-a-subdomain-to-email-sending).
|
|
116
|
+
A simple setup uses a shared verified From address and a customer-specific
|
|
117
|
+
Reply-To address. To send through a managed mailbox, follow the
|
|
118
|
+
[mailbox sending setup](https://github.com/cole-robertson/cloudflare-email/blob/main/docs/mailboxes.md#send-from-the-mailbox).
|
data/templates/worker/README.md
CHANGED
|
@@ -7,7 +7,7 @@ gem.
|
|
|
7
7
|
## Deploy
|
|
8
8
|
|
|
9
9
|
Choose a single receiving domain or dynamic organization subdomains using the
|
|
10
|
-
[domain setup
|
|
10
|
+
[domain setup guide](docs/domain-setup.md). Once your
|
|
11
11
|
receiving namespace is verified, new mailboxes can be created in Rails without
|
|
12
12
|
editing a Worker recipient list. `npm run check:subdomains -- --base in.example.com`
|
|
13
13
|
checks public MX for fresh names; it does not claim live delivery verification.
|
|
@@ -66,7 +66,7 @@ For each inbound message, the Worker:
|
|
|
66
66
|
pass. Even exhausted queue retries leave the original message recoverable.
|
|
67
67
|
|
|
68
68
|
The signed request uses HMAC-SHA256 and authenticates the original SMTP envelope
|
|
69
|
-
alongside the MIME bytes. See [durable delivery setup and recovery](
|
|
69
|
+
alongside the MIME bytes. See [durable delivery setup and recovery](docs/durable-inbound.md).
|
|
70
70
|
`INBOUND_DELIVERY_MODE=direct` selects an explicit single-attempt fallback that
|
|
71
71
|
calls `message.setReject` on non-2xx, timeout or network failure. Existing pending
|
|
72
72
|
mail keeps draining through Queue and cron while this fallback is selected.
|
|
@@ -76,8 +76,7 @@ timestamps outside its 5-minute acceptance window. A signature authenticates the
|
|
|
76
76
|
Worker request, not the original email sender. Captured requests remain valid
|
|
77
77
|
inside that window; Rails deduplicates messages through Action Mailbox.
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
transition. Rails accepts v2 and v3 signatures and rejects v1/missing versions. It stores authenticated
|
|
79
|
+
Rails accepts v2 and v3 signatures and rejects v1/missing versions. It stores authenticated
|
|
81
80
|
SMTP metadata separately from MIME; applications read it using
|
|
82
81
|
`Cloudflare::Email::Envelope.for(inbound_email)`. Duplicate detection includes the
|
|
83
82
|
exact SMTP recipient, preserving separate To/Cc/Bcc deliveries. The format accepts
|
|
@@ -144,8 +143,7 @@ encoded as unpadded base64url, limited to 16,384 encoded characters, and sent as
|
|
|
144
143
|
HMAC-SHA256(secret, "v3.{timestamp}.{encoded_envelope}.{encoded_metadata}.{raw_body}")
|
|
145
144
|
```
|
|
146
145
|
|
|
147
|
-
Neither the MIME nor its headers are rewritten.
|
|
148
|
-
supports v3 before enabling metadata in a custom Worker. Keep sensitive provider
|
|
146
|
+
Neither the MIME nor its headers are rewritten. Keep sensitive provider
|
|
149
147
|
data out of metadata unless your application's retention and access policies
|
|
150
148
|
permit storing it with email records.
|
|
151
149
|
|
|
@@ -240,10 +238,10 @@ after delivery and does not replace this optional outage archive.
|
|
|
240
238
|
|
|
241
239
|
## Durable inbound delivery
|
|
242
240
|
|
|
243
|
-
[Durable inbound delivery](
|
|
241
|
+
[Durable inbound delivery](docs/durable-inbound.md) is the default. Provision the
|
|
244
242
|
configured private R2 bucket, Queue producer/consumer and cron before deploying.
|
|
245
243
|
There is no rollout enable flag or automatic downgrade when bindings are missing.
|
|
246
|
-
The guide covers
|
|
244
|
+
The guide covers monitoring, recovery tests, and direct fallback.
|
|
247
245
|
|
|
248
246
|
## Validate changes
|
|
249
247
|
|