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).
|
|
@@ -5,12 +5,6 @@ The bundled Worker uses R2 storage, Queue delivery and scheduled recovery by
|
|
|
5
5
|
default. No enable flag, Rails model, database, or tenancy change is needed.
|
|
6
6
|
Missing infrastructure fails visibly; it never silently downgrades to direct delivery.
|
|
7
7
|
|
|
8
|
-
Upgrade the Rails gem before deploying this Worker. New messages without a parseable
|
|
9
|
-
Message-ID use a deterministic fallback for deduplication across Rails hosts.
|
|
10
|
-
Older records created before this change use ActionMailbox's hostname-dependent
|
|
11
|
-
fallback; replaying those historical messages after upgrading can create a new
|
|
12
|
-
record. Reconcile any existing archive backlog before replaying it automatically.
|
|
13
|
-
|
|
14
8
|
## Set it up
|
|
15
9
|
|
|
16
10
|
Create a private R2 bucket and a Queue for each environment:
|
|
@@ -23,8 +17,7 @@ npx wrangler queues create cloudflare-email-inbound-production
|
|
|
23
17
|
The bundled `wrangler.toml` already configures these resources for each environment.
|
|
24
18
|
Customize their names if needed. Configure **all three** pieces: the `INBOUND_EMAIL_STORE` R2
|
|
25
19
|
binding, `INBOUND_EMAIL_QUEUE` producer and consumer, and the once-per-minute
|
|
26
|
-
scheduled trigger. Keep `RAILS_INGRESS_URL` and `INGRESS_SECRET` configured
|
|
27
|
-
before. Deploy with `npm run deploy -- --env production`.
|
|
20
|
+
scheduled trigger. Keep `RAILS_INGRESS_URL` and `INGRESS_SECRET` configured. Deploy with `npm run deploy -- --env production`.
|
|
28
21
|
|
|
29
22
|
Use separate buckets, queues and secrets for staging and production. Do not add
|
|
30
23
|
an R2 lifecycle rule that expires `cloudflare-email/pending/` objects. Pending
|
|
@@ -38,7 +31,7 @@ binding, and minute schedule before changing code or secrets. This check does no
|
|
|
38
31
|
validate the bucket lifecycle or queue consumer: verify those in your infrastructure
|
|
39
32
|
configuration and live drill. The Ruby task does not provision resources.
|
|
40
33
|
|
|
41
|
-
## Direct fallback
|
|
34
|
+
## Direct fallback
|
|
42
35
|
|
|
43
36
|
Set `INBOUND_DELIVERY_MODE = "direct"` in the environment's Wrangler vars only when
|
|
44
37
|
you deliberately want a single HTTP handoff. With the Ruby task, pass
|
|
@@ -49,15 +42,10 @@ recovery continue draining previously retained mail. To return to durable delive
|
|
|
49
42
|
remove the Wrangler override or pass `INBOUND_DELIVERY_MODE=durable` to the Ruby task.
|
|
50
43
|
Unknown values fail rather than choosing a transport implicitly.
|
|
51
44
|
|
|
52
|
-
The old `DURABLE_INBOUND_ENABLED` rollout flag has been removed. Existing direct
|
|
53
|
-
installations must provision the resources before upgrading the Worker, or explicitly
|
|
54
|
-
select `INBOUND_DELIVERY_MODE=direct` before deploying. Upgrading the Ruby gem alone
|
|
55
|
-
does not redeploy a Worker or change live routing.
|
|
56
|
-
|
|
57
45
|
## What happens to a message
|
|
58
46
|
|
|
59
47
|
1. The Worker validates the envelope and actual MIME size. Invalid or oversized
|
|
60
|
-
mail is permanently rejected
|
|
48
|
+
mail is permanently rejected.
|
|
61
49
|
2. It writes one R2 object containing the exact raw bytes, SMTP envelope, original
|
|
62
50
|
receive time, and optional provider metadata. It waits for the write before
|
|
63
51
|
returning from the Email Worker handler.
|
|
@@ -129,7 +117,7 @@ callback is not canceled. Set `archiveTimeoutMs` to an integer from 1 to 120,000
|
|
|
129
117
|
milliseconds to fit your host's runtime budget. Treat bytes as read-only and never parse the private
|
|
130
118
|
pending frame to build an archive. The primary R2 write is always mandatory.
|
|
131
119
|
Trusted provider metadata is captured once at receipt, never recomputed on replay.
|
|
132
|
-
The
|
|
120
|
+
The `relayEmail` archive callback is best effort and does not enable this
|
|
133
121
|
durable protocol automatically.
|
|
134
122
|
|
|
135
123
|
## Guarantee boundary
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cloudflare-email
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cole
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.1.
|
|
18
|
+
version: 0.1.1
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.1.
|
|
25
|
+
version: 0.1.1
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: base64
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -156,7 +156,6 @@ files:
|
|
|
156
156
|
- app/controllers/cloudflare/email/management/mailboxes_controller.rb
|
|
157
157
|
- app/controllers/cloudflare/email/management/styles_controller.rb
|
|
158
158
|
- docs/activerecord-tenanted.md
|
|
159
|
-
- docs/architecture.md
|
|
160
159
|
- docs/custom-ingress.md
|
|
161
160
|
- docs/delivery-events.md
|
|
162
161
|
- docs/features.md
|
|
@@ -164,25 +163,11 @@ files:
|
|
|
164
163
|
- docs/mailboxes.md
|
|
165
164
|
- docs/management-engine.md
|
|
166
165
|
- docs/outbox.md
|
|
166
|
+
- docs/reference.md
|
|
167
167
|
- docs/routing-deliveries.md
|
|
168
168
|
- docs/routing-diagnostics.md
|
|
169
|
-
- docs/tenant-mailboxes-plan.md
|
|
170
169
|
- docs/thread-correlation.md
|
|
171
170
|
- docs/troubleshooting.md
|
|
172
|
-
- docs/upgrading-0.2.md
|
|
173
|
-
- docs/upgrading-0.3.md
|
|
174
|
-
- docs/verification/2026-09-10-followup.md
|
|
175
|
-
- docs/verification/2026-09-10-install-upgrade.md
|
|
176
|
-
- docs/verification/2026-09-10-live.md
|
|
177
|
-
- docs/verification/2026-09-10.md
|
|
178
|
-
- docs/verification/2026-09-11-custom-ingress.md
|
|
179
|
-
- docs/verification/2026-09-11-extraction.md
|
|
180
|
-
- docs/verification/2026-09-11-outbound-ledger.md
|
|
181
|
-
- docs/verification/2026-09-11-security.md
|
|
182
|
-
- docs/verification/2026-09-11-tenant-mailboxes.md
|
|
183
|
-
- docs/verification/2026-09-12-destination-context.md
|
|
184
|
-
- docs/verification/2026-09-13-action-mailbox-core.md
|
|
185
|
-
- docs/verification/2026-09-13-durable-inbound-and-routing.md
|
|
186
171
|
- examples/plain_ruby.rb
|
|
187
172
|
- lib/cloudflare-email.rb
|
|
188
173
|
- lib/cloudflare/email/active_record.rb
|
data/docs/architecture.md
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# Gem and inbox responsibilities
|
|
2
|
-
|
|
3
|
-
The Cloudflare gem supplies email transport and reusable delivery infrastructure.
|
|
4
|
-
The inbox application supplies a product built on that infrastructure. Plain Ruby
|
|
5
|
-
users should not need Rails or a database. Rails users should be able to opt into
|
|
6
|
-
durable infrastructure without copying the reference inbox's models and services.
|
|
7
|
-
|
|
8
|
-
## Implemented in this extraction
|
|
9
|
-
|
|
10
|
-
| Gem capability | Application responsibility |
|
|
11
|
-
| --- | --- |
|
|
12
|
-
| Optional mailbox/address directory and tenant connection adapter | Authorize domain ownership, provision tenant databases and grant user access |
|
|
13
|
-
| Mailbox memberships, read/archive state and retained raw mail | Inbox UI, retention schedule and explicit purge policy |
|
|
14
|
-
| Shared event intake, tenant correlation and tenant-aware jobs | Durable queue workers, scheduled recovery and schema rollout to every tenant |
|
|
15
|
-
| Structured/raw sending and ActionMailer transport | Compose UI, recipients and send authorization |
|
|
16
|
-
| `Response#accepted?`, plus individual recipient outcomes | Handle partial acceptance without resending accepted recipients |
|
|
17
|
-
| Authenticated ingress (default v2; opt-in v3 [metadata](custom-ingress.md)) and context-scoped deduplication | Map trusted recipient to an authorized mailbox; apply sender and document policy |
|
|
18
|
-
| `MessageId.normalize` and returned provider IDs | Store conversation membership and scope reply lookups |
|
|
19
|
-
| Queue decoding, validation and ACK | Queue/subscription setup and recurring execution |
|
|
20
|
-
| Optional ActiveRecord event receipts, deduplication and indexed replay | Configure queue polling and receipt retention |
|
|
21
|
-
| Immutable outbox snapshots, send claims, per-recipient outcomes | Authorize sending and choose a stable operation key |
|
|
22
|
-
| `DeliveryEvents` account/message/recipient correlation and ordering | Transactional callback to update product records |
|
|
23
|
-
| Send/replay jobs, recovery tasks and audited reconciliation | Authorize operators and provide evidence of provider outcome |
|
|
24
|
-
|
|
25
|
-
The ActiveRecord adapter is explicit opt-in. Its generator installs a receipt table
|
|
26
|
-
and initializer; the default plain Ruby client does not load ActiveRecord. Its
|
|
27
|
-
handler applies application writes on the same database connection as the receipt.
|
|
28
|
-
It does not wrap HTTP calls or other external effects in a database transaction.
|
|
29
|
-
|
|
30
|
-
Version 0.2 is a preproduction break: v1 ingress and signed Message-ID tokens are
|
|
31
|
-
removed. Cloudflare's documented queue encodings and recipient-response forms are
|
|
32
|
-
provider contracts, so accepting them is not obsolete application compatibility.
|
|
33
|
-
Historical verification reports describe their dated runs, not the current API.
|
|
34
|
-
|
|
35
|
-
## Implemented: durable outbound delivery
|
|
36
|
-
|
|
37
|
-
The inbox now uses the gem's ledger tables and orchestration. Its model subclasses
|
|
38
|
-
add application message associations and display snapshots; they do not maintain
|
|
39
|
-
a second sending ledger. See the [outbox guide](outbox.md) for installation and API.
|
|
40
|
-
|
|
41
|
-
1. Claim an application-supplied operation key scoped to an account using a unique
|
|
42
|
-
index before sending; repeated or concurrent calls cannot silently resend.
|
|
43
|
-
2. Persist immutable rendered MIME and envelope recipients before the network
|
|
44
|
-
request. Track each recipient separately, including partial acceptance.
|
|
45
|
-
3. Separate preparation failures from ambiguous outcomes. A timeout, process death
|
|
46
|
-
or persistence failure after acceptance keeps the operation blocked. An elapsed
|
|
47
|
-
timeout alone never proves the message was not sent.
|
|
48
|
-
4. Persist validated provider IDs and recipient acceptance outcomes separately
|
|
49
|
-
from later lifecycle events. Replay correlates account, message and recipient;
|
|
50
|
-
conflicting IDs remain uncertain. The network and database are not atomic.
|
|
51
|
-
5. Expose audited reconciliation with caller-supplied actor/reason and evidence.
|
|
52
|
-
The application authorizes the operator; the gem enforces legal transitions.
|
|
53
|
-
6. Provide jobs/tasks and notifications that preserve the same claim on retries.
|
|
54
|
-
Include migration/import support for the reference inbox's existing attempts.
|
|
55
|
-
|
|
56
|
-
The suite exercises concurrent senders, actual process termination, acceptance
|
|
57
|
-
followed by persistence failure, partial recipients, event-before-response,
|
|
58
|
-
out-of-order events, callbacks that roll back, and recovery without a duplicate
|
|
59
|
-
send. PostgreSQL and SQLite run separate concurrency checks. Inbox HTTP and browser
|
|
60
|
-
workflows use the shared implementation, with controlled local provider responses.
|
|
61
|
-
|
|
62
|
-
Historic attempts did not preserve rendered MIME. Their import retains available
|
|
63
|
-
bodies, IDs, outcomes and operator evidence and marks reconstructed snapshots.
|
|
64
|
-
Unknown historical outcomes remain blocked. No migration claims to recreate the
|
|
65
|
-
exact bytes of a previously sent message.
|
|
66
|
-
|
|
67
|
-
## Operational completion
|
|
68
|
-
|
|
69
|
-
The reference inbox supplies a functional mailbox product over these APIs. Before
|
|
70
|
-
production use, configure sending DNS and receiving routes, queue subscriptions
|
|
71
|
-
and dead-letter handling, durable Rails jobs, receipt/MIME retention and backups,
|
|
72
|
-
and monitoring for prepared/sending/unknown/partial operations. Exercise live
|
|
73
|
-
external mailbox delivery and recovery with the deployed revision. Installation
|
|
74
|
-
does not automatically provision domains or deploy infrastructure.
|
|
75
|
-
|
|
76
|
-
Current limitations are deliberate and visible: no provider exactly-once API,
|
|
77
|
-
no automatic resend of uncertain or rejected operations, no multi-provider-ID
|
|
78
|
-
operation (conflicting IDs require review). Direct inbound forwarding can reject
|
|
79
|
-
mail during a Rails outage. The default [durable Worker path](../templates/worker/README.md#durable-inbound-delivery)
|
|
80
|
-
commits raw mail and stable envelope context to R2 before returning successfully.
|
|
81
|
-
Queue handoffs and a scheduled recovery pass retry pending mail; a Rails 2xx
|
|
82
|
-
allows removal from the pending store. Queue expiration does not remove R2 mail.
|
|
83
|
-
This boundary requires durable Rails persistence before 2xx, idempotent replay,
|
|
84
|
-
private R2 storage without expiration of pending objects, and operational
|
|
85
|
-
monitoring. It cannot guarantee acceptance when Cloudflare itself cannot store
|
|
86
|
-
the incoming message.
|
|
87
|
-
A prepared operation can be dispatched using its existing identity after a job
|
|
88
|
-
enqueue failure; sending/unknown operations require evidence-based reconciliation.
|
|
89
|
-
Notifications indicate method execution and may run inside an outer transaction;
|
|
90
|
-
the durable database record is authoritative.
|
|
91
|
-
|
|
92
|
-
## Keep in the inbox product
|
|
93
|
-
|
|
94
|
-
Users, tenancy, mailbox ownership, conversations, folders, search and UI remain in
|
|
95
|
-
the inbox. AI context, review flags, approval thresholds, auto-send policy and
|
|
96
|
-
operator permissions also stay there. Suppression/unsubscribe requirements depend
|
|
97
|
-
on message purpose and application policy; expose provider outcomes, but do not
|
|
98
|
-
silently impose a marketing subscription model on transactional mail.
|
|
99
|
-
|
|
100
|
-
The optional [mailbox module](mailboxes.md) now packages mailbox persistence and
|
|
101
|
-
address management over the existing transport/receipt APIs. It includes a
|
|
102
|
-
[tenant adapter](activerecord-tenanted.md), without adding a tenant library to
|
|
103
|
-
the plain Ruby client. The optional [management engine](management-engine.md)
|
|
104
|
-
consumes these same APIs with host-supplied authentication and authorization.
|
|
105
|
-
Full inbox products can continue using them through their own frontends.
|