mailbox-kit 0.1.0 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 186fcaea489075063aeb25ee886080ff243289eb3ec02fdfac110f9c7e5c9081
4
- data.tar.gz: 81071f06cf54c2de754bf16a056e6be93ae7dcbbca6604c3636a8e3ed19e3169
3
+ metadata.gz: 1b8650dea4b54dbd598376075edb422437abd1f64fb88aaae1b1d342f0bdd35d
4
+ data.tar.gz: 96f6170e145f22e14469cec036bdd4858ed10c2f518429fa72ef866fe197a345
5
5
  SHA512:
6
- metadata.gz: 6df3f4be7cd2b1b07eebbd164a609c4fa488572b2bed5c65312e1363e4c21c04312ad11d3b6cf73cf636ff0893dc7d4b066a31f7f842beb1d1a829819db783ef
7
- data.tar.gz: ba69a55126f0a291dc21296230532c4c575a183d45f49c37dbeeb4295762847a43c47b7fa091c8e835b2b0cd3bdbf87fd697baebd9244ff52345d46795997df5
6
+ metadata.gz: 1f29ae48e734ffbdfb5ef3074188c9189eb666eecc84644094439c698efe507753d0af829b41f2df7b7ee399f1cec50ed164bfc4e236bcfe484e02cac847948a
7
+ data.tar.gz: fbb0c980266f89fa4e96550de66982afa323a8958d3cfce1c4b950d0248385dc07144f0d52c16c98355f35ee367479add6de70351766e179a33100698dcf4043
data/README.md CHANGED
@@ -7,22 +7,14 @@ and access control. SQLite works out of the box; database tenancy is opt-in.
7
7
  Rails already stores raw email, parses it, routes it to handlers, and runs
8
8
  processing jobs. Mailbox Kit adds the inbox your users interact with.
9
9
 
10
- ## Choose your setup
10
+ ## Install
11
11
 
