cloudflare-email 0.3.0 → 0.4.1

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -1
  3. data/README.md +63 -369
  4. data/SECURITY.md +1 -9
  5. data/app/controllers/cloudflare/email/ingress_controller.rb +1 -2
  6. data/app/controllers/cloudflare/email/management/mailboxes_controller.rb +2 -166
  7. data/app/controllers/cloudflare/email/management/styles_controller.rb +2 -6
  8. data/docs/activerecord-tenanted.md +0 -11
  9. data/docs/custom-ingress.md +6 -16
  10. data/docs/delivery-events.md +2 -5
  11. data/docs/features.md +49 -123
  12. data/docs/getting-started.md +91 -172
  13. data/docs/mailboxes.md +27 -56
  14. data/docs/management-engine.md +1 -3
  15. data/docs/outbox.md +5 -6
  16. data/docs/reference.md +190 -0
  17. data/docs/routing-diagnostics.md +7 -2
  18. data/docs/thread-correlation.md +2 -4
  19. data/docs/troubleshooting.md +4 -4
  20. data/lib/cloudflare/email/active_record/base.rb +3 -53
  21. data/lib/cloudflare/email/client.rb +23 -5
  22. data/lib/cloudflare/email/engine.rb +1 -13
  23. data/lib/cloudflare/email/envelope.rb +2 -12
  24. data/lib/cloudflare/email/error.rb +5 -12
  25. data/lib/cloudflare/email/ingress.rb +19 -8
  26. data/lib/cloudflare/email/mailboxes/configuration.rb +7 -15
  27. data/lib/cloudflare/email/mailboxes/inbound_retention.rb +1 -14
  28. data/lib/cloudflare/email/mailboxes/models.rb +8 -147
  29. data/lib/cloudflare/email/mailboxes/service.rb +7 -258
  30. data/lib/cloudflare/email/mailboxes.rb +1 -0
  31. data/lib/cloudflare/email/management/adapter.rb +3 -25
  32. data/lib/cloudflare/email/management/configuration.rb +4 -11
  33. data/lib/cloudflare/email/management/engine.rb +2 -0
  34. data/lib/cloudflare/email/tenancy.rb +2 -73
  35. data/lib/cloudflare/email/tenant_job_context.rb +2 -85
  36. data/lib/cloudflare/email/version.rb +1 -1
  37. data/lib/cloudflare-email.rb +6 -0
  38. data/lib/generators/cloudflare/email/mailboxes/templates/create_cloudflare_email_mailboxes.rb +1 -0
  39. data/lib/generators/cloudflare/email/mailboxes/templates/create_cloudflare_email_receiving_domains.rb +1 -1
  40. data/templates/deploy-to-cloudflare/README.md +25 -7
  41. data/templates/deploy-to-cloudflare/docs/domain-setup.md +118 -0
  42. data/templates/deploy-to-cloudflare/package.json +1 -0
  43. data/templates/deploy-to-cloudflare/scripts/check-subdomains.mjs +54 -0
  44. data/templates/deploy-to-cloudflare/test/subdomains.test.ts +51 -0
  45. data/templates/worker/README.md +11 -7
  46. data/templates/worker/docs/domain-setup.md +118 -0
  47. data/templates/worker/docs/durable-inbound.md +4 -16
  48. data/templates/worker/package.json +1 -0
  49. data/templates/worker/scripts/check-subdomains.mjs +54 -0
  50. data/templates/worker/test/subdomains.test.ts +51 -0
  51. metadata +22 -21
  52. data/app/views/cloudflare/email/management/mailboxes/index.html.erb +0 -65
  53. data/app/views/cloudflare/email/management/mailboxes/message.html.erb +0 -34
  54. data/app/views/cloudflare/email/management/mailboxes/show.html.erb +0 -86
  55. data/app/views/layouts/cloudflare/email/management.html.erb +0 -29
  56. data/docs/architecture.md +0 -105
  57. data/docs/tenant-mailboxes-plan.md +0 -149
  58. data/docs/upgrading-0.2.md +0 -101
  59. data/docs/upgrading-0.3.md +0 -55
  60. data/docs/verification/2026-09-10-followup.md +0 -49
  61. data/docs/verification/2026-09-10-install-upgrade.md +0 -86
  62. data/docs/verification/2026-09-10-live.md +0 -102
  63. data/docs/verification/2026-09-10.md +0 -175
  64. data/docs/verification/2026-09-11-custom-ingress.md +0 -49
  65. data/docs/verification/2026-09-11-extraction.md +0 -32
  66. data/docs/verification/2026-09-11-outbound-ledger.md +0 -60
  67. data/docs/verification/2026-09-11-security.md +0 -76
  68. data/docs/verification/2026-09-11-tenant-mailboxes.md +0 -75
  69. data/docs/verification/2026-09-12-destination-context.md +0 -46
  70. data/docs/verification/2026-09-13-durable-inbound-and-routing.md +0 -71
  71. data/lib/cloudflare/email/management/management.css +0 -68
