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,175 +0,0 @@
|
|
|
1
|
-
# Verification report — 2026-09-10
|
|
2
|
-
|
|
3
|
-
## Result
|
|
4
|
-
|
|
5
|
-
The local automated checks pass after fixing a defect found only in the actual Workers runtime. This report records the initial local-only phase. A subsequent [live verification pass](2026-09-10-live.md) verified isolated sending, deployed ingress, replies, real LLM processing, and delivery-event redelivery/acknowledgement; it found and fixed a queue decoder bug and confirmed custom Message-IDs are replaced. See that report for current counts and remaining limits.
|
|
6
|
-
|
|
7
|
-
**Rebulk does not currently dogfood this gem.** Its existing email tests pass, but a test-only switch to the gem exposes four migration blockers. Do not treat those characterization results as production acceptance.
|
|
8
|
-
|
|
9
|
-
**Additional dogfooding app located and tested:** `cole-robertson/agentic-inbox-rails-full`
|
|
10
|
-
is the Rails/Inertia inbox prototype built in April. [PR #26](https://github.com/cole-robertson/agentic-inbox-rails-full/pull/26)
|
|
11
|
-
initially pinned this gem's 0.2.0 candidate at `a1f80d8` and passed 140 local Rails examples,
|
|
12
|
-
including signed ingress → triage/draft → UI send approval → provider acceptance →
|
|
13
|
-
reply threading. Its copied Worker passes nine tests and a dry-run build. See its
|
|
14
|
-
[application verification report](https://github.com/cole-robertson/agentic-inbox-rails-full/blob/maintenance/cloudflare-email-0.2-dogfood/docs/verification/2026-09-10.md)
|
|
15
|
-
for fixes, existing app maintenance failures, and remaining live-verification limits.
|
|
16
|
-
The smaller `agentic-inbox-rails` demo remains on 0.1.0 and was not changed.
|
|
17
|
-
|
|
18
|
-
Scope and source snapshots:
|
|
19
|
-
|
|
20
|
-
- Gem implementation baseline: [9d1cd58](https://github.com/cole-robertson/cloudflare-email/commit/9d1cd581489690c5befbfaadc7f88f3535a3435f), plus this report's accompanying runtime fix and verification scripts.
|
|
21
|
-
- Rebulk: [46db134](https://github.com/Rebulk/rebulk-system/commit/46db134e2722568d09fc1c4139266a937a03eaef), checked out separately for testing. No tracked Rebulk files changed.
|
|
22
|
-
- Ruby 3.4.1 / Rails 8.1.3.1 for the final gem run; Ruby 3.4.5 / Rails 8.1.3.1 for Rebulk. Local runtime: Node 22.23.1, Wrangler 4.131.0 and its bundled workerd.
|
|
23
|
-
|
|
24
|
-
## Evidence and results
|
|
25
|
-
|
|
26
|
-
| Layer | Result | What it establishes |
|
|
27
|
-
|---|---|---|
|
|
28
|
-
| Gem suite | **192 tests, 577 assertions, zero failures/errors/skips** | Sending/MIME, retry/error contracts, response outcomes, signing, routing, tasks, events, and three subprocess Rails fixtures |
|
|
29
|
-
| Rails fixture detail | **40 nested tests** across inbound, send-only, and fresh-inbound apps | Actual boot, generators, migrations, credentials, persistence, duplicate acknowledgement, default mailbox routing/job execution, and task/dev behavior; these run inside the three suite wrapper tests |
|
|
30
|
-
| Actual local workerd → Rails | **16 checks passed** | Runtime interoperability, raw MIME/binary integrity, duplicate acceptance, mailbox processing, misconfiguration rejection, redirects, and request timeout |
|
|
31
|
-
| Worker Node tests | **9 tests passed** | Unit behavior, including explicit 302 response handling; complementary to workerd |
|
|
32
|
-
| Packaged distribution | **Isolated Ruby consumer passed; 40 Rails tests / 179 assertions passed** | Build/install of the actual .gem, Base64 dependency without Rails, and all three Rails fixture modes against packaged files |
|
|
33
|
-
| Expanded CI | **[13/13 jobs passed on 4b968f6](https://github.com/cole-robertson/cloudflare-email/actions/runs/34515534609)** | Eleven Ruby/Rails combinations, packaged installation, Worker unit/build/audit, and actual local workerd → Rails |
|
|
34
|
-
| Rebulk existing email suite | **437 examples in 22 files; zero failures/pending/errors outside examples** | Current SMTP-oriented mailers, custom inbound controller/COA flow, email records, retention, retries, and tracking |
|
|
35
|
-
| Rebulk adapter characterization | **6 examples reproduced expected observations** | Actual UserMailer and CSV ReportMailer can use the adapter in a test process; four production migration gaps below remain |
|
|
36
|
-
| Public DNS read | Completed | Apex MX is Google; mail/in/agent/test subdomains resolve to Cloudflare MX. This does not establish Worker route or token configuration |
|
|
37
|
-
|
|
38
|
-
The gem suite's 192 top-level tests include three process-wrapper tests; the 40 nested Rails tests are reported separately to avoid implying they are counted by Minitest's top-level summary.
|
|
39
|
-
|
|
40
|
-
### Runtime defect found and fixed
|
|
41
|
-
|
|
42
|
-
The April-to-0.2 update used `fetch(..., { redirect: "error" })`. Node accepted it, so the unit tests and dry-run bundle were green. Actual workerd rejected that option before contacting Rails.
|
|
43
|
-
|
|
44
|
-
The Worker now uses `redirect: "manual"` and rejects the returned 3xx response through its non-2xx path. The new runtime test proves a 302 target is never contacted.
|
|
45
|
-
|
|
46
|
-
The same test:
|
|
47
|
-
|
|
48
|
-
- Sends a synthetic multipart message through the real local email simulator.
|
|
49
|
-
- Compares the stored raw RFC822 bytes exactly.
|
|
50
|
-
- Verifies an attachment containing all 256 byte values plus binary terminators.
|
|
51
|
-
- Checks a repeated message stores only one record.
|
|
52
|
-
- Executes the real ActionMailbox routing job and checks the mailbox receives the attachment.
|
|
53
|
-
- Checks wrong/missing shared-secret configurations do not store mail.
|
|
54
|
-
- Measures the actual abort of a stalled local ingress: **15.08 seconds** in the final run.
|
|
55
|
-
|
|
56
|
-
This test uses loopback HTTP and temporary storage. It does not exercise Cloudflare's managed SMTP service, deployed Workers, DNS routing, or sender-provider behavior.
|
|
57
|
-
|
|
58
|
-
## Was there an old test report?
|
|
59
|
-
|
|
60
|
-
I found meaningful historical tests, but no preserved, sanitized live-results report:
|
|
61
|
-
|
|
62
|
-
- Local `2026-04-16-cloudflare-mailer/trial/test/integration/` contains **20 Rails integration tests** across credentials, inbound, instrumentation, outbound, and routing. Outbound uses WebMock; inbound uses local signed requests. These are not live Cloudflare tests.
|
|
63
|
-
- `trial2/app/mailboxes/main_mailbox.rb` contains the signed-Message-ID reply experiment. No reproducible live round-trip assertion script was found alongside it.
|
|
64
|
-
- Both `trial/` and `trial2/` are gitignored and untracked; they were never part of repository CI.
|
|
65
|
-
- The original changelog/README claim live end-to-end testing and a 191-character signed Message-ID. Those are historical assertions, not current verification evidence.
|
|
66
|
-
- A deleted historical `PLAN.md` was a design/checklist, not a results report.
|
|
67
|
-
|
|
68
|
-
This report and the scripts below are tracked so the current evidence can be rerun.
|
|
69
|
-
|
|
70
|
-
## Rebulk's actual integration
|
|
71
|
-
|
|
72
|
-
At the pinned Rebulk commit:
|
|
73
|
-
|
|
74
|
-
| Area | Current implementation |
|
|
75
|
-
|---|---|
|
|
76
|
-
| Dependency | No `cloudflare-email` entry in Gemfile or Gemfile.lock |
|
|
77
|
-
| Outbound | `:smtp` in [production.rb](https://github.com/Rebulk/rebulk-system/blob/46db134e2722568d09fc1c4139266a937a03eaef/config/environments/production.rb#L72), defaulting to AWS SES; deployment environment can override SMTP settings |
|
|
78
|
-
| Inbound | Custom [email-worker.js](https://github.com/Rebulk/rebulk-system/blob/46db134e2722568d09fc1c4139266a937a03eaef/cloudflare/email-worker.js) → Basic-authenticated `/api/v1/ingest/email` |
|
|
79
|
-
| Routing/business behavior | Org/site routing, sender-authentication metadata, BOL/COA processing, raw R2 archive, ops recovery and backend dispatch |
|
|
80
|
-
| Gem experiments | [cloudflare/README.md](https://github.com/Rebulk/rebulk-system/blob/46db134e2722568d09fc1c4139266a937a03eaef/cloudflare/README.md) distinguishes literal routes under in/agent/test.rebulk.com as separate gem experiments |
|
|
81
|
-
|
|
82
|
-
The gem's HMAC ActionMailbox ingress is not a drop-in replacement for Rebulk's custom endpoint. Its existing R2 archive, tenant/site dispatch, authenticated-sender policy, recovery behavior, and BOL/COA processing would need an explicit integration design.
|
|
83
|
-
|
|
84
|
-
No tests referencing Rebulk's Worker source were found in its tracked tree. The 437-example baseline exercises its Rails side; it does not establish that its deployed Worker matches the checked-in source.
|
|
85
|
-
|
|
86
|
-
### Rebulk migration blockers demonstrated locally
|
|
87
|
-
|
|
88
|
-
The [portable adapter probe](../../script/rebulk_outbound_probe_spec.rb) loads the gem only into a test process, uses real Rebulk mailers/observers and isolated test databases, and mocks every Cloudflare request.
|
|
89
|
-
|
|
90
|
-
1. **Provider identity/correlation is wrong.** The preflight CommunicationMessage retains its original local Message-ID after the adapter receives a provider ID. Its provider remains SES/SMTP. The observer finds the record through an internal header, but does not update provider identity. See [CommunicationsEmailTracker](https://github.com/Rebulk/rebulk-system/blob/46db134e2722568d09fc1c4139266a937a03eaef/app/mailers/communications_email_tracker.rb) and [Recorder](https://github.com/Rebulk/rebulk-system/blob/46db134e2722568d09fc1c4139266a937a03eaef/app/services/communications/recorder.rb).
|
|
91
|
-
2. **All-bounced/all-suppressed reports become “sent.”** A normal HTTP-success response is sufficient for Rebulk's observers to mark the report sent and its communication accepted, even when no recipient was delivered or queued. Integrate the per-recipient response/event outcomes before using this provider. See [ReportEmailDeliveryObserver](https://github.com/Rebulk/rebulk-system/blob/46db134e2722568d09fc1c4139266a937a03eaef/app/mailers/report_email_delivery_observer.rb).
|
|
92
|
-
3. **Ambiguous failures lose their special treatment.** Rebulk's [ReportMailer](https://github.com/Rebulk/rebulk-system/blob/46db134e2722568d09fc1c4139266a937a03eaef/app/mailers/report_mailer.rb#L1805) recognizes raw network exception classes. The gem wraps these in `Cloudflare::Email::NetworkError`, so a read timeout moves its report claim back to queued rather than preserving the existing unknown-outcome state.
|
|
93
|
-
4. **Job retries are SMTP-specific.** An exhausted `Cloudflare::Email::RateLimitError` is re-raised without scheduling an application retry. The [retry initializer](https://github.com/Rebulk/rebulk-system/blob/46db134e2722568d09fc1c4139266a937a03eaef/config/initializers/mailer_retry.rb) needs a deliberate Cloudflare policy that distinguishes pre-send/retryable errors from ambiguous delivery.
|
|
94
|
-
|
|
95
|
-
The probe's green result means it reproduced these observations, **not** that a migration passes acceptance tests. Production dependencies/configuration were not changed.
|
|
96
|
-
|
|
97
|
-
## Reproduction
|
|
98
|
-
|
|
99
|
-
### Gem suite and package
|
|
100
|
-
|
|
101
|
-
From this gem checkout:
|
|
102
|
-
|
|
103
|
-
```sh
|
|
104
|
-
bundle install
|
|
105
|
-
bundle exec rake test
|
|
106
|
-
bundle exec ruby script/verify_package.rb
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
The package script builds a temporary .gem, installs it and Base64 into an isolated consumer bundle from local gem caches, then runs the Rails fixtures against extracted packaged files. It requires a preceding `bundle install` and does not publish anything.
|
|
110
|
-
|
|
111
|
-
### Actual Workers runtime
|
|
112
|
-
|
|
113
|
-
```sh
|
|
114
|
-
# Node 22+ required
|
|
115
|
-
npm ci --prefix templates/worker
|
|
116
|
-
npm test --prefix templates/worker
|
|
117
|
-
BUNDLE_GEMFILE=gemfiles/local_ingress.gemfile bundle install
|
|
118
|
-
BUNDLE_GEMFILE=gemfiles/local_ingress.gemfile bundle exec ruby script/verify_local_ingress.rb
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Set `NODE_BINARY=/absolute/path/to/node` if your PATH points to an older Node. The final local run used the Node 22.23.1 executable explicitly because this host's npm wrapper otherwise selected Node 20.
|
|
122
|
-
|
|
123
|
-
### Rebulk baseline
|
|
124
|
-
|
|
125
|
-
Use a separate checkout of the pinned Rebulk commit, never the shared primary clone. With dependencies installed:
|
|
126
|
-
|
|
127
|
-
```sh
|
|
128
|
-
env -u DATABASE_URL -u RAILS_MASTER_KEY RAILS_ENV=test \
|
|
129
|
-
NIGHTRAIL_TOKEN= LANTERN_TOKEN= SECRET_KEY_BASE_DUMMY=1 \
|
|
130
|
-
bundle exec rails db:test:prepare
|
|
131
|
-
|
|
132
|
-
env -u DATABASE_URL -u RAILS_MASTER_KEY RAILS_ENV=test \
|
|
133
|
-
NIGHTRAIL_TOKEN= LANTERN_TOKEN= SECRET_KEY_BASE_DUMMY=1 \
|
|
134
|
-
bundle exec rspec spec/mailers \
|
|
135
|
-
spec/jobs/mail_delivery_retry_spec.rb \
|
|
136
|
-
spec/jobs/prune_inbound_emails_job_spec.rb \
|
|
137
|
-
spec/tasks/retry_failed_mailer_jobs_spec.rb \
|
|
138
|
-
spec/requests/api/v1/email_ingest_spec.rb \
|
|
139
|
-
spec/requests/api/v1/email_ingest_coa_flow_spec.rb \
|
|
140
|
-
spec/models/inbound_email_spec.rb \
|
|
141
|
-
spec/models/report_email_spec.rb \
|
|
142
|
-
spec/models/organization_email_domain_spec.rb \
|
|
143
|
-
spec/services/communications/recorder_spec.rb
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Observed: seed **46556**, 437 examples, 0 failures, 0 pending; 51.88 seconds of examples plus 9.91 seconds loading.
|
|
147
|
-
|
|
148
|
-
The separate adapter probe's header documents its commands. Reserve `TEST_ENV_NUMBER=9` exclusively for that probe; never run another process against the same primary/tenant test databases. The adapted probe passed its six characterization examples with seed **45808**.
|
|
149
|
-
|
|
150
|
-
### Public DNS observation
|
|
151
|
-
|
|
152
|
-
A read-only MX lookup returned:
|
|
153
|
-
|
|
154
|
-
- `rebulk.com`: Google's five Workspace MX hosts.
|
|
155
|
-
- `mail.rebulk.com`, `in.rebulk.com`, `agent.rebulk.com`, `test.rebulk.com`: `route1/2/3.mx.cloudflare.net` with priorities 30/61/91.
|
|
156
|
-
|
|
157
|
-
No DNS records were changed. These answers do not identify which Worker a literal address reaches or prove sending-domain verification.
|
|
158
|
-
|
|
159
|
-
## Initial live acceptance gate (see subsequent live report)
|
|
160
|
-
|
|
161
|
-
Do not describe the release as live-verified until these have evidence from a controlled account and confirmed recipient inbox:
|
|
162
|
-
|
|
163
|
-
| Check | Required evidence |
|
|
164
|
-
|---|---|
|
|
165
|
-
| Account, sending domain, tokens | Read-only confirmation of the exact account/domain and least-privilege token capabilities |
|
|
166
|
-
| Outbound structured + raw MIME | Provider response and received synthetic message; HTML/text, attachment hash, cc/bcc behavior, provider ID |
|
|
167
|
-
| Inbound deployed Worker | Dedicated exact test address reaches an isolated verification Worker and Rails app; one stored/processed message |
|
|
168
|
-
| Reply threading | Delivered raw Message-ID and returned reply headers; confirm whether custom signed IDs survive or use provider-ID correlation |
|
|
169
|
-
| Delivery events | Actual domain-scoped Queue subscription, observed event, durable handler result, successful acknowledgement, duplicate-safe processing |
|
|
170
|
-
| Environment isolation | Confirm exact Worker names/addresses and secrets; no production route changes |
|
|
171
|
-
| Cleanup | Remove only newly created verification resources; retain sanitized results |
|
|
172
|
-
|
|
173
|
-
Proposed live scope for approval: a dedicated address under the already-existing `test.rebulk.com`, a uniquely named verification Worker/Queue, a temporary test Rails app, and a mailbox explicitly confirmed by the owner. Inspect existing address routes first; do not replace Rebulk's zone catch-all or existing experimental routes.
|
|
174
|
-
|
|
175
|
-
These steps send real email and may create/deploy isolated resources, so the target mailbox/account and live scope must be confirmed before execution. No real email, Cloudflare deployment, Queue consumption, secret decryption, or production changes occurred during this verification. Public DNS lookup was the only live infrastructure inspection.
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# Custom ingress and routing diagnostics verification
|
|
2
|
-
|
|
3
|
-
Scope: upstream gem integration tools informed by Rebulk's existing ingestion
|
|
4
|
-
pipeline. This report covers local synthetic verification, not a Rebulk rollout.
|
|
5
|
-
|
|
6
|
-
## Results
|
|
7
|
-
|
|
8
|
-
| Check | Result |
|
|
9
|
-
| --- | --- |
|
|
10
|
-
| Full Ruby suite, Ruby 3.4.1 / Rails 7.2, 8.0, 8.1 | Each: 257 tests, 916 assertions, no failures/errors/skips |
|
|
11
|
-
| Shared ingress unit tests | 12 tests, 98 assertions; independent Node/Ruby v3 binary/Unicode vector, tampering, metadata bounds, short streams, immutable results, retry identity |
|
|
12
|
-
| Custom Rails endpoint | 5 integration tests, 37 assertions; separate SQLite tenant databases, policy before persistence, durable signed metadata and retries |
|
|
13
|
-
| Built-in Rails endpoint | V3 persistence before routing enqueue, binary preservation, retries and unsigned v2 metadata rejection; covered in the full suite |
|
|
14
|
-
| Read-only routing diagnostics | 27 tests, 50 assertions; mocked Cloudflare configuration, exact-domain DNS, rule selection, ambiguous/inaccessible responses and pagination |
|
|
15
|
-
| Worker | 45 tests pass; Wrangler dry-run succeeds |
|
|
16
|
-
| Packaged consumer | Isolated plain Ruby install and packaged Rails installation/ingress fixtures pass |
|
|
17
|
-
| Local workerd → Rails | Real local forwarding, Bcc routing, attachment preservation, wrong/missing secrets, redirects and timeout handling pass |
|
|
18
|
-
|
|
19
|
-
Independent reviews found two verifier issues: a readable stream can return
|
|
20
|
-
short chunks, and HMAC must use the timestamp's exact transmitted bytes. Both
|
|
21
|
-
were corrected and covered by regression tests. Freshness still parses decimal
|
|
22
|
-
seconds; existing bundled Worker timestamps are unchanged.
|
|
23
|
-
|
|
24
|
-
## Reproduce
|
|
25
|
-
|
|
26
|
-
```sh
|
|
27
|
-
BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle exec rake test
|
|
28
|
-
BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rake test
|
|
29
|
-
BUNDLE_GEMFILE=gemfiles/rails_8_1.gemfile bundle exec rake test
|
|
30
|
-
bundle exec ruby script/verify_package.rb
|
|
31
|
-
BUNDLE_GEMFILE=gemfiles/local_ingress.gemfile bundle exec ruby script/verify_local_ingress.rb
|
|
32
|
-
npm --prefix templates/worker test
|
|
33
|
-
npm --prefix templates/worker run check
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Use Node 22 for the Worker and local forwarding checks.
|
|
37
|
-
|
|
38
|
-
## Limits
|
|
39
|
-
|
|
40
|
-
No Rebulk application code, live DNS, Worker deployment, inbox activation, R2
|
|
41
|
-
archive, or gem publication was changed. The local workerd test exercises the
|
|
42
|
-
default v2 transport; opt-in v3 is covered by Worker tests, an independent shared
|
|
43
|
-
signature vector, and real Rails integration tests, not deployed Cloudflare mail.
|
|
44
|
-
|
|
45
|
-
Routing diagnostics inspect provider configuration through mocked API tests.
|
|
46
|
-
A passing configuration snapshot does not establish public DNS propagation,
|
|
47
|
-
complete SPF evaluation, Worker application behavior, or live delivery. Signed
|
|
48
|
-
metadata authenticates the forwarding integration's assertions; the host still
|
|
49
|
-
owns original-sender trust, held-message policy, and business processing.
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# Preproduction cleanup and reusable event receipts — September 11, 2026
|
|
2
|
-
|
|
3
|
-
This pass removes v1 ingress and SecureMessageId, adds optional ActiveRecord event
|
|
4
|
-
receipts, and extracts provider acceptance, message-ID normalization and delivery
|
|
5
|
-
ordering helpers. See [architecture](../architecture.md) for the implemented
|
|
6
|
-
boundary and the next outbound-ledger extraction.
|
|
7
|
-
|
|
8
|
-
## Local checks
|
|
9
|
-
|
|
10
|
-
| Check | Result |
|
|
11
|
-
| --- | --- |
|
|
12
|
-
| `bundle exec rake test`, Ruby 3.4.1 / Rails 8.1.3.1 | 189 tests, 635 assertions, no failures/errors/skips |
|
|
13
|
-
| Durable event adapter subprocess included in suite | 13 SQLite tests, 56 assertions |
|
|
14
|
-
| `bundle exec ruby script/verify_package.rb` | Isolated Ruby consumer and Rails package checks passed; 48 tests, 235 assertions |
|
|
15
|
-
| `script/verification/install_upgrade.rb` under Ruby 4 / Rails 8.1 | 16 checks passed, including v1 rejection and v2 inbox routing |
|
|
16
|
-
| Local actual workerd → Rails | 19 checks passed using Node 22.23.1 |
|
|
17
|
-
|
|
18
|
-
Receipt tests exercise the generated migration, account-scoped database uniqueness,
|
|
19
|
-
concurrent duplicate insertion, changed-payload rejection, commit-before-ACK,
|
|
20
|
-
failed persistence preventing ACK, replay, savepoint/handler rollback, and explicit
|
|
21
|
-
handler outcomes. Plain gem loading is checked without ActiveRecord.
|
|
22
|
-
|
|
23
|
-
The Worker run verifies authenticated SMTP metadata, unchanged MIME and all 256
|
|
24
|
-
binary byte values, same-recipient deduplication, separate Bcc recipient records,
|
|
25
|
-
mailbox-job execution, secret mismatch, redirect rejection, and a real 15-second
|
|
26
|
-
timeout. The first invocation found Node 20 on PATH and failed before Worker
|
|
27
|
-
startup; rerunning with explicit installed Node 22.23.1 passed.
|
|
28
|
-
|
|
29
|
-
These are local checks using synthetic mail, SQLite and controlled HTTP responses.
|
|
30
|
-
No email was sent, no Worker deployed, and no RubyGem published in this pass.
|
|
31
|
-
PostgreSQL locking and live delivery using this new adapter have not been verified.
|
|
32
|
-
Earlier live reports remain evidence for their dated revisions only.
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# Shared outbound ledger verification — September 11, 2026
|
|
2
|
-
|
|
3
|
-
The optional Rails layer now owns immutable MIME preparation, account-scoped
|
|
4
|
-
operation keys, committed send claims, recipient acceptance evidence, provider
|
|
5
|
-
event projection, and audited reconciliation. The reference inbox consumes these
|
|
6
|
-
tables and services rather than keeping its own transport ledger.
|
|
7
|
-
|
|
8
|
-
## Local results
|
|
9
|
-
|
|
10
|
-
| Check | Result |
|
|
11
|
-
| --- | --- |
|
|
12
|
-
| Gem suite, Ruby 3.4.1 / Rails 8.1.3.1 | 191 tests, 637 assertions, no failures/errors/skips |
|
|
13
|
-
| Outbox subprocess included in suite | 21 SQLite tests, 112 assertions |
|
|
14
|
-
| Mail snapshots, jobs, event projection subprocess | 9 SQLite tests, 45 assertions |
|
|
15
|
-
| Existing receipt subprocess | 13 SQLite tests, 56 assertions |
|
|
16
|
-
| Actual PostgreSQL 15 verifier | 9 tests, 45 assertions |
|
|
17
|
-
| Packaged isolated Ruby + Rails install/ingress + outbound checks | 57 tests, 280 assertions; all passed |
|
|
18
|
-
|
|
19
|
-
The top-level gem counts include subprocess assertions, not their individual
|
|
20
|
-
inner assertions. PostgreSQL 16 verification is added to CI. The PostgreSQL
|
|
21
|
-
driver remains an optional test dependency; the gem has no new runtime database
|
|
22
|
-
or job dependencies for plain Ruby users.
|
|
23
|
-
|
|
24
|
-
## Failure and concurrency coverage
|
|
25
|
-
|
|
26
|
-
- Eight concurrent identical operations produce one stored snapshot and one
|
|
27
|
-
provider request. Conflicting snapshots cannot overwrite the winner.
|
|
28
|
-
- Independent operations reach the controlled provider concurrently.
|
|
29
|
-
- A real SIGKILL after the durable claim leaves the operation blocked after
|
|
30
|
-
restart. A real PostgreSQL backend termination plus simulated provider timeout
|
|
31
|
-
also preserves the non-retryable claim.
|
|
32
|
-
- Acceptance followed by database failure does not permit another send.
|
|
33
|
-
- Partial acceptance never resends the whole batch. Unknown recipients can be
|
|
34
|
-
reconciled independently while known lifecycle states remain intact.
|
|
35
|
-
- Malformed IDs and conflicting per-recipient provider IDs become uncertainty;
|
|
36
|
-
a valid common ID on a later recipient entry is retained.
|
|
37
|
-
- Event-before-response, duplicate callbacks, concurrent out-of-order events,
|
|
38
|
-
terminal guards, account mismatch and ambiguous IDs are exercised.
|
|
39
|
-
- Application callback failure rolls back product and recipient writes while
|
|
40
|
-
retaining the receipt; replay applies it. A send-job retry repairs failed
|
|
41
|
-
product projection without another provider request.
|
|
42
|
-
- MIME includes a binary attachment containing all 256 byte values and a hidden
|
|
43
|
-
Bcc envelope recipient. Jobs serialize only the account and operation key.
|
|
44
|
-
- Reconciliation requires actor/reason/evidence and legal unresolved transitions.
|
|
45
|
-
Sending reconciliation requires a stopped sender confirmation and a minimum
|
|
46
|
-
claim age. Generated migrations refuse to delete safety evidence on rollback.
|
|
47
|
-
|
|
48
|
-
## Boundaries
|
|
49
|
-
|
|
50
|
-
HTTP acceptance and lifecycle payloads use controlled local fixtures. This pass
|
|
51
|
-
does not send live mail, deploy a Worker, publish RubyGems, or prove inbox placement.
|
|
52
|
-
Earlier Cloudflare live reports describe their own revisions. Provider-ID
|
|
53
|
-
correlation assumes Cloudflare assigns unique IDs; pre-existing duplicate matches
|
|
54
|
-
are retained as unmatched for review. The network and database remain separate
|
|
55
|
-
transactions, and the system does not claim exactly-once provider delivery.
|
|
56
|
-
|
|
57
|
-
The reference inbox has separate HTTP, browser, process-recovery and import
|
|
58
|
-
verification recorded in its `docs/verification/2026-09-11-outbox.md`. Historical
|
|
59
|
-
records preserve available content and audit data; reconstructed imported MIME
|
|
60
|
-
is explicitly marked and is not claimed to reproduce historical wire bytes.
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# Security review — 2026-09-11
|
|
2
|
-
|
|
3
|
-
Reviewed the 0.2.0 gem at baseline `bd83e067938d5c8df5a7a2ced2cf2a74c3f58e06`
|
|
4
|
-
and applied the hardening described below on `security/gem-hardening`.
|
|
5
|
-
This was a defensive source review, dependency audit and local verification.
|
|
6
|
-
No live penetration testing was performed and no production compromise was
|
|
7
|
-
demonstrated. Prior live test reports describe different revisions.
|
|
8
|
-
|
|
9
|
-
## Findings and fixes
|
|
10
|
-
|
|
11
|
-
Priorities are contextual review judgments, not CVSS scores.
|
|
12
|
-
|
|
13
|
-
| Priority | Finding and prerequisite | Remediation |
|
|
14
|
-
| --- | --- | --- |
|
|
15
|
-
| High | The development tunnel exposed the full Rails origin when enabled, including routes unrelated to email. | Force a dedicated origin Host and install a first-position development middleware that permits only the ingress POST. Refuse to start the tunnel unless the running server has the guard. |
|
|
16
|
-
| High | The old Rails 7.1 test stack resolved dependencies with 12 advisory hits, including ActiveStorage CVE-2026-66066. Application exposure depends on its resolved dependencies and feature use. | Remove that unsupported matrix variant; raise Rails/SQLite test floors and audit resolved Ruby matrix locks in CI. Applications must update their own dependencies. |
|
|
17
|
-
| Medium | Inbound MIME was read without an application bound, allowing large requests/messages to consume memory. Upstream limits determine reachability. | Default 25 MiB bound in Rails and Worker, declared-size checks and bounded stream reads. Reject malformed/stale signing headers before Rails reads the body. Full HMAC verification still follows the read. |
|
|
18
|
-
| Medium | Operator-configured remote HTTP endpoints could transmit credentials or mail without TLS. | Require HTTPS for API and ingress endpoints, except literal loopback development hosts. Validate deploy configuration before remote mutations. Endpoint configuration remains trusted. |
|
|
19
|
-
| Medium | Delivery-event validation omitted important field types; original receipt identity/payload could be changed through ordinary model updates. Requires malformed queue input or application writes. | Validate identifiers, recipient, timestamp, terminal boolean and optional detail objects; mark receipt evidence fields read-only. Preserve unknown schema-1 future event types without projecting an unknown lifecycle state. |
|
|
20
|
-
| Low | Client inspection, retry warnings and generated mailbox logs could disclose tokens or private provider/mail details. | Redact client inspection and remove sensitive fields from those default logging paths. Raw errors/responses remain available to applications. |
|
|
21
|
-
| Low | Provider acceptance helpers treated some malformed success fields or message IDs permissively. | Require explicit boolean success when present, successful HTTP status, valid string IDs and array-shaped recipient outcomes. |
|
|
22
|
-
|
|
23
|
-
Reviewed HMAC v2/envelope handling, recipient-scoped deduplication, transport
|
|
24
|
-
retries, event account/domain checks and ACK ordering, durable outbox claims,
|
|
25
|
-
tenant/account correlation, evidence storage, deployment/generator behavior,
|
|
26
|
-
package contents and CI permissions. No additional concrete authentication or
|
|
27
|
-
claim-bypass finding was established in these paths; this is not proof that
|
|
28
|
-
all defects are absent.
|
|
29
|
-
|
|
30
|
-
## Verification
|
|
31
|
-
|
|
32
|
-
Final Ruby behavior checks used Ruby 3.4.1 and Rails 8.1.3.1:
|
|
33
|
-
|
|
34
|
-
- `bundle exec rake test`: **205 tests, 751 assertions**, no failures/errors/skips.
|
|
35
|
-
Includes real Rails subprocesses, SQLite receipts/outbox and development guard.
|
|
36
|
-
- Worker `npm test`: **28 tests passed**, Node 22.23.1.
|
|
37
|
-
- Worker `npm run check`: Wrangler deployment dry-run passed.
|
|
38
|
-
- `script/verify_package.rb`: isolated plain Ruby consumer, packaged Rails
|
|
39
|
-
installation/ingress and durable outbound checks passed.
|
|
40
|
-
- `script/verify_local_ingress.rb`: actual local workerd-to-Rails verification
|
|
41
|
-
passed, including exact MIME/binary attachment preservation, trusted envelope,
|
|
42
|
-
deduplication and separate recipients, routing, invalid/missing credentials,
|
|
43
|
-
refused redirects and a stalled upstream aborted after 15 seconds.
|
|
44
|
-
No real email was sent and no Worker was deployed by this run.
|
|
45
|
-
- `bundler-audit 0.9.3`: no known vulnerabilities in the six supported locally
|
|
46
|
-
resolved lockfiles (default, local ingress, PostgreSQL, Rails 7.2/8.0/8.1).
|
|
47
|
-
Advisory database commit: `93b32f641f84282183ce58ab1d7204bee50885bd`.
|
|
48
|
-
- `npm audit --audit-level=low`: **zero known vulnerabilities**.
|
|
49
|
-
- `git diff --check`: passed.
|
|
50
|
-
|
|
51
|
-
Ruby lockfiles are not committed; audits describe these resolutions and the
|
|
52
|
-
advisory database at review time. CI audits newly resolved Ruby matrix stacks.
|
|
53
|
-
PostgreSQL concurrency execution and the broader Ruby matrix are delegated to
|
|
54
|
-
the PR's CI; their results should be checked separately from the local results.
|
|
55
|
-
|
|
56
|
-
## Remaining boundaries and rollout
|
|
57
|
-
|
|
58
|
-
See [SECURITY.md](../../SECURITY.md) for application and deployment controls.
|
|
59
|
-
Upstream request buffering, rate limiting and read deadlines remain external.
|
|
60
|
-
Mail rendering, malware scanning, tenant authorization, encryption and retention
|
|
61
|
-
are application responsibilities. Queue write access is an authenticity boundary;
|
|
62
|
-
account labels are not signatures. Read-only ActiveRecord attributes do not
|
|
63
|
-
prevent privileged SQL changes. Inbound deduplication intentionally keys identical
|
|
64
|
-
MIME by SMTP recipient, retaining the first authenticated envelope sender.
|
|
65
|
-
|
|
66
|
-
The development Host guard restricts routes, but the allowed ingress still runs
|
|
67
|
-
inside the development application: safe error handling and test-only data are
|
|
68
|
-
required. CI action references use version tags rather than immutable commits;
|
|
69
|
-
pinning them is additional supply-chain hardening still available.
|
|
70
|
-
|
|
71
|
-
These changes are not yet a RubyGems release or a deployed Worker upgrade. The
|
|
72
|
-
dogfood inbox has not been repinned or live-tested against this security branch.
|
|
73
|
-
Rollout requires updating the gem revision, redeploying the Worker, aligning any
|
|
74
|
-
`MAX_EMAIL_BYTES` override, updating application dependencies and restarting Rails
|
|
75
|
-
before opening a development tunnel. Do not equate the earlier dogfood results
|
|
76
|
-
with verification of this revision in a deployed application.
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# Optional mailboxes and tenant support — 2026-09-11
|
|
2
|
-
|
|
3
|
-
Implementation source: `4273c5ac10cb87d5e5610acc6a4e69c55d94a42f`.
|
|
4
|
-
The guides and installation pin may receive subsequent documentation-only updates.
|
|
5
|
-
|
|
6
|
-
## Implemented
|
|
7
|
-
|
|
8
|
-
- Explicit optional tenant adapter and configurable model base for the existing
|
|
9
|
-
outbox/receipt models and new tenant mailbox models. Plain Ruby remains independent.
|
|
10
|
-
- Shared receiving-domain directory and tenant mailboxes, aliases, owner references,
|
|
11
|
-
lifecycle, inbound memberships, read/archive state and explicit purge.
|
|
12
|
-
- Full HMAC verification before recipient-to-tenant resolution, followed by
|
|
13
|
-
same-connection ActionMailbox/ActiveStorage persistence and membership storage.
|
|
14
|
-
- Scoped mailbox sender checks and identity-only send jobs using the existing
|
|
15
|
-
immutable outbox, callbacks, reconciliation and conservative retry behavior.
|
|
16
|
-
- Shared event intake before ACK, account/message/recipient correlation to a
|
|
17
|
-
tenant outbox, tenant commit before shared completion, and bounded recovery jobs.
|
|
18
|
-
- Tenant serialization before framework GlobalID lookup; missing/conflicting
|
|
19
|
-
tenant metadata rejected. Managed raw mail retained against normal incineration.
|
|
20
|
-
- Composed generator with separate tenant/shared migration paths and optional
|
|
21
|
-
`activerecord-tenanted` integration guide/bundle/CI job.
|
|
22
|
-
|
|
23
|
-
## Local verification
|
|
24
|
-
|
|
25
|
-
Ruby 3.4.1 / Rails 8.1.3.1 unless noted:
|
|
26
|
-
|
|
27
|
-
| Check | Result |
|
|
28
|
-
| --- | --- |
|
|
29
|
-
| Full `bundle exec rake test` | 212 tests, 758 assertions, no failures/errors/skips |
|
|
30
|
-
| Tenant connection and job subprocess | 11 tests, 54 assertions; physical SQLite shards with overlapping IDs, host-only framework job capture and context restoration |
|
|
31
|
-
| Default job-context subprocess | 3 tests, 7 assertions; ordinary untagged jobs preserved without database tenancy |
|
|
32
|
-
| Mailbox service subprocess | 16 tests, 92 assertions; two fixed SQLite tenant pools plus shared directory |
|
|
33
|
-
| Shared event subprocess | 8 tests, 28 assertions; also checked on Rails 7.2 |
|
|
34
|
-
| Real Rails mailbox ingress subprocess | 4 tests, 29 assertions; signed recipient routing, raw storage, jobs, aliases, deduplication, unavailable destinations, retention and purge |
|
|
35
|
-
| Actual `activerecord-tenanted` 0.8 integration | 3 tests, 23 assertions; real tenant provisioning/migrations, isolation, context and GlobalID checks |
|
|
36
|
-
| Generator checks | Fresh single-DB and separate shared/tenant SQLite installs, migration ordering and initializer syntax |
|
|
37
|
-
| Packaged gem | Isolated Ruby consumer; Rails install/ingress/outbox checks; new tenant ingress, jobs, retention and all mailbox migrations from extracted package |
|
|
38
|
-
| Local workerd → Rails | Existing real runtime fixture passed, including raw bytes, routing, duplicates, invalid credentials, redirects and timeout |
|
|
39
|
-
|
|
40
|
-
Top-level test totals include subprocess wrapper assertions; detailed subprocess
|
|
41
|
-
counts above describe their internal checks and are not additional top-level tests.
|
|
42
|
-
The service suite includes concurrent claims producing one provider request,
|
|
43
|
-
callback rollback, retry/recovery without resending, reconcile rollback, pagination,
|
|
44
|
-
suspension after enqueue, caller operation-key isolation and sender/account checks.
|
|
45
|
-
Event tests include event-before-response, conflicting identities, cross-tenant
|
|
46
|
-
correlation ambiguity, tenant-commit/shared-failure replay, and callback rollback.
|
|
47
|
-
|
|
48
|
-
The optional tenant dependency resolution passed bundler-audit. CI additionally
|
|
49
|
-
runs the supported Ruby/Rails matrix, PostgreSQL outbox checks and Worker checks.
|
|
50
|
-
Check the PR's CI for results on its latest revision.
|
|
51
|
-
|
|
52
|
-
## Operational boundaries
|
|
53
|
-
|
|
54
|
-
No production Worker, domain/DNS, inbox or Rebulk deployment changed. No real
|
|
55
|
-
email was sent by these checks. The new Worker-to-mailbox path uses the existing
|
|
56
|
-
v2 signature contract; live customer subdomain provisioning was not exercised.
|
|
57
|
-
Rebulk's existing ingest/communication records and sender-authentication policy
|
|
58
|
-
still require a separate, explicit application adoption project.
|
|
59
|
-
|
|
60
|
-
Directory activation records authorized operator evidence, not independent
|
|
61
|
-
domain verification. Application permissions select tenant and mailbox access;
|
|
62
|
-
raw ActiveRecord and administrative directory methods are privileged interfaces.
|
|
63
|
-
Database keys and queue payloads must come from trusted application state.
|
|
64
|
-
|
|
65
|
-
Framework records must share the configured tenant connection. Drain old
|
|
66
|
-
ActionMailbox/ActiveStorage jobs before enabling database tenancy because old
|
|
67
|
-
payloads without the new metadata cannot safely be guessed. Mailbox memberships
|
|
68
|
-
retain raw mail until explicit purge, so applications must define a retention
|
|
69
|
-
policy and monitor storage usage.
|
|
70
|
-
|
|
71
|
-
Queue intake and tenant projection use separate commits with idempotent recovery,
|
|
72
|
-
not a distributed transaction. Schedule polling, shared replay and per-tenant
|
|
73
|
-
recovery. Unknown provider outcomes stay blocked; no exactly-once guarantee or
|
|
74
|
-
automatic resend is added. Existing outbox PostgreSQL behavior remains covered;
|
|
75
|
-
the new mailbox/tenant integration's direct database evidence is SQLite.
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# Destination context and email persistence verification
|
|
2
|
-
|
|
3
|
-
Scope: standalone accepted-address resolution and destination context yielded
|
|
4
|
-
from the existing persisted receive path. No Rebulk deployment or gem release.
|
|
5
|
-
|
|
6
|
-
## Results
|
|
7
|
-
|
|
8
|
-
- Full suites on Ruby 3.4.1 with Rails 7.2, 8.0 and 8.1: each passed with
|
|
9
|
-
257 top-level tests and 916 assertions. These include subprocess integration
|
|
10
|
-
fixtures whose internal assertions are not included in that top-level count.
|
|
11
|
-
- The standalone service fixture uses two real SQLite tenant databases with
|
|
12
|
-
colliding mailbox IDs and no ActionMailbox loaded. It verifies host-owned
|
|
13
|
-
binary-email persistence, immutable destinations, aliases, inactive records,
|
|
14
|
-
nested context restoration, host exceptions and missing-tenant refusal without
|
|
15
|
-
creating a database. Lookup itself creates no mailbox membership.
|
|
16
|
-
- The custom Rails ingress fixture verifies saved raw MIME and signed metadata,
|
|
17
|
-
mailbox membership, and a host business link created from the destination.
|
|
18
|
-
Routing jobs execute in the correct tenant after commit. Retries do not
|
|
19
|
-
duplicate the linked records. A host write failure rolls back the email and
|
|
20
|
-
membership and schedules no routing job.
|
|
21
|
-
- Existing callbacks remain supported, including strict zero-argument lambdas.
|
|
22
|
-
- Packaged plain Ruby and Rails consumer checks pass, including mailbox ingress,
|
|
23
|
-
routing and existing retention behavior.
|
|
24
|
-
- Independent review found no remaining blockers. A callback arity compatibility
|
|
25
|
-
issue was fixed and covered before completion.
|
|
26
|
-
|
|
27
|
-
## Reproduce
|
|
28
|
-
|
|
29
|
-
```sh
|
|
30
|
-
BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle exec rake test
|
|
31
|
-
BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rake test
|
|
32
|
-
BUNDLE_GEMFILE=gemfiles/rails_8_1.gemfile bundle exec rake test
|
|
33
|
-
bundle exec ruby script/verify_package.rb
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Boundaries
|
|
37
|
-
|
|
38
|
-
Destination context is a lifecycle snapshot, not host authorization or a durable
|
|
39
|
-
processing claim. Applications still validate owners and sender policy and make
|
|
40
|
-
downstream work idempotent. The host owns custom-store transactions; `receive`
|
|
41
|
-
preserves the existing shared tenant transaction for ActionMailbox persistence.
|
|
42
|
-
Neither API makes cross-database or external object-store operations atomic.
|
|
43
|
-
|
|
44
|
-
Managed mailbox memberships protect raw email from automatic incineration;
|
|
45
|
-
deliberate purge, backups and host-reference cleanup remain application policy.
|
|
46
|
-
These local tests do not verify Rebulk's actual queues, sender policy or archive.
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# Action Mailbox composition verification
|
|
2
|
-
|
|
3
|
-
## Independent review follow-up
|
|
4
|
-
|
|
5
|
-
The independent architecture review of `63660eb` confirmed the Rails/core/provider
|
|
6
|
-
split and reproduced a missing inbound membership index with SQLite's query planner.
|
|
7
|
-
The follow-up adds that index to both installers and supplies `mailbox_kit:upgrade`
|
|
8
|
-
for existing schemas. The generator fixture verifies indexed lookup, existing
|
|
9
|
-
membership preservation and repeat application. It passes 4 tests / 27 assertions.
|
|
10
|
-
|
|
11
|
-
Cloudflare configuration now delegates shared validation to the core, and its
|
|
12
|
-
session behavior is an explicit integration module. Handler overrides retain
|
|
13
|
-
their existing extension point. The short core README links to packaged
|
|
14
|
-
integration and upgrade guides.
|
|
15
|
-
|
|
16
|
-
After these changes, the full suite passes 289 tests / 1,151 assertions. Package
|
|
17
|
-
verification passes for Rails-free Cloudflare, packaged ingress/outbound/tenancy,
|
|
18
|
-
and an independent Rails consumer without Cloudflare in its bundle.
|
|
19
|
-
|
|
20
|
-
This is local/package verification, not a Rebulk production rollout. Automatic
|
|
21
|
-
model-owner lifecycle bindings and a universal outbound adapter framework remain
|
|
22
|
-
outside this change. Existing application acceptance, ownership and business
|
|
23
|
-
processing claims must be retained.
|
|
24
|
-
|
|
25
|
-
Mailbox Kit now treats `ActionMailbox::InboundEmail` as the original email record.
|
|
26
|
-
The kit's `Message` remains an inbox membership with read/archive state; no new
|
|
27
|
-
raw-email table, parser, routing job or processing-status model was introduced.
|
|
28
|
-
|
|
29
|
-
## Verified behavior
|
|
30
|
-
|
|
31
|
-
- `Mailboxes.receive(recipient:, source:)` resolves the trusted receiving scope
|
|
32
|
-
before Rails persistence, and attaches the existing record on source replay.
|
|
33
|
-
Identical source in two inboxes of one tenant produces one Rails email, two
|
|
34
|
-
memberships and one routing job. Read state remains independent per inbox.
|
|
35
|
-
- Identical source in different tenants sharing a database produces distinct
|
|
36
|
-
Rails records/jobs. `Session#attach` rejects a row already associated with a
|
|
37
|
-
different tenant, and resolves IDs only inside the selected connection.
|
|
38
|
-
- `Session#attach(recipient:, inbound_email_id:)` supports already-stored Rails
|
|
39
|
-
mail. It is idempotent per mailbox/email, preserves alias reservations and
|
|
40
|
-
refuses suspended addresses or missing records.
|
|
41
|
-
- The stock Rails Postmark ingress was exercised through HTTP in the isolated
|
|
42
|
-
Rails fixture. Duplicate webhook payloads produced one Rails email; explicit
|
|
43
|
-
host-authorized attachment created its inbox membership without reprocessing.
|
|
44
|
-
- Cloudflare retains its prior authenticated envelope/metadata identity and
|
|
45
|
-
stable Message-ID fallback. Its controller uses `receive_into_mailbox!` and
|
|
46
|
-
repairs missing membership on replay without re-enqueueing Rails routing.
|
|
47
|
-
The older `persist_action_mailbox!` still returns nil on duplicate delivery.
|
|
48
|
-
- Rails' `incinerate = false` prevents automatic cleanup scheduling without a
|
|
49
|
-
membership. With normal cleanup enabled, aged processed unassociated mail is
|
|
50
|
-
removed while inbox-associated mail survives. The guard uses Rails' public
|
|
51
|
-
inbound-email load hook and the same row lock as attachment and purge.
|
|
52
|
-
- Removing one of two memberships preserves the shared raw email; removing the
|
|
53
|
-
last through explicit purge removes it. Failed membership persistence rolls
|
|
54
|
-
back Rails database records and prevents a routing enqueue.
|
|
55
|
-
- PostgreSQL deliveries were synchronized at Rails creation to force a unique
|
|
56
|
-
conflict. Both calls completed with one inbound record, one membership, one
|
|
57
|
-
Active Storage blob record and one routing job. A savepoint rollback keeps the
|
|
58
|
-
losing transaction usable before looking up the winning record.
|
|
59
|
-
|
|
60
|
-
## Checks
|
|
61
|
-
|
|
62
|
-
- Full Ruby suite: 289 top-level tests, 1,151 assertions passed. Integration
|
|
63
|
-
subprocesses exercise the additional Rails behavior rather than inflating these
|
|
64
|
-
top-level counts.
|
|
65
|
-
- `script/verify_package.rb`: both gem archives built; Rails-free Cloudflare
|
|
66
|
-
consumer, packaged Cloudflare integration and independent core-only Rails
|
|
67
|
-
consumer passed. The independent bundle contains no Cloudflare gem.
|
|
68
|
-
- PostgreSQL inbound suite: 25 tests, 273 assertions passed on a disposable local
|
|
69
|
-
PostgreSQL 15 instance. CI runs the same suite against PostgreSQL 16 alongside
|
|
70
|
-
the existing outbox tests.
|
|
71
|
-
- SQLite core/compatibility, separate-tenant ingress and custom ingress regression
|
|
72
|
-
suites were exercised locally. CI covers the supported Ruby/Rails matrix.
|
|
73
|
-
|
|
74
|
-
Run the PostgreSQL check only against a disposable database:
|
|
75
|
-
|
|
76
|
-
```sh
|
|
77
|
-
BUNDLE_GEMFILE=gemfiles/postgres.gemfile \
|
|
78
|
-
POSTGRES_TEST_URL=postgres://localhost/cloudflare_email_test \
|
|
79
|
-
bundle exec ruby script/verify_postgres_inbound.rb
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
The script creates a unique schema and drops that schema when its fixture exits.
|
|
83
|
-
|
|
84
|
-
## Limits
|
|
85
|
-
|
|
86
|
-
The Postmark test is a fixed, authorized single-database route, not a live
|
|
87
|
-
Postmark deployment or a complete dynamic-tenant provider adapter. The caller
|
|
88
|
-
must authenticate ingress and authorize the envelope recipient and inbound ID;
|
|
89
|
-
MIME To/X-Original-To headers are not tenant entitlement. Tenant selection after
|
|
90
|
-
ingress cannot relocate raw mail already persisted to another database.
|
|
91
|
-
|
|
92
|
-
Raw blob uploads and external side effects are not made transactional by Active
|
|
93
|
-
Record. This check does not establish exactly-once business effects, provider
|
|
94
|
-
outage recovery, lost-job recovery, or live delivery. Existing Cloudflare durable
|
|
95
|
-
transport remains responsible for its outage/retry behavior. Owner lifecycle
|
|
96
|
-
macros and a general outbound adapter framework are separate work.
|