12
- - **Cloudflare receiving or sending:** follow the [Cloudflare Rails guide](https://github.com/cole-robertson/cloudflare-email/blob/main/docs/getting-started.md).
13
- It includes this core; use the Cloudflare installer.
14
- - **An existing Action Mailbox integration:** install this core and attach received
15
- email to inboxes. Follow the example below.
16
- - **Only processing incoming email:** Action Mailbox alone may be enough.
17
-
18
- Add Mailbox Kit 0.1 to your Gemfile:
12
+ For a Rails app using another inbound provider:
19
13
 
20
14
  ```ruby
21
15
  gem "mailbox-kit", "~> 0.1.0"
22
16
  ```
23
17
 
24
- For a new Rails 7.2–8.1 application:
25
-
26
18
  ```sh
27
19
  bundle install
28
20
  bin/rails action_mailbox:install
@@ -30,8 +22,10 @@ bin/rails generate mailbox_kit:install
30
22
  bin/rails db:migrate
31
23
  ```
32
24
 
33
- Already using Cloudflare mailboxes? Follow the [upgrade guide](docs/upgrading.md)
34
- instead of creating the tables again.
25
+ Skip `action_mailbox:install` if Action Mailbox is already configured.
26
+
27
+ For Cloudflare, use the [Cloudflare mailbox guide](https://github.com/cole-robertson/cloudflare-email/blob/main/docs/mailboxes.md)
28
+ instead. Cloudflare Email installs Mailbox Kit automatically.
35
29
 
36
30
  ## Create an inbox in code
37
31
 
@@ -81,24 +75,11 @@ The optional management engine provides a server-rendered UI with no frontend
81
75
  build. Your application supplies authentication and authorizes each action;
82
76
  access is denied until configured. See [management setup](docs/integration.md#management-interface).
83
77
 
84
- ## The split
85
-
86
- | Layer | Owns |
87
- | --- | --- |
88
- | Rails | Raw MIME, Active Storage, parsing, routing, processing jobs/status, cleanup policy, Action Mailer |
89
- | Mailbox Kit | Inbox identities, addresses/aliases, memberships, read/archive state, selective retention, optional tenant context, management UI |
90
- | Cloudflare | Request verification, authoritative envelope metadata, Worker retries/storage, sending, delivery feedback, provisioning |
91
- | Your app | Users/organizations/sites, ownership lifecycle, authorization, sender acceptance, business effects |
92
-
93
- Inbound and outbound can use different providers through Rails. The core does
94
- not send email or supply a universal outbound adapter registry. `owner_ref` is an
95
- application-managed reference; it is not an automatic `has_mailbox` association
96
- or permission grant.
78
+ ## More features
97
79
 
98
- Inbox-associated messages survive Rails' automatic incineration. For retaining
99
- all inbound messages, Rails already offers `config.action_mailbox.incinerate = false`.
100
- Raw storage and processing remain Rails responsibilities.
80
+ Use the [integration guide](docs/integration.md) for aliases, catch-all addresses,
81
+ retention, authentication, and separate tenant databases.
101
82
 
102
- See the [integration guide](docs/integration.md) for aliases, catch-all addresses,
103
- verified source ingestion, duplicates, retention, authorization and optional
104
- database tenancy. See [upgrading](docs/upgrading.md) for existing installations.
83
+ Your app supplies users, organizations, sender policy, and mailbox permissions.
84
+ `owner_ref` stores your reference to an application record; it does not grant access.
85
+ Inbound and outbound email can use different providers through Rails.
data/docs/integration.md CHANGED
@@ -6,29 +6,9 @@ configurable retention. Mailbox Kit adds inbox identities, addresses/aliases,
6
6
  membership, read/archive state, scoped access and a server-rendered management UI.
7
7
  SQLite works; separate tenant databases are opt-in.
8
8
 
9
- This package is maintained alongside `cloudflare-email` so changes to the core and
10
- its first integration can be tested together. It is not yet published. In this
11
- checkout use `gem "mailbox-kit", path: "mailbox-kit"`. Once released, applications
12
- can use `gem "mailbox-kit", "~> 0.1"`.
13
-
14
- ## What belongs where?
15
-
16
- | Layer | Responsibility |
17
- | --- | --- |
18
- | Mailbox Kit | Inbox identities, addresses, recipient lookup, membership, read/archive/purge, selective retention, tenant context, management UI |
19
- | Rails | InboundEmail records, original MIME/ActiveStorage, parsing, processing callbacks/status, routing jobs, configurable retention, and ActionMailer |
20
- | Provider integration | Verify incoming requests and envelope recipients, deliver outgoing messages, authenticate delivery feedback, configure DNS/routes |
21
- | Your application | Users, organizations, sites, permissions, sender acceptance and business workflows |
22
-
23
- Mailbox Kit does not require Cloudflare, configure DNS, or send messages by itself.
24
- `cloudflare-email` supplies its existing Worker ingress, sending/outbox, feedback,
25
- and provisioning integration. Other providers can call the core receiving APIs;
26
- this release does not claim complete SES, Postmark, or generic outbound adapters.
27
- The tests exercise Rails' stock Postmark HTTP ingress followed by explicit kit
28
- attachment in one database; provider setup, dynamic envelope authorization and
29
- provider outage behavior are separate integration responsibilities.
30
- Inbound and outbound need not use the same service. A mailbox has no `provider`
31
- attribute: receiving through one provider does not authorize sending through it.
9
+ Install `gem "mailbox-kit", "~> 0.1.0"`, or use the
10
+ [Cloudflare mailbox setup](https://github.com/cole-robertson/cloudflare-email/blob/main/docs/mailboxes.md)
11
+ when Cloudflare is your provider.
32
12
 
33
13
  ## Create an inbox
34
14
 
@@ -63,20 +43,13 @@ end
63
43
  tenancy. Domain/address activation records evidence supplied by your trusted
64
44
  application. It does not verify DNS or grant permission to send from that address.
65
45
 
66
- `owner_ref` is currently an application-managed reference, not a foreign key or an
67
- authorization grant. Use stable identities, rather than reusable numeric IDs.
68
- Automatic `has_mailbox` owner bindings and deletion reconciliation are a separate
69
- follow-up; this extraction does not introduce a callback that could silently
70
- reassign an old address to a new owner.
46
+ `owner_ref` stores your application's reference to a user, team, or organization.
47
+ Use a stable identity and check access in your application.
71
48
 
72
- ## Reuse Action Mailbox
49
+ ## Attach an Action Mailbox record
73
50
 
74
- For a receive-and-process application, Action Mailbox alone may be enough. It
75
- already supports dynamic handlers through regex/callable routes, processing
76
- callbacks, test helpers, and the development conductor at
77
- `/rails/conductor/action_mailbox/inbound_emails`. The kit does not replace those.
78
- Rails' `delivered` status means an inbound handler finished processing; read and
79
- archive state belong to the kit's inbox membership instead.
51
+ Use ordinary `ApplicationMailbox` handlers for processing. Rails' `delivered`
52
+ status means processing finished; read and archive state belong to the inbox.
80
53
 
81
54
  If an existing Rails ingress has already stored an email, attach that record:
82
55
 
@@ -127,7 +100,7 @@ The adapter remains responsible for authentication, authoritative envelope
127
100
  recipients, delivery-specific identity where necessary, and retrying failed
128
101
  requests. ActionMailbox still owns processing and its `ApplicationMailbox` routes.
129
102
 
130
- For application checks before persistence, the existing block form remains:
103
+ To check application policy before saving, use a block:
131
104
 
132
105
  ```ruby
133
106
  require "mailbox_kit/inbound_email"
@@ -140,12 +113,11 @@ end
140
113
  Blocks must return the existing Rails record on duplicates if membership should
141
114
  be attached. Rails' bare `create_and_extract_message_id!` returns `nil` for a
142
115
  duplicate, so using it directly in that block can omit a second inbox membership.
143
- Returning `nil` intentionally skips membership, preserving the older block API.
116
+ Returning `nil` skips membership.
144
117
  Neither a database transaction nor source deduplication guarantees exactly-once
145
118
  business effects, external blob cleanup on rollback, or recovery of lost jobs.
146
119
 
147
- With Cloudflare, the adapter preserves its existing authenticated envelope and
148
- metadata-based delivery identity, then uses the same Rails persistence bridge:
120
+ A custom Cloudflare endpoint can verify and receive in two steps:
149
121
 
150
122
  ```ruby
151
123
  verified = Cloudflare::Email::Ingress.verify(
@@ -158,8 +130,8 @@ verified.message.receive_into_mailbox! if verified.status == :ok
158
130
  The shipped Cloudflare ingress controller already does this, including HTTP error
159
131
  handling and size limits. Installing the core does not add another HTTP endpoint
160
132
  or SMTP server. `persist_action_mailbox!` remains available for applications that
161
- only need Rails storage; its existing return convention is new record or `nil`
162
- on duplicate. The new inbox bridge can repair membership on duplicate delivery
133
+ only need Rails storage; it returns the new record or `nil`
134
+ on duplicate. The inbox bridge can repair membership on duplicate delivery
163
135
  without rerouting the email or replacing stored authentication metadata.
164
136
 
165
137
  For an application with its own persistence, use
@@ -254,30 +226,9 @@ Never choose the tenant from request parameters. Rails framework jobs preserve t
254
226
  explicit context; your own tenant jobs can `prepend MailboxKit::TenantJobContext`
255
227
  after requiring `mailbox_kit/tenant_job_context`.
256
228
 
257
- ## Existing Cloudflare Email applications
258
-
259
- Keep your existing requires, initializer, migrations, Worker and mounted engine.
260
- Updating the Cloudflare gem brings in this core as a dependency. Public Cloudflare
261
- constants resolve to the same core models; tables, IDs and tenant job payload keys
262
- are retained. The `cloudflare_email_` table prefix is intentionally unchanged.
263
- Do **not** run `mailbox_kit:install` over an existing Cloudflare mailbox schema.
264
-
265
- For a fresh Cloudflare application, continue using the Cloudflare mailbox generator,
266
- which also installs its outbox and event tables. A core-only installation should
267
- not run both installers; adding Cloudflare outbound later needs its additional
268
- tables and explicit sending-account/domain configuration. The receiving-only core
269
- does not automatically become a Cloudflare sending account when the adapter loads.
270
-
271
229
  ## Sending from customer subdomains
272
230
 
273
231
  Cloudflare onboards each sending domain/subdomain separately. Parent-domain
274
232
  verification and wildcard receiving do not authorize arbitrary From subdomains.
275
233
  Use an explicitly verified sending domain and, where useful, a customer-specific
276
234
  inbound Reply-To. See [Cloudflare's subdomain rules](https://developers.cloudflare.com/email-service/configuration/subdomains/).
277
-
278
- ## Development and release
279
-
280
- From the repository root, `bundle exec rake test` exercises both the compatibility
281
- API and standalone core. `bundle exec ruby script/verify_package.rb` builds both
282
- archives, installs a Rails-free consumer, and tests the packaged Rails integrations.
283
- Publish `mailbox-kit` before releasing a Cloudflare version that depends on it.
@@ -3,12 +3,31 @@ require "mailbox_kit/tenancy"
3
3
 
4
4
  module MailboxKit
5
5
  module ActiveRecord
6
- # Rails may inspect every model's pool while preloading schema metadata
7
- # before any request has selected a tenant. Report an unavailable
8
- # connection using Rails' error hierarchy so boot can recover, while
9
- # retaining the same fail-closed guard for every caller.
10
6
  class TenantConnectionUnavailable < ::ActiveRecord::ConnectionNotEstablished; end
11
7
 
8
+ # Returned instead of a pool while no valid tenant context is selected.
9
+ # Rails' production boot asks every model's pool for its schema reflection
10
+ # before any tenant exists (railtie "active_record.define_attribute_methods");
11
+ # raising there aborts that loop for every model still to come. This answers
12
+ # only that probe, with an empty reflection, and raises for anything else.
13
+ class UnavailablePool
14
+ def initialize(error)
15
+ @error = error
16
+ end
17
+
18
+ def schema_reflection
19
+ ::ActiveRecord::ConnectionAdapters::SchemaReflection.new(nil)
20
+ end
21
+
22
+ def method_missing(*)
23
+ raise TenantConnectionUnavailable, @error.message, cause: @error
24
+ end
25
+
26
+ def respond_to_missing?(*)
27
+ false
28
+ end
29
+ end
30
+
12
31
  # Uses the host's abstract tenant connection owner when explicitly configured.
13
32
  class Base < Tenancy.model_base(::ActiveRecord::Base)
14
33
  self.abstract_class = true
@@ -19,7 +38,7 @@ module MailboxKit
19
38
  begin
20
39
  Tenancy.require_context!
21
40
  rescue ConfigurationError => error
22
- raise TenantConnectionUnavailable, error.message
41
+ return UnavailablePool.new(error)
23
42
  end
24
43
  end
25
44
  super
@@ -1,15 +1,30 @@
1
1
  require "mailbox-kit"
2
2
  require "digest"
3
+ require "stringio"
3
4
 
4
5
  module MailboxKit
5
6
  # A small bridge to Rails' existing storage and routing lifecycle. It returns
6
7
  # the existing record on duplicate delivery so callers can attach memberships.
8
+ #
9
+ # The raw source is uploaded BEFORE any transaction this module opens. A
10
+ # database transaction can hold a write lock (SQLite's from BEGIN when
11
+ # transactions are immediate), so a storage round trip inside one stalls
12
+ # every other writer. Call .prepare outside the caller's transaction too
13
+ # (Mailboxes.receive does) and only rows are written while it is open.
7
14
  module InboundEmail
8
15
  Result = Struct.new(:record, :created, keyword_init: true) do
9
16
  def created? = created
10
17
  end
11
18
 
12
19
  def self.persist(source:, message_id: nil, message_checksum: nil, metadata: {})
20
+ prepare(source: source, message_id: message_id, message_checksum: message_checksum,
21
+ metadata: metadata).persist
22
+ end
23
+
24
+ # Resolves the identity and, unless a record already has it, uploads the raw
25
+ # email. Call in the tenant that will own the record, outside any
26
+ # transaction; then call #persist on the result.
27
+ def self.prepare(source:, message_id: nil, message_checksum: nil, metadata: {})
13
28
  unless defined?(::ActionMailbox::InboundEmail)
14
29
  raise ConfigurationError, "load ActionMailbox before persisting inbound mail"
15
30
  end
@@ -33,24 +48,72 @@ module MailboxKit
33
48
  end
34
49
  identity = { message_id: message_id || "#{checksum}@mailbox-kit.invalid",
35
50
  message_checksum: checksum }
36
- model = ::ActionMailbox::InboundEmail
37
- model.transaction do
38
- if (existing = model.find_by(identity))
39
- next Result.new(record: existing, created: false).freeze
40
- end
41
- # Rails returns nil on a unique conflict. Roll back that savepoint before
42
- # looking up the winner, including on PostgreSQL's aborted transactions.
43
- record = model.transaction(requires_new: true) do
44
- created = model.create_and_extract_message_id!(source, **identity)
45
- raise ::ActiveRecord::Rollback unless created
46
- unless metadata.empty?
47
- blob = created.raw_email.blob
48
- blob.update!(metadata: blob.metadata.merge(metadata))
51
+ Prepared.new(source, identity, metadata).tap(&:upload)
52
+ end
53
+
54
+ class Prepared
55
+ # The tenant the bytes were uploaded under, for a #discard run later.
56
+ attr_reader :tenant_key
57
+
58
+ def initialize(source, identity, metadata)
59
+ @source, @identity, @metadata = source, identity, metadata
60
+ @tenant_key = Tenancy.current_key if defined?(Tenancy)
61
+ end
62
+
63
+ # Uploads the bytes unless a record with this identity already exists.
64
+ def upload
65
+ return if @blob || model.uncached { model.exists?(@identity) }
66
+
67
+ blob = ::ActiveStorage::Blob.new(filename: "message.eml", content_type: "message/rfc822",
68
+ service_name: ::ActionMailbox.storage_service, metadata: @metadata.to_h)
69
+ io = StringIO.new(@source)
70
+ blob.unfurl(io)
71
+ blob.upload_without_unfurling(io)
72
+ @blob = blob
73
+ end
74
+
75
+ # Writes the record and its blob row, or returns the record a concurrent
76
+ # delivery already wrote. Uploaded bytes no committed row will name are
77
+ # deleted once no transaction is open.
78
+ def persist
79
+ result = model.transaction do
80
+ # Uncached: a request's query cache would repeat #upload's answer.
81
+ if (existing = model.uncached { model.find_by(@identity) })
82
+ next Result.new(record: existing, created: false).freeze
49
83
  end
50
- created
84
+ # Rare: a record existed when #upload looked and is gone now.
85
+ upload
86
+ # Rails returns nil on a unique conflict. Roll back that savepoint before
87
+ # looking up the winner, including on PostgreSQL's aborted transactions.
88
+ record = model.transaction(requires_new: true) do
89
+ model.create!(raw_email: @blob, **@identity)
90
+ rescue ::ActiveRecord::RecordNotUnique
91
+ raise ::ActiveRecord::Rollback
92
+ end
93
+ Result.new(record: record || model.uncached { model.find_by!(@identity) }, created: !record.nil?).freeze
51
94
  end
52
- Result.new(record: record || model.find_by!(identity), created: !record.nil?).freeze
95
+ discard unless result.created?
96
+ result
97
+ rescue StandardError
98
+ discard unless model.connection.transaction_open?
99
+ raise
100
+ end
101
+
102
+ # Deletes the uploaded bytes unless a committed row names them. Run after
103
+ # the transaction that would have committed that row has finished.
104
+ def discard
105
+ return unless @blob
106
+ return if ::ActiveStorage::Blob.uncached { ::ActiveStorage::Blob.exists?(key: @blob.key) }
107
+
108
+ @blob.service.delete(@blob.key)
109
+ @blob = nil
110
+ rescue StandardError => error
111
+ warn "mailbox-kit: raw email #{@blob&.key} not deleted: #{error.class}: #{error.message}"
53
112
  end
113
+
114
+ private
115
+
116
+ def model = ::ActionMailbox::InboundEmail
54
117
  end
55
118
  end
56
119
  end
@@ -39,15 +39,26 @@ module MailboxKit
39
39
  raise ArgumentError, "provide source or a persistence block, not both" if source && block
40
40
  raise ArgumentError, "source or persistence block required" unless source || block
41
41
  in_recipient_tenant(recipient) do |session, address, directory|
42
- session.receive(address, directory) do |destination|
43
- if block
44
- # Legacy custom persistence blocks must return the existing Rails
45
- # record on duplicates when membership attachment is desired.
46
- block.lambda? && block.arity.zero? ? block.call : block.call(destination)
47
- else
48
- require "mailbox_kit/inbound_email"
49
- InboundEmail.persist(source: source).record
42
+ if source
43
+ # Upload before session.receive opens the membership transaction,
44
+ # and only for a destination that can accept it.
45
+ session.with_recipient(address, directory) { nil }
46
+ require "mailbox_kit/inbound_email"
47
+ prepared = InboundEmail.prepare(source: source)
48
+ end
49
+ begin
50
+ session.receive(address, directory) do |destination|
51
+ if block
52
+ # Legacy custom persistence blocks must return the existing Rails
53
+ # record on duplicates when membership attachment is desired.
54
+ block.lambda? && block.arity.zero? ? block.call : block.call(destination)
55
+ else
56
+ prepared.persist.record
57
+ end
50
58
  end
59
+ ensure
60
+ # The transaction has finished: delete the upload if it rolled back.
61
+ prepared&.discard
51
62
  end
52
63
  end
53
64
  end
@@ -1,3 +1,3 @@
1
1
  module MailboxKit
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailbox-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cole
@@ -27,7 +27,6 @@ files:
27
27
  - app/views/mailbox_kit/management/mailboxes/message.html.erb
28
28
  - app/views/mailbox_kit/management/mailboxes/show.html.erb
29
29
  - docs/integration.md
30
- - docs/upgrading.md
31
30
  - lib/generators/mailbox_kit/install/install_generator.rb
32
31
  - lib/generators/mailbox_kit/install/templates/create_mailbox_kit_mailboxes.rb
33
32
  - lib/generators/mailbox_kit/install/templates/create_mailbox_kit_receiving_domains.rb
data/docs/upgrading.md DELETED
@@ -1,60 +0,0 @@
1
- # Upgrading existing mailbox installations
2
-
3
- The extracted core keeps existing `cloudflare_email_` tables, record IDs, public
4
- Cloudflare constants and tenant job payloads. Keep your initializer, mounted
5
- engine, Worker and application processing code. Do not run `mailbox_kit:install`
6
- or the Cloudflare mailbox installer over existing mailbox tables.
7
-
8
- After updating the gems, generate the index and receiving-domain migrations:
9
-
10
- ```sh
11
- bin/rails generate mailbox_kit:upgrade
12
- bin/rails db:migrate
13
- ```
14
-
15
- For separate tenant databases:
16
-
17
- ```sh
18
- bin/rails generate mailbox_kit:upgrade --tenant-migrations-path=db/tenant_migrate --directory-migrations-path=db/migrate
19
- ```
20
-
21
- Apply the index migration to every database containing mailbox messages using your
22
- application's tenant migration runner. It does not belong in the shared domain
23
- directory database. The migration preserves messages and skips an existing
24
- single-column inbound index. New installers already include this index.
25
-
26
- Apply `AllowProviderNeutralReceivingDomains` to the shared directory database.
27
- It allows a null receiving-domain account ID, preserving existing account values.
28
- Receiving-only registration requires no Cloudflare account; sending still requires
29
- an explicit account and verified sending domain. On SQLite this schema change can
30
- rebuild the directory table, so use your normal migration window.
31
-
32
- Index creation can block writes on large tables. Plan its application according
33
- to your database's normal migration procedure; PostgreSQL installations needing
34
- online builds can adapt it to `algorithm: :concurrently` with
35
- `disable_ddl_transaction!`.
36
-
37
- ## Verify the application upgrade
38
-
39
- 1. Receive a message through the existing authenticated provider integration.
40
- 2. Confirm it appears in the right inbox and runs the existing business handler.
41
- 3. Replay the delivery and confirm no duplicate business effects.
42
- 4. Check authorized and unauthorized UI access, read/archive actions, and retention.
43
- 5. For separate databases, exercise tenant routing and processing jobs in two tenants.
44
-
45
- Keep your application's sender acceptance, ownership reconciliation and processing
46
- claims. Rails processing status does not replace these business policies.
47
-
48
- ## Adding Cloudflare to a core-only application
49
-
50
- The core's mailbox schema alone does not supply Cloudflare's outbox and event
51
- tables. Add those provider tables and sending configuration explicitly using the
52
- Cloudflare integration guide; do not run both complete installers against the
53
- same database. Arbitrary subdomain receiving does not authorize sending from those
54
- subdomains: verify each Cloudflare sending domain separately.
55
-
56
- ## Versions
57
-
58
- Cloudflare Email 0.4.0 depends on Mailbox Kit 0.1.x and installs it automatically.
59
- Core-only applications can install `gem "mailbox-kit", "~> 0.1.0"` directly.
60
- Upgrade and verify your application's email flows before a wider rollout.