@@ -1,70 +1,58 @@
1
- # Get email working in your Rails app
1
+ # Set up email in Rails
2
2
 
3
- This guide takes you from your first outgoing email to receiving replies and
4
- tracking deliveries. Start with sending and add the other pieces as needed.
5
- For a mailbox app, follow all four parts. **SQLite is supported throughout.**
3
+ This guide covers sending, receiving, and delivery tracking. For persistent
4
+ inboxes and a management UI, continue with [mailboxes](mailboxes.md).
5
+ SQLite works throughout.
6
6
 
7
- You will need an existing Rails app, a Cloudflare account, and a domain you can
8
- configure in Cloudflare. Use Ruby 3.2+ and a patched supported Rails release:
9
- tested floors are 7.2.3.2, 8.0.5.1 and 8.1.3.1. Rails 8 requires Ruby 3.3+.
10
- Ruby 4 is tested with Rails 8.1. Plain Ruby users can use the
11
- [standalone client](../README.md#plain-ruby) instead.
7
+ You need Rails 7.2–8.1, a Cloudflare account, and a domain you control.
8
+ These examples use `mail.example.com` for sending and `in.example.com` for receiving.
12
9
 
13
- Example addresses below use `mail.example.com` for sending and
14
- `in.example.com` for receiving. Replace them with subdomains you own. They do
15
- not need to be the same domain as your Rails application's web address.
10
+ ## 1. Install
16
11
 
17
- ## 1. Send your first email
18
-
19
- ### Install the current code
20
-
21
- Add version 0.3 to your app's `Gemfile`. If upgrading an existing installation, read the
22
- [upgrade guide](upgrading-0.3.md) before changing an existing Worker deployment:
12
+ Add to your Gemfile:
23
13
 
24
14
  ```ruby
25
- gem "cloudflare-email", "~> 0.3.0"
15
+ gem "cloudflare-email", "~> 0.4.0"
26
16
  ```
27
17
 
28
18
  ```sh
29
19
  bundle install
30
- bin/rails generate cloudflare:email:install --no-inbound
20
+ bin/rails generate cloudflare:email:install
21
+ bin/rails db:migrate
31
22
  ```
32
23
 
33
- The generator creates an initializer that chooses `:cloudflare` as your
34
- ActionMailer delivery method. The `--no-inbound` option keeps this first step
35
- focused on sending; you can add receiving below.
24
+ Follow the prompts to install Action Mailbox and the default mailbox.
25
+ The generator also configures Action Mailer and copies the Worker template.
26
+ For a send-only app, use `--no-inbound` and skip the receiving steps below.
36
27
 
37
- ### Connect your sending domain
28
+ ## 2. Send your first email
38
29
 
39
- In Cloudflare, open **Compute → Email Service → Email Sending** and onboard
40
- your sending domain. Complete Cloudflare's DNS instructions and wait for domain
41
- verification. A dedicated subdomain helps keep an existing mail provider's apex
42
- configuration separate. Do not replace existing apex mail records casually.
30
+ In Cloudflare, open **Compute → Email Service → Email Sending**, add
31
+ `mail.example.com`, and complete its DNS verification. A sending subdomain lets
32
+ you keep your existing domain's mail provider.
43
33
 
44
- Create an account-scoped token with permission to send email. Add these variables
45
- to the environment where Rails runs, or use the equivalent Rails credentials:
34
+ Create a token with Email Sending permission and set:
46
35
 
47
36
  ```sh
48
37
  export CLOUDFLARE_ACCOUNT_ID=your-account-id
49
38
  export CLOUDFLARE_API_TOKEN=your-email-sending-token
50
39
  ```
51
40
 
52
- Rails credentials use `cloudflare.account_id` and `cloudflare.api_token`.
53
- Nonempty Rails credentials take precedence over environment variables. Keep
54
- real tokens out of committed files. Restart Rails after changing configuration.
41
+ Alternatively, use Rails credentials `cloudflare.account_id` and
42
+ `cloudflare.api_token`. Nonempty Rails credentials take precedence.
43
+ Restart Rails after changing configuration.
55
44
 
56
- ### Check the setup and send
45
+ Check the configuration and send to an inbox you control:
57
46
 
58
47
  ```sh
59
48
  bin/rails cloudflare:email:doctor
60
49
  FROM=hello@mail.example.com TO=you@example.net bin/rails cloudflare:email:send_test
61
50
  ```
62
51
 
63
- Use a destination inbox you control. `doctor` checks configuration and available
64
- read access; the second command actually sends mail. Confirm that it arrives,
65
- including checking spam. A diagnostics success alone does not prove delivery.
52
+ Check the destination inbox and spam folder. `doctor` checks configuration;
53
+ `send_test` sends the message.
66
54
 
67
- ### Use your regular mailers
55
+ ### Use your mailers
68
56
 
69
57
  ```ruby
70
58
  # app/mailers/hello_mailer.rb
@@ -75,80 +63,48 @@ class HelloMailer < ApplicationMailer
75
63
  end
76
64
  end
77
65
  end
78
- ```
79
-
80
- From `bin/rails console`:
81
66
 
82
- ```ruby
83
67
  HelloMailer.hello("you@example.net").deliver_now
84
68
  ```
85
69
 
86
- Your existing HTML templates, multipart messages, attachments and cc/bcc work
87
- through ActionMailer. Use `deliver_later` with your app's job backend for ordinary
88
- background delivery. For saved send history and protection against ambiguous
89
- resends, use the outbox in part 3. Do not send the same message through both paths.
70
+ Use `deliver_later` with your app's job backend for background sending.
71
+ HTML templates, attachments, multipart messages, and cc/bcc work through Action Mailer.
90
72
 
91
- ## 2. Receive email and replies
92
-
93
- Incoming email follows this path:
73
+ ## 3. Receive email
94
74
 
95
75
  ```text
96
- Sender → Cloudflare Email Routing → Email Worker → Rails ActionMailbox → your app
76
+ Sender → Cloudflare Email Routing → Worker → Rails Action Mailbox → your handler
97
77
  ```
98
78
 
99
- The Worker is a small program that forwards the email to Rails and signs it so
100
- Rails can verify the forwarding request. The installer supplies its code.
79
+ The Worker saves incoming email at Cloudflare and retries delivery while Rails
80
+ is unavailable.
101
81
 
102
- ### Install receiving support
82
+ ### Deploy the Worker
103
83
 
104
- ```sh
105
- bin/rails generate cloudflare:email:install
106
- bin/rails db:migrate
107
- ```
84
+ Save the generated secret as `cloudflare.ingress_secret` in Rails credentials
85
+ or `CLOUDFLARE_INGRESS_SECRET`. Deploy Rails with Active Storage and a running
86
+ job worker, then follow the [Deploy to Cloudflare guide](../templates/deploy-to-cloudflare/README.md).
108
87
 
109
- Follow the interactive prompts to install ActionMailbox and the default mailbox.
110
- If you already have custom mailbox routing or initializer changes, inspect the
111
- generator's overwrite prompts before accepting. The generated default mailbox
112
- only logs receipt; you will add your product's processing logic.
88
+ The deployment form asks for:
113
89
 
114
- Save the generated shared secret as `cloudflare.ingress_secret` in Rails
115
- credentials or `CLOUDFLARE_INGRESS_SECRET`. The deploy task copies that secret to
116
- the Worker. Use a separate deployment token as `cloudflare.management_token` or
117
- `CLOUDFLARE_MANAGEMENT_TOKEN`; [the permission table](../README.md#observability-and-permissions)
118
- lists the scopes needed for Worker deployment and routing.
90
+ - **RAILS_INGRESS_URL:** `https://app.example.com/rails/action_mailbox/cloudflare/inbound_emails`
91
+ - **INGRESS_SECRET:** the same secret configured in Rails
119
92
 
120
- ### Connect the receiving address
93
+ ### Connect an address
121
94
 
122
- In Cloudflare **Email Routing → your apex domain → Settings → Subdomains**, add
123
- your receiving subdomain, such as `in.example.com`, and finish its DNS setup.
124
- This is separate from verifying the sending domain.
95
+ In Cloudflare Email Routing, configure `in.example.com` and its receiving DNS
96
+ records. Create a rule for `support@in.example.com` with **Send to a Worker**,
97
+ selecting the deployed Worker.
125
98
 
126
- Deploy Rails with ActionMailbox storage and workers configured. For guided
127
- Worker setup, use the [Deploy to Cloudflare template](../templates/deploy-to-cloudflare/README.md).
128
- It provisions R2 and Queues and prompts for your Rails URL and shared secret.
129
- Then select that Worker in your Email Routing rule as described in its guide.
99
+ Sending-domain verification and receiving routes are separate.
100
+ For many addresses or customer subdomains, use the [domain setup guide](../templates/worker/docs/domain-setup.md).
130
101
 
131
- For the CLI setup instead, run these commands in an environment with production
132
- configuration and deployment credentials:
102
+ Send a test email to `support@in.example.com`. Confirm Rails stores it and
103
+ the mailbox job finishes.
133
104
 
134
- First create the private R2 bucket and Queue, then deploy the generated Wrangler
135
- configuration using the [durable setup guide](../templates/worker/docs/durable-inbound.md).
136
- The Ruby task below updates that provisioned Worker while preserving its bindings.
137
- It refuses a missing bucket/queue binding or recovery schedule before changing code.
105
+ ### Process the email
138
106
 
139
- ```sh
140
- RAILS_ENV=production bin/rails cloudflare:email:deploy_worker URL=https://app.example.com/rails/action_mailbox/cloudflare/inbound_emails
141
- RAILS_ENV=production bin/rails cloudflare:email:provision_route ADDRESS=support@in.example.com
142
- ```
143
-
144
- The first command deploys the Worker; the second routes the address to it.
145
- Send a real test email to `support@in.example.com`, then check the ActionMailbox
146
- record and processing job. A web request accepted by Rails means storage worked;
147
- the mailbox job can still need attention.
148
-
149
- ### Read the message in your mailbox
150
-
151
- Inside the generated `MainMailbox#process`, these values are available:
107
+ Add your application logic to the generated `MainMailbox#process`:
152
108
 
153
109
  ```ruby
154
110
  envelope = Cloudflare::Email::Envelope.for(inbound_email)
@@ -159,41 +115,32 @@ attachments = mail.attachments
159
115
  raw_message = inbound_email.raw_email.download
160
116
  ```
161
117
 
162
- Save or process those values using your application's models. For mailbox or
163
- tenant selection, use `receiving_address`, which comes from the authenticated
164
- SMTP envelope. MIME `To`/`Cc` headers may differ, especially for Bcc. The envelope
165
- can be absent for records from another ingress; handle that case explicitly.
166
- Treat message contents and attachments as untrusted input when displaying them.
118
+ Use the verified `receiving_address` for mailbox or organization lookup.
119
+ The MIME `To` header can differ, especially for Bcc. Treat message contents
120
+ and attachments as untrusted input.
167
121
 
168
- To direct replies to this address, add `reply_to: "support@in.example.com"` to
169
- your mailer's `mail(...)` call. For conversation matching, save the returned
170
- provider message ID and compare reply headers within your application's mailbox
171
- scope. The outbox saves this ID for you. See [thread correlation](thread-correlation.md).
122
+ Set `reply_to: "support@in.example.com"` in outgoing mail to receive replies here.
123
+ See [reply matching](thread-correlation.md) for conversations.
172
124
 
173
- ### Receive into a local Rails server
125
+ ### Test with a local Rails server
174
126
 
175
- Install `cloudflared`, configure development credentials and a separate development
176
- receiving route, then start Rails. In another terminal:
127
+ Install `cloudflared`, start Rails, and configure a separate development address.
128
+ Then run:
177
129
 
178
130
  ```sh
179
131
  INBOUND_DELIVERY_MODE=direct bin/rails cloudflare:email:deploy_worker
180
132
  bin/rails cloudflare:email:dev
181
133
  ```
182
134
 
183
- The first command creates the development Worker with the explicit direct fallback; the second points it at a
184
- temporary tunnel to your local Rails server on port 3000. Keep the tunnel running
185
- while testing and stop it with Ctrl-C. Set `PORT=...` if Rails uses another port.
186
- The task checks that Rails has the ingress-only guard; restart Rails if asked.
187
- Use development test data. The tunnel URL is temporary and does not provide an
188
- offline mailbox when your local server is stopped.
189
-
190
- ## 3. Add the durable outbox with SQLite
135
+ These commands need a [Worker management token](reference.md#observability-and-permissions).
136
+ Keep the tunnel running while testing. It uses port 3000 by default;
137
+ set `PORT=...` for another port. Direct mode does not retain mail while
138
+ your local server is stopped.
191
139
 
192
- An outbox saves the exact message before sending it. Background jobs reference
193
- that saved operation instead of re-rendering and resending mail on every retry.
194
- This is useful for compose, replies, invoices and approved drafts.
140
+ ## 4. Save outgoing messages
195
141
 
196
- If your Rails app already uses SQLite, keep its database configuration. Run:
142
+ The outbox saves a message before sending, tracks each recipient, and protects
143
+ against accidental resends after a timeout.
197
144
 
198
145
  ```sh
199
146
  bin/rails generate cloudflare:email:tracking
@@ -201,33 +148,26 @@ bin/rails generate cloudflare:email:outbox
201
148
  bin/rails db:migrate
202
149
  ```
203
150
 
204
- These add the receipt and delivery tables plus opt-in initializers. Skip a
205
- generator whose migration you already installed. Configure a durable ActiveJob
206
- backend and run a worker that processes the `mailers` queue; the gem does not
207
- install the backend for you.
151
+ If you are setting up [managed mailboxes](mailboxes.md), use that generator
152
+ instead—it includes both of these.
208
153
 
209
- Try this from Rails console, outside an existing database transaction:
154
+ With a job worker processing the `mailers` queue, try this from Rails console:
210
155
 
211
156
  ```ruby
212
157
  account_id = Cloudflare::Email::Credentials.account_id
213
158
  operation = Cloudflare::Email::ActiveRecord::Outbox.prepare_mail(
214
159
  account_id: account_id,
215
- operation_key: "getting-started:hello:1",
160
+ operation_key: "hello:1",
216
161
  mail: HelloMailer.hello("you@example.net").message,
217
162
  )
218
163
  Cloudflare::Email::SendJob.perform_later(account_id, operation.operation_key)
219
164
  ```
220
165
 
221
- The operation key identifies **one intended send** in your account. Save it with
222
- your product record. For a genuinely new message use a new key; for a job retry
223
- reuse the saved identity. Do not re-render a mailer and call `prepare_mail` as
224
- your retry mechanism: generated MIME headers can change, causing a snapshot conflict.
225
-
226
- In an application transaction, prepare the operation alongside your business
227
- record, then enqueue only after the outer transaction commits. The detailed
228
- [outbox guide](outbox.md#prepare-once-dispatch-after-commit) shows that pattern.
166
+ Use one operation key per intended send. For retries, dispatch the saved
167
+ operation rather than rendering and sending the mailer again.
168
+ In application transactions, enqueue after commit.
229
169
 
230
- After the worker runs, inspect the result:
170
+ Inspect the result:
231
171
 
232
172
  ```ruby
233
173
  operation.reload.state
@@ -235,26 +175,23 @@ operation.provider_message_id
235
175
  operation.outbound_recipients.pluck(:recipient, :acceptance_state, :state)
236
176
  ```
237
177
 
238
- `accepted` means all recipients have provider acceptance evidence; `partial`
239
- means only some do. Neither promises that a human received or read the message.
240
- `unknown` or a stuck `sending` operation needs investigation, not a new send key
241
- as a shortcut. See [states and recovery](outbox.md#states-and-retries).
178
+ See the [outbox guide](outbox.md) for application transactions, callbacks,
179
+ and recovery of uncertain sends.
242
180
 
243
- ## 4. Track delivery, bounces and complaints
181
+ ## 5. Track delivery
244
182
 
245
- Cloudflare sends later delivery updates through a Queue. You configure that
246
- Queue and its Email Sending subscription once; your app polls it regularly.
247
- Follow [the provisioning steps](delivery-events.md#provision-once), including an
248
- HTTP pull consumer, retries and a dead-letter queue for repeatedly failing events.
183
+ Create an outbound Cloudflare Queue and Email Sending subscription using the
184
+ [delivery-event setup](delivery-events.md#provision-once). This is a separate
185
+ queue from the inbound Worker queue.
249
186
 
250
- Add a separate queue token and queue ID to your Rails environment:
187
+ Set:
251
188
 
252
189
  ```sh
253
190
  export CLOUDFLARE_QUEUES_TOKEN=your-queues-read-write-token
254
191
  export CLOUDFLARE_EVENT_QUEUE_ID=your-queue-id
255
192
  ```
256
193
 
257
- With the outbox installed, configure this initializer:
194
+ With the outbox installed, configure:
258
195
 
259
196
  ```ruby
260
197
  # config/initializers/cloudflare_delivery_events.rb
@@ -268,41 +205,23 @@ Rails.application.configure do
268
205
  end
269
206
  ```
270
207
 
271
- Restart Rails and poll a batch:
208
+ Restart Rails and run:
272
209
 
273
210
  ```sh
274
211
  bin/rails cloudflare:email:consume_events
275
- ```
276
-
277
- This command processes **one batch and exits**. Schedule it repeatedly using
278
- your application's scheduler. Also schedule receipt replay for recovery:
279
-
280
- ```sh
281
212
  bin/rails cloudflare:email:replay_events
282
213
  ```
283
214
 
284
- The gem saves receipts before acknowledgement, handles duplicates, and matches
285
- updates to the account, provider message ID and recipient. If an event arrives
286
- before the send result is saved, it stays available for replay. Recipient state
287
- then reflects delivered, deferred, bounced, failed, rejected or complained events.
288
- Your app still decides what those outcomes mean for its UI and future sending.
289
-
290
- For custom product updates, use the [transactional callbacks](outbox.md#recover-dispatch-and-application-projections).
291
- If you only want events and have your own delivery models, use the lower-level
292
- [durable receipt adapter](delivery-events.md#durable-rails-receipts).
293
-
294
- ## Before relying on it
215
+ Each command runs once and exits; schedule both to run regularly.
216
+ Receipts are saved before acknowledgement and matched to the message's
217
+ recipients. Delivery states include delivered, deferred, bounced, failed,
218
+ rejected, and complained.
295
219
 
296
- Send to an external inbox you control, reply back, verify stored attachments,
297
- and confirm a real queue event updates the intended recipient. Keep a durable
298
- job worker running and schedule recovery of `prepared` operations that might
299
- have committed before enqueueing. Monitor stuck operations and failed mailbox jobs.
220
+ ## Check your setup
300
221
 
301
- Protect stored MIME and attachments, choose retention and backup policies, and
302
- set proxy/server request limits. The default incoming raw message limit is
303
- 25 MiB; set the same positive `MAX_EMAIL_BYTES` on Rails and the Worker if you
304
- override it. Cloudflare has separate [outgoing size and recipient limits](https://developers.cloudflare.com/email-service/platform/limits/).
222
+ Send to an external inbox, reply back, check the stored attachment, and confirm
223
+ a delivery event updates the intended recipient. Keep Rails jobs and event
224
+ polling running, and monitor failed jobs and the Worker's pending mail.
305
225
 
306
- Use [troubleshooting](troubleshooting.md) when something does not arrive,
307
- [the feature overview](features.md) to explore more, and
308
- [the upgrade guide](upgrading-0.3.md) for an existing 0.1 installation.
226
+ Next: [create inboxes](mailboxes.md), [add the management UI](management-engine.md),
227
+ or [troubleshoot missing mail](troubleshooting.md).
data/docs/mailboxes.md CHANGED
@@ -3,15 +3,10 @@
3
3
  The optional mailbox module lets your app create mailboxes and aliases, receive
4
4
  mail into them, track read/archive state, and send through the durable outbox.
5
5
  It works in one SQLite database or with a separate database per organization.
6
- It supplies models and services; your app supplies permissions and the UI.
6
+ Use the [management UI](management-engine.md) or build your own interface with these APIs.
7
7
 
8
- This module is available since version 0.2.0. Install `gem "cloudflare-email", "~> 0.3.0"` for the latest features.
9
-
10
- **Database multi-tenancy is off by default.** The mailbox generator works with
11
- one ordinary database. Calling `for_tenant` groups and scopes mailbox records;
12
- it does not create databases or install a tenant adapter. Separate databases
13
- require explicit `Tenancy.configure(...)` before models load. Neither Rails nor
14
- `activerecord-tenanted` is added to plain Ruby applications by this module.
8
+ **Database multi-tenancy is off by default.** `for_tenant` scopes records in your
9
+ existing database. [Separate databases](activerecord-tenanted.md) require explicit setup.
15
10
 
16
11
  ## Install
17
12
 
@@ -22,20 +17,19 @@ bin/rails generate cloudflare:email:mailboxes
22
17
  bin/rails db:migrate
23
18
  ```
24
19
 
25
- The generator includes the outbox and tracking generators. Do not generate those
26
- migrations a second time if you already installed them; inspect existing
27
- generator conflicts and retain your installed migrations. Restart Rails after
28
- configuration changes. The generated initializer explicitly loads:
20
+ The generator includes the outbox and tracking setup. Keep any migrations you
21
+ have already installed. Restart Rails after configuration changes.
22
+ The generated initializer loads:
29
23
 
30
24
  ```ruby
31
25
  require "cloudflare/email/mailboxes"
32
26
  ```
33
27
 
34
28
  Loading this module enables mailbox lookup on the gem's Cloudflare ingress.
35
- Existing receiving addresses must be registered and activated before switching
36
- an existing application over. Unregistered or suspended destinations return
37
- HTTP 422 and the Worker rejects the delivery by default. You can explicitly
38
- configure a domain catch-all below when your application needs that behavior.
29
+ Register and activate each accepted address. Unregistered or suspended destinations
30
+ return HTTP 422. In the default durable Worker mode, the email stays pending at
31
+ Cloudflare until you resolve the destination. Enable a domain catch-all below if
32
+ you want unknown local parts to enter an inbox.
39
33
 
40
34
  For separate organization databases, follow the
41
35
  [activerecord-tenanted setup](activerecord-tenanted.md) **before loading the
@@ -45,6 +39,10 @@ use the same tenant connection as the mailbox tables for atomic incoming storage
45
39
 
46
40
  ## Register an organization's domain
47
41
 
42
+ For addresses such as `invoices@acme.in.example.com`, follow the
43
+ [Cloudflare domain setup guide](../templates/worker/docs/domain-setup.md).
44
+ It covers receiving DNS, Worker routing, and registering customer domains in Rails.
45
+
48
46
  Run directory management from your authorized administration/provisioning code.
49
47
  Do not expose arbitrary domain claims to customers without ownership checks.
50
48
 
@@ -144,30 +142,14 @@ suspension is an administrative update of `ReceivingDomain#state` to `suspended`
144
142
 
145
143
  Addresses use lowercase ASCII dot-atom local parts and domains. Alias addresses
146
144
  are explicit: the module does not automatically strip `+tags` or invent address
147
- fallbacks. Reserve application-specific names such as Rebulk's `tracking` in
145
+ fallbacks. Reserve application-specific names such as `tracking` in
148
146
  your mailbox-management policy before creation.
149
147
 
150
148
  ## Receive unregistered local parts with an optional catch-all
151
149
 
152
- Catch-all receiving is **off by default** and is available in the 0.3
153
- version. It lets one existing address receive otherwise unregistered addresses
154
- on its exact domain, without creating an alias row for each incoming local part.
155
- For example, `anything@acme.example.com` can arrive in an existing support mailbox.
156
- It does not cover subdomains such as `anything@other.acme.example.com`.
157
-
158
- Fresh installations include the schema. For an existing installation, generate
159
- the optional upgrade and apply it to the database containing your mailbox tables:
160
-
161
- ```sh
162
- bin/rails generate cloudflare:email:mailboxes:catch_all
163
- bin/rails db:migrate
164
- ```
165
-
166
- With separate tenant databases, pass
167
- `--tenant-migrations-path=db/tenant_migrate` and run your application's tenant
168
- migration command for every affected tenant. Updating the gem alone keeps older
169
- schemas working with exact-address routing. Enabling a catch-all without its
170
- migration raises a configuration error.
150
+ Catch-all receiving is **off by default**. It lets one inbox receive unregistered
151
+ local parts on its exact domain. For example, `anything@acme.example.com` can
152
+ arrive in your support inbox. It does not cover `anything@other.acme.example.com`.
171
153
 
172
154
  First configure and verify the domain's catch-all Email Routing rule in Cloudflare
173
155
  to send mail to your Worker. Then use your authorized provisioning code:
@@ -341,23 +323,12 @@ the same tenant connection when atomicity is required. External effects cannot
341
323
  be rolled back. Provider acceptance, shared storage and tenant storage are not
342
324
  one transaction; this is not exactly-once delivery.
343
325
 
344
- ## Isolation and rollout
345
-
346
- The scoped session API checks tenant/mailbox ownership and sender addresses.
347
- It does not authenticate Rails users. Protect admin directory APIs, choose the
348
- tenant through your access resolver, and authorize every mailbox operation.
349
- Direct/unscoped ActiveRecord access is an application-level privileged interface,
350
- particularly in shared-database mode; it is not a row-level authorization system.
351
-
352
- For database tenancy, configure all framework storage on the tenant connection
353
- and exclude ingress from hostname/session tenant selection. New framework jobs
354
- capture tenant context before serialization and restore it before GlobalID
355
- lookup. Drain existing ActionMailbox/ActiveStorage queues before enabling this
356
- mode: previously serialized jobs without the new metadata are rejected.
357
-
358
- The gem does not create organizations/users, verify customer domain ownership,
359
- provide IMAP/POP, or supply a full compose/conversation inbox. The optional
360
- [management engine](management-engine.md) supplies mailbox administration and
361
- plain-text previews using your host's access policy. The original Rebulk document-processing
362
- Worker contract and sender-review policy still need a separate application
363
- migration; installing this module does not replace that live pipeline.
326
+ ## Permissions
327
+
328
+ Choose the tenant from your authenticated application context and authorize each
329
+ mailbox operation. The scoped APIs check mailbox ownership within that tenant;
330
+ direct Active Record access requires your own permission checks.
331
+
332
+ For separate databases, use the [tenant connection guide](activerecord-tenanted.md)
333
+ to configure framework storage and jobs. Your app supplies users, sender policy,
334
+ and business processing.
@@ -1,12 +1,10 @@
1
- # Optional mailbox management engine
1
+ # Mailbox management UI
2
2
 
3
3
  The management engine is a small, server-rendered Rails interface for the
4
4
  [managed mailbox APIs](mailboxes.md). It works with ordinary Rails apps and with
5
5
  apps using Inertia, React, Turbo or another frontend. No JavaScript, Node build,
6
6
  asset pipeline or frontend package is required for the engine.
7
7
 
8
- This feature is available in RubyGems 0.3.0.
9
-
10
8
  ## What you get
11
9
 
12
10
  - Mailbox lists, names, creation and aliases on allowed receiving domains.
data/docs/outbox.md CHANGED
@@ -166,10 +166,9 @@ For a partial operation, only unresolved recipients can be reconciled. Omit `rec
166
166
 
167
167
  For a `sending` operation, first stop the original sender and verify it cannot resume. Reconciliation additionally requires `confirm_sender_stopped: true` and a claim at least 15 minutes old. Elapsed time alone is insufficient. There is no automatic lease expiry or timeout-based resend.
168
168
 
169
- ## Data ownership and migration
169
+ ## Stored data
170
170
 
171
- The gem owns its operation, recipient, receipt, and reconciliation infrastructure. The inbox owns mailbox permissions, conversations, approval policy, AI drafts, and UI. Link product records to operations by a stable key or application foreign key instead of copying the ledger algorithm.
172
-
173
- The MIME snapshot contains email bodies, addresses, and attachments. Apply your application's database access, encryption, backup, and retention policies. Removing ledgers or replaying old backups can discard uncertainty and deduplication evidence; quiesce sending and reconcile provider activity during recovery.
174
-
175
- Importing historical attempts is application-specific. If the old application did not save exact MIME, a reconstructed snapshot is historical metadata, not proof of the original bytes. Preserve its known/unknown state and do not turn imported accepted or uncertain attempts into dispatchable `prepared` operations. The generated outbox and receipt migrations refuse rollback to preserve delivery and deduplication evidence; use a forward fix or a reconciled backup.
171
+ The outbox saves message bodies, addresses, attachments, and delivery history.
172
+ Apply your application's database access, encryption, backup, and retention policies.
173
+ Keep deduplication and acceptance evidence when restoring data so a recovery
174
+ does not resend accepted mail.