arcane-os 0.15.2 → 0.16.0
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.
- package/CHANGELOG.md +22 -0
- package/README.md +9 -1
- package/docs/reference/cli.md +44 -30
- package/docs/reference/mail.md +166 -67
- package/docs/reviews/mail-server-purpose-review.md +350 -0
- package/package.json +1 -1
- package/runtime/arcane/modules/Mail.js +42 -71
- package/runtime/arcane/modules/MailOutbox.mjs +5 -5
- package/runtime/arcane/modules/MailTransport.mjs +12 -21
- package/src/cli/main.mjs +14 -46
- package/src/mail-server.mjs +333 -617
- package/src/mail.mjs +50 -110
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.0
|
|
4
|
+
|
|
5
|
+
- Make mail hosting domain-based: use the current browser origin by default,
|
|
6
|
+
preserve explicit endpoints, support any application name, and remove
|
|
7
|
+
loopback-only binding and request admission. Keep exact configured CORS
|
|
8
|
+
allowlists and recipient policy for To, Cc, and Bcc. Make the CLI sender
|
|
9
|
+
override optional so reports and provider templates can supply their sender.
|
|
10
|
+
- Replace the local app-key and digest mechanism with optional
|
|
11
|
+
`verifySubscription({appName, subscriptionKey, signal})` configuration.
|
|
12
|
+
Verification is disabled until configured for the staged service setup.
|
|
13
|
+
A configured verifier must accept each app and bearer key before provider
|
|
14
|
+
delivery; rejection, service failure, and cancellation remain distinct.
|
|
15
|
+
- Remove redundant header reconstruction, address/origin rewriting, local
|
|
16
|
+
provider payload and identifier grammars, duplicate setup, manual response
|
|
17
|
+
length calculation, unused CLI queue options, and absent-observer work.
|
|
18
|
+
Preserve complete provider fields and responses, idempotency, caller-selected
|
|
19
|
+
deadlines, concurrent requests, and owned shutdown. Report observer and
|
|
20
|
+
escaped handler failures without changing provider acceptance.
|
|
21
|
+
- Resolve subscription credentials from the current User at HTTP delivery,
|
|
22
|
+
preserving explicit credentials and User replacement. Update mail references
|
|
23
|
+
and include the method-by-method gate report.
|
|
24
|
+
|
|
3
25
|
## 0.15.2
|
|
4
26
|
|
|
5
27
|
- Rename the private mail configuration helper to `optionalTimeoutMs` so its
|
package/README.md
CHANGED
|
@@ -19,10 +19,18 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
|
|
20
20
|
event, cancellation, and browser run contracts.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.
|
|
22
|
+
This checkout defines the `0.16.0` SDK contract. Applications pin one exact npm
|
|
23
23
|
version and lockfile; registry state is deliberately not baked into application
|
|
24
24
|
artifacts.
|
|
25
25
|
|
|
26
|
+
The [mail gateway](docs/reference/mail.md) serves the configured host and defaults
|
|
27
|
+
browser mail to `/v1/mail` on the current domain. Multiple applications can share
|
|
28
|
+
one server with explicit allowed origins. Subscription verification is disabled
|
|
29
|
+
until a `verifySubscription` callback is configured; that callback receives the
|
|
30
|
+
application name and bearer subscription key before each provider attempt.
|
|
31
|
+
See the [method and action gate report](docs/reviews/mail-server-purpose-review.md)
|
|
32
|
+
for the removals, retained responsibilities, and naming decisions.
|
|
33
|
+
|
|
26
34
|
Applications can enable [PWA installation and offline resources](docs/reference/pwa.md)
|
|
27
35
|
through their app descriptor. The SDK generates manifests, an independent
|
|
28
36
|
registration module and a service worker, with clean browser resource URLs,
|
package/docs/reference/cli.md
CHANGED
|
@@ -34,7 +34,7 @@ and exits nonzero on failure. Machine output is defined by
|
|
|
34
34
|
| `arcane repo status\|pull\|push` | Runs one selected repository operation for the current app workspace. |
|
|
35
35
|
| `arcane mail key set\|status\|delete` | Manages one server-only Resend API-key profile in Windows Credential Manager. |
|
|
36
36
|
| `arcane mail send` | Performs one explicit, idempotency-keyed Resend attempt from a complete JSON report on redirected stdin. |
|
|
37
|
-
| `arcane mail serve` | Starts one
|
|
37
|
+
| `arcane mail serve` | Starts one Arcane-to-Resend gateway with a server-only provider profile, a selected listener, and optional CORS and recipient configuration. |
|
|
38
38
|
|
|
39
39
|
## Parser-wide options
|
|
40
40
|
|
|
@@ -46,9 +46,9 @@ meaning and cardinality rules:
|
|
|
46
46
|
| `--path` | directory | `new` |
|
|
47
47
|
| `--display-name` | string | `new`, `init` |
|
|
48
48
|
| `--workspace` | directory | Commands that select an external or integrated workspace; defaults to `.`. |
|
|
49
|
-
| `--app` | app id | Workspace/app operations except shared scope and `verify-bundle`;
|
|
49
|
+
| `--app` | app id or label | Workspace/app operations except shared scope and `verify-bundle`; optional diagnostic label for `mail serve`. |
|
|
50
50
|
| `--arcane-root` | directory | `doctor`, native `build`/`run`, `native-doctor`, `native-prepare` |
|
|
51
|
-
| `--host` / `--port` | host / integer 0–65535 | Browser `dev`/`run` default to HTTPS at `127.0.0.1:8000`; `mail serve` defaults to HTTP at `
|
|
51
|
+
| `--host` / `--port` | host / integer 0–65535 | Browser `dev`/`run` default to HTTPS at `127.0.0.1:8000`; `mail serve` defaults to HTTP at `0.0.0.0:8025` and accepts an explicit bind host. |
|
|
52
52
|
| `--http-port` | integer 0–65535 | Browser `dev`/`run` HTTP redirect listener; defaults to `0`, which selects an available port. |
|
|
53
53
|
| `--public` | flag | `dev`; binds to `0.0.0.0` unless `--host` explicitly selects another address. |
|
|
54
54
|
| `--http` | flag | `dev` only; serves source and PWA routes on one HTTP listener selected by `--port`, without TLS. |
|
|
@@ -61,10 +61,10 @@ meaning and cardinality rules:
|
|
|
61
61
|
| `--test-file` | repository-relative `.test.mjs` | `test --scope shared` only |
|
|
62
62
|
| `--artifact` | bundle path | `bundle`, `verify-bundle` |
|
|
63
63
|
| `--profile` | credential profile id | `mail send`, `mail serve` |
|
|
64
|
-
| `--from` |
|
|
65
|
-
| `--origin` | exact browser origin | `mail serve
|
|
64
|
+
| `--from` | optional sender override | `mail send`, `mail serve`; otherwise the report or provider template supplies the sender. |
|
|
65
|
+
| `--origin` | optional exact browser origin | `mail serve`; selects an explicit CORS allowed origin. |
|
|
66
66
|
| `--allow-to` | optional comma-separated addresses | `mail serve` |
|
|
67
|
-
| `--report-key` | nonempty
|
|
67
|
+
| `--report-key` | nonempty string | `mail send`; caller-owned stable Resend idempotency key, forwarded unchanged |
|
|
68
68
|
| `--request-timeout` | optional integer from 1 through 2147483647 milliseconds | `mail send`, `mail serve` |
|
|
69
69
|
| `--output` | `human`, `json`, `ndjson` | Every invocation; the final occurrence wins. |
|
|
70
70
|
| `--git` | flag | `new` |
|
|
@@ -73,7 +73,6 @@ meaning and cardinality rules:
|
|
|
73
73
|
| `--require-local-ai` | flag | `doctor` |
|
|
74
74
|
| `--overwrite` | flag | `bundle` only |
|
|
75
75
|
| `--secret-stdin` | flag | `mail key set`; requires redirected input |
|
|
76
|
-
| `--app-key-stdin` | flag | `mail serve`; requires redirected input |
|
|
77
76
|
| `--report-stdin` | flag | `mail send`; requires redirected JSON input |
|
|
78
77
|
| `--help`, `-h` | flag | Prints help and exits zero. |
|
|
79
78
|
| `--version`, `-v` | flag | Prints the exact SDK version and exits zero. |
|
|
@@ -835,10 +834,10 @@ or positional values.
|
|
|
835
834
|
### One-shot provider send
|
|
836
835
|
|
|
837
836
|
`mail send` performs exactly one Resend provider attempt without starting a
|
|
838
|
-
|
|
837
|
+
server:
|
|
839
838
|
|
|
840
839
|
```text
|
|
841
|
-
arcane mail send --profile <profile> --from <verified-sender> --report-key <id> --report-stdin [--request-timeout <ms>]
|
|
840
|
+
arcane mail send --profile <profile> [--from <verified-sender>] --report-key <id> --report-stdin [--request-timeout <ms>]
|
|
842
841
|
```
|
|
843
842
|
|
|
844
843
|
`--report-stdin` is mandatory and rejects a terminal before attaching input
|
|
@@ -854,15 +853,16 @@ shape:
|
|
|
854
853
|
}
|
|
855
854
|
```
|
|
856
855
|
|
|
857
|
-
The
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
856
|
+
The CLI forwards the complete provider fields, including template requests.
|
|
857
|
+
Resend owns their accepted shape. The adapter removes the application-only
|
|
858
|
+
`type` field and applies `--from` when supplied; otherwise the report or provider
|
|
859
|
+
template supplies the sender. Direct CLI sending has
|
|
860
|
+
no configured fallback recipients. The Resend credential comes only from the
|
|
861
|
+
selected Windows Credential Manager profile; neither it nor report content is
|
|
862
|
+
accepted through argv or environment variables.
|
|
863
863
|
|
|
864
|
-
The caller owns `--report-key
|
|
865
|
-
|
|
864
|
+
The caller owns the nonempty `--report-key`, which is forwarded unchanged.
|
|
865
|
+
Reuse the same key only with the same
|
|
866
866
|
logical report when deliberately reconciling or retrying an
|
|
867
867
|
ambiguous attempt. The CLI never retries automatically.
|
|
868
868
|
|
|
@@ -875,32 +875,46 @@ available request and outcome detail. Cancellation before the
|
|
|
875
875
|
provider attempt exits 130 without sending; cancellation, timeout, or transport
|
|
876
876
|
loss after the attempt begins is ambiguous because Resend may have accepted it.
|
|
877
877
|
|
|
878
|
-
###
|
|
878
|
+
### Mail gateway
|
|
879
879
|
|
|
880
880
|
`mail serve` starts one owned Node HTTP gateway:
|
|
881
881
|
|
|
882
882
|
```text
|
|
883
|
-
arcane mail serve --profile <profile> --from <verified-sender> --app <
|
|
883
|
+
arcane mail serve --profile <profile> [--from <verified-sender>] [--app <label>] [--origin <exact-origin>] [--allow-to <addresses>] [--host 0.0.0.0] [--port 8025] [--request-timeout <ms>]
|
|
884
884
|
```
|
|
885
885
|
|
|
886
886
|
The selected credential profile supplies only the server-side Resend API key.
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
`
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
887
|
+
The CLI does not read a browser app key. Its optional `--app` value labels the
|
|
888
|
+
server; the incoming request's `X-Mail-App` identifies the application for
|
|
889
|
+
subscription verification. The HTTP authentication contract pairs that
|
|
890
|
+
application with `Authorization: Bearer <subscription_key>`.
|
|
891
|
+
|
|
892
|
+
Subscription verification is disabled for this initial service setup. The
|
|
893
|
+
programmatic `createToolchain().mail({action: 'serve', ...})` path accepts
|
|
894
|
+
`verifySubscription({appName, subscriptionKey, signal})`; supplying that callback
|
|
895
|
+
enables verification before each provider attempt. It must resolve to `true`
|
|
896
|
+
to accept the request. An invalid subscription receives 401; verifier service
|
|
897
|
+
failure receives retryable 503; cancellation stops verification before sending.
|
|
898
|
+
The callback connects the actual TWiN Stripe endpoint when its contract is
|
|
899
|
+
ready. There is no guessed URL, response schema, or command-line endpoint flag.
|
|
900
|
+
|
|
901
|
+
The listener defaults to `0.0.0.0`; `--host` selects another bind host. Browser
|
|
902
|
+
mail defaults to `/v1/mail` on the current domain. `--origin` is optional and
|
|
903
|
+
selects an explicit CORS allowlist when supplied. `--allow-to` optionally
|
|
904
|
+
supplies a comma-separated recipient allowlist. CLI parsing preserves supplied
|
|
905
|
+
address spelling and repeated entries. Programmatic `errorTo` selects fallback
|
|
906
|
+
recipients for error reports; when omitted, the selected `allowTo` list supplies
|
|
907
|
+
that fallback. `--request-timeout`
|
|
896
908
|
adds a caller-selected provider-attempt timeout from 1 through 2147483647
|
|
897
909
|
milliseconds, the Node timer range. When it is omitted, the SDK adds no
|
|
898
910
|
provider timeout.
|
|
899
911
|
|
|
900
912
|
After binding, `server.ready` reports lifecycle fields such as
|
|
901
|
-
protocol, app
|
|
913
|
+
protocol, optional app label, bind address, port, URL, and `callerAuthentication`
|
|
914
|
+
(`none` or `subscription`). Human output states whether verification is disabled
|
|
915
|
+
or configured.
|
|
902
916
|
The command owns the server until its lifecycle ends or `SIGINT`/`SIGTERM`
|
|
903
|
-
cancels it.
|
|
917
|
+
cancels it. The server's Resend credential remains outside results and events;
|
|
904
918
|
per-request observer events preserve the complete delivery, report, provider
|
|
905
919
|
outcome, and failure detail available to the gateway.
|
|
906
920
|
|
package/docs/reference/mail.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Mail gateway and durable outbox
|
|
2
2
|
|
|
3
3
|
Arcane Mail is a pure-JavaScript SDK path. It does not use WebAssembly: mail is
|
|
4
|
-
network and durable-state work
|
|
5
|
-
|
|
4
|
+
network and durable-state work. The Resend credential belongs in the Node
|
|
5
|
+
gateway and is never included in browser or WebAssembly state.
|
|
6
6
|
|
|
7
7
|
## Ownership and availability
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ local Node gateway rather than in browser or WebAssembly state.
|
|
|
12
12
|
| `MailOutbox.mjs` | Browser or compatible injected storage | Stores exact requests in the `mail_outbox` DBOPFS table before delivery and normalizes terminal, retry, and reconciliation states. |
|
|
13
13
|
| `MailTransport.mjs` | Browser, WebView, or compatible Fetch host | Sends one already-persisted request to the configured Arcane gateway with the stable report key as its idempotency key. |
|
|
14
14
|
| `arcane mail send` | Node on the local machine | Reads one complete provider-neutral report from redirected stdin and performs one explicit Resend attempt with a caller-owned idempotency key. |
|
|
15
|
-
| `arcane mail serve` | Node on the
|
|
15
|
+
| `arcane mail serve` | Node on the configured host | Owns caller verification, protects the provider credential, applies explicitly configured recipient and origin settings, and makes one server-side Resend request. |
|
|
16
16
|
| `arcane mail key ...` | Node on Windows | Stores, inspects, or deletes a Resend API key in Windows Credential Manager. |
|
|
17
17
|
|
|
18
18
|
The browser never receives the Resend API key. The gateway never writes that
|
|
@@ -49,52 +49,81 @@ boundary; ordinary operators use `arcane mail send`, `arcane mail serve`, and
|
|
|
49
49
|
`arcane mail key ...`. This keeps Node credential and server authority out of a
|
|
50
50
|
browser import while preserving one shared CLI/toolchain implementation.
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## Provider and subscription credentials
|
|
53
53
|
|
|
54
54
|
Arcane Mail deliberately separates two credentials:
|
|
55
55
|
|
|
56
56
|
- The **Resend API key** is provider authority. `arcane mail key set <profile>`
|
|
57
57
|
stores it in Windows Credential Manager. `mail send --profile <profile>` and
|
|
58
58
|
`mail serve --profile <profile>` read it only inside the owning Node process.
|
|
59
|
-
- The **
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
- The **subscription key** is the application user's subscription credential.
|
|
60
|
+
When present, the browser sends it as `Authorization: Bearer <subscriptionKey>`,
|
|
61
|
+
with the exact application name in `X-Mail-App`. The application name identifies the
|
|
62
|
+
subscription account to verify; it is a separate field from the key and the
|
|
63
|
+
report content.
|
|
64
64
|
|
|
65
65
|
Do not put either secret on the command line. Command-line arguments may be
|
|
66
|
-
recorded by the operating system or shell history.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
when page or process trust is lost. It protects the loopback server from
|
|
75
|
-
unadmitted local callers; it is not provider authority or a replacement for
|
|
76
|
-
browser application security.
|
|
66
|
+
recorded by the operating system or shell history. The credential-store
|
|
67
|
+
command uses hidden terminal input or explicitly selected redirected input;
|
|
68
|
+
subscription credentials are supplied through runtime configuration.
|
|
69
|
+
|
|
70
|
+
Subscription credentials stay in runtime configuration or the canonical User
|
|
71
|
+
entity. They are not added to reports, outbox records, delivery events, or
|
|
72
|
+
provider payloads. Supply credentials at runtime; do not hardcode them in
|
|
73
|
+
shipped source or fixtures.
|
|
77
74
|
|
|
78
75
|
## Configure the browser runtime
|
|
79
76
|
|
|
80
|
-
|
|
77
|
+
Configure Mail before its durable lifecycle starts:
|
|
81
78
|
|
|
82
79
|
```javascript
|
|
83
80
|
globalThis.arcane = globalThis.arcane || {};
|
|
84
81
|
globalThis.arcane.config = globalThis.arcane.config || {};
|
|
85
82
|
globalThis.arcane.config.mail = {
|
|
86
|
-
appName: '
|
|
87
|
-
appKey: localMailAppKey,
|
|
88
|
-
endpoint: 'http://127.0.0.1:8025/v1/mail'
|
|
83
|
+
appName: 'My application'
|
|
89
84
|
};
|
|
90
85
|
```
|
|
91
86
|
|
|
92
|
-
|
|
93
|
-
`
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
87
|
+
`appName` is an arbitrary nonempty string, preserved as supplied. `BOSS` and
|
|
88
|
+
`TWiN` are examples, not an enum or an admission list. When `appName` is omitted,
|
|
89
|
+
Mail reads the page's `arcane-app-id` metadata without changing its value.
|
|
90
|
+
|
|
91
|
+
On an HTTP or HTTPS page, the default endpoint is `/v1/mail` on the current
|
|
92
|
+
origin, including its port. Mail does not infer a base domain, select a mail
|
|
93
|
+
subdomain, or treat loopback addresses specially. An explicit `endpoint` may
|
|
94
|
+
select a shared server on another domain. The transport resolves it through
|
|
95
|
+
the URL parser and accepts HTTP or HTTPS; relative URLs and query strings are
|
|
96
|
+
supported. The gateway routes by pathname. Its configured CORS origins must
|
|
97
|
+
include a caller on another origin.
|
|
98
|
+
|
|
99
|
+
An explicit `endpoint: ''` disables HTTP delivery and selects the existing
|
|
100
|
+
native `Arcane.mail.send` fallback when available. Pages with a non-HTTP origin
|
|
101
|
+
also have no default HTTP endpoint. Configured HTTP delivery takes precedence
|
|
102
|
+
over an available native bridge.
|
|
103
|
+
|
|
104
|
+
An explicit nonempty string `subscriptionKey` supplies the HTTP credential.
|
|
105
|
+
When it is omitted, Mail resolves the current canonical User entity at the actual HTTP delivery,
|
|
106
|
+
waits for its existing `load()` operation, and reads `subscription_key`.
|
|
107
|
+
An injected `options.user` retains precedence, including an explicit `null`.
|
|
108
|
+
Mail does not retain a separate User instance, so later deliveries observe a
|
|
109
|
+
replacement canonical User. This adds no page-startup wait, polling, new
|
|
110
|
+
credential storage, or migration of saved data.
|
|
111
|
+
|
|
112
|
+
An absent key, `null`, or an empty string omits the Authorization header; the
|
|
113
|
+
transport does not block initial setup because a key is missing. Explicit
|
|
114
|
+
`subscriptionKey: null` or `subscriptionKey: ''` also skips User lookup.
|
|
115
|
+
A supplied value of another type is rejected. A gateway with subscription
|
|
116
|
+
verification configured owns rejection of requests without a usable key.
|
|
117
|
+
|
|
118
|
+
For a caller-owned key and shared endpoint:
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
globalThis.arcane.config.mail = {
|
|
122
|
+
appName: 'Another application',
|
|
123
|
+
subscriptionKey: currentSubscriptionKey,
|
|
124
|
+
endpoint: 'https://mail.example.test/v1/mail'
|
|
125
|
+
};
|
|
126
|
+
```
|
|
98
127
|
|
|
99
128
|
The ordinary browser transport has no automatic request deadline and reads the
|
|
100
129
|
complete gateway response. A caller may explicitly supply a positive
|
|
@@ -107,7 +136,7 @@ accepted the request.
|
|
|
107
136
|
`Mail.send(to, subject, payload, messageStyle, messageType)` preserves the
|
|
108
137
|
existing signature. `messageType` is `error`, `report`, or `crisis_detected`.
|
|
109
138
|
Report and crisis mail require at least one recipient; error mail may use the
|
|
110
|
-
gateway's configured
|
|
139
|
+
gateway's configured fallback recipients.
|
|
111
140
|
|
|
112
141
|
In a browser, the module owns the one `window.mail` singleton. An explicit
|
|
113
142
|
`new Mail(config, options)` may configure that owned singleton only before its
|
|
@@ -124,9 +153,10 @@ option `{includeContext:true}`, every message also captures
|
|
|
124
153
|
current User entity and add its `username`, `email`, `language`, and `phone`
|
|
125
154
|
values to the locally rendered content. Those fields are then stored and sent
|
|
126
155
|
unencrypted as part of the message, so the application owns consent, purpose,
|
|
127
|
-
recipient scope, retention, and disclosure. Without that option, Mail
|
|
128
|
-
|
|
129
|
-
|
|
156
|
+
recipient scope, retention, and disclosure. Without that option, Mail adds no
|
|
157
|
+
path/profile fields to the report. HTTP subscription lookup may still load
|
|
158
|
+
the User at delivery without copying profile fields into the report. The
|
|
159
|
+
generated `source_at` timestamp, caller-supplied payload, subject, type, and recipients
|
|
130
160
|
remain part of the requested report in either mode.
|
|
131
161
|
|
|
132
162
|
The public `Mail` integration requires its compatible DBOPFS adapter. Before the
|
|
@@ -145,8 +175,9 @@ durable claims then belong to that adapter's implemented `get`, `set`,
|
|
|
145
175
|
default uses `navigator.locks`; when that cross-context authority is absent,
|
|
146
176
|
the outbox reports `MAIL_OUTBOX_LOCK_UNAVAILABLE` and does not start the drain.
|
|
147
177
|
|
|
148
|
-
|
|
149
|
-
|
|
178
|
+
Start `mail.start()` during application startup so pre-existing records are
|
|
179
|
+
scanned even when the application does not send a new report. Observe its
|
|
180
|
+
completion or error without holding page rendering behind the drain. The first
|
|
150
181
|
`send()` also starts the lifecycle if needed.
|
|
151
182
|
|
|
152
183
|
| Mail method/property | Contract |
|
|
@@ -218,9 +249,14 @@ boundary.
|
|
|
218
249
|
Mail publishes complete semantic events through the SDK singleton event
|
|
219
250
|
authority. Public detail includes the full mutable durable record, serialized
|
|
220
251
|
report, result or failure, provider details, and complete drain inventory
|
|
221
|
-
available at that transition. It never includes the Resend API key or
|
|
222
|
-
key. Listener exceptions are observational and cannot change a
|
|
223
|
-
operation result.
|
|
252
|
+
available at that transition. It never includes the Resend API key or
|
|
253
|
+
subscription key. Listener exceptions are observational and cannot change a
|
|
254
|
+
committed mail operation result.
|
|
255
|
+
|
|
256
|
+
Provider IDs, provider codes, and nonempty failure codes retain their complete
|
|
257
|
+
string values through delivery, persistence, and retrieval. They have no SDK
|
|
258
|
+
character grammar. Missing values retain the existing fallback behavior.
|
|
259
|
+
Request IDs and outbox filenames retain their existing contracts.
|
|
224
260
|
|
|
225
261
|
When both an explicit endpoint and native `Arcane.mail.send` exist, Mail uses
|
|
226
262
|
the configured HTTP endpoint so the authenticated SDK gateway can return its
|
|
@@ -232,7 +268,7 @@ unavailability is a non-ambiguous retryable failure. Once a valid accepted
|
|
|
232
268
|
result has returned, a racing lifecycle cancellation cannot erase that
|
|
233
269
|
committed acceptance result.
|
|
234
270
|
|
|
235
|
-
## Operate the CLI and
|
|
271
|
+
## Operate the CLI and gateway
|
|
236
272
|
|
|
237
273
|
Store one Resend key under a local profile:
|
|
238
274
|
|
|
@@ -251,19 +287,20 @@ Perform one provider attempt directly from the SDK CLI:
|
|
|
251
287
|
arcane mail send --profile arcane-dev --from "Arcane <verified@example.com>" --report-key <stable-id> --report-stdin
|
|
252
288
|
```
|
|
253
289
|
|
|
254
|
-
The redirected UTF-8 JSON
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
290
|
+
The redirected UTF-8 JSON object is read completely. Its fields and values
|
|
291
|
+
are retained; the Resend adapter removes the SDK's `type` routing field,
|
|
292
|
+
uses `from` when configured (otherwise the report or provider template supplies
|
|
293
|
+
the sender), and applies configured error-recipient fallback
|
|
294
|
+
when applicable. Resend evaluates its own required provider fields. Message
|
|
295
|
+
content is not accepted in argv. Programmatic results and observer events
|
|
296
|
+
preserve the complete report, provider request, provider response, and error detail while
|
|
260
297
|
never exposing either credential.
|
|
261
298
|
|
|
262
|
-
The caller must create and retain a
|
|
299
|
+
The caller must create and retain a nonempty `--report-key` before the
|
|
263
300
|
attempt. It is the Resend idempotency key and may be reused only with the same
|
|
264
301
|
serialized report content for an intentional retry or reconciliation. The
|
|
265
302
|
CLI performs exactly one attempt and never retries automatically. Exit zero
|
|
266
|
-
requires a successful Resend response containing a
|
|
303
|
+
requires a successful Resend response containing a nonempty string provider id; that is
|
|
267
304
|
provider acceptance, not an inbox-delivery claim. Timeout, connection loss, or
|
|
268
305
|
cancellation after the provider attempt begins is returned as an ambiguous
|
|
269
306
|
nonzero outcome because the provider may already have accepted the request.
|
|
@@ -272,16 +309,80 @@ For both CLI mail operations, `--request-timeout` accepts 1 through 2147483647
|
|
|
272
309
|
milliseconds, the Node timer range. When omitted, the SDK adds no provider
|
|
273
310
|
deadline.
|
|
274
311
|
|
|
275
|
-
Start the
|
|
312
|
+
Start the gateway:
|
|
276
313
|
|
|
277
314
|
```text
|
|
278
|
-
arcane mail serve --profile arcane-dev --from "Arcane <verified@example.com>"
|
|
315
|
+
arcane mail serve --profile arcane-dev --from "Arcane <verified@example.com>"
|
|
279
316
|
```
|
|
280
317
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
318
|
+
The default listener is `0.0.0.0:8025`; `--host` and `--port` select its bind
|
|
319
|
+
address and port. The server can serve callers from multiple domains on the
|
|
320
|
+
same machine. Route the page's `/v1/mail` to this listener, or configure an
|
|
321
|
+
explicit shared endpoint in the caller.
|
|
322
|
+
|
|
323
|
+
`--app` is an optional server event label and does not restrict incoming
|
|
324
|
+
application names. `--from` is an optional shared sender override; omit it to
|
|
325
|
+
preserve each report's sender or its provider template's default.
|
|
326
|
+
`--origin` selects an exact allowed caller origin; the
|
|
327
|
+
programmatic `origin` option also accepts an array for multiple origins. With
|
|
328
|
+
no origins configured, the gateway accepts an Origin matching its request
|
|
329
|
+
Host using HTTP or HTTPS. Requests without Origin continue normally.
|
|
330
|
+
Cross-origin preflight permits `Content-Type`, `Idempotency-Key`, `X-Mail-App`,
|
|
331
|
+
and `Authorization`. This is origin configuration, not a loopback policy.
|
|
332
|
+
|
|
333
|
+
`--allow-to` explicitly limits recipients when supplied. With it omitted, the
|
|
334
|
+
gateway imposes no recipient allowlist. When configured, the list applies to
|
|
335
|
+
every recipient in the resolved `to`, `cc`, and `bcc` fields, whether supplied
|
|
336
|
+
as a string or an array. Sender, recipient, subject, and body
|
|
337
|
+
values are not trimmed, lowercased, or filtered by an SDK email grammar at
|
|
338
|
+
the gateway. Error reports with an empty `to` array use configured fallback
|
|
339
|
+
recipients; other report content is preserved.
|
|
340
|
+
|
|
341
|
+
## Configure subscription verification
|
|
342
|
+
|
|
343
|
+
Subscription verification is disabled during initial setup when
|
|
344
|
+
`verifySubscription` is omitted. The server reports
|
|
345
|
+
`callerAuthentication: 'none'` and can perform mail delivery without a
|
|
346
|
+
subscription key. Starting the ordinary CLI gateway uses this mode. This does
|
|
347
|
+
not claim that a subscription was checked.
|
|
348
|
+
|
|
349
|
+
The hosting process enables verification by supplying the programmatic
|
|
350
|
+
`verifySubscription` function through
|
|
351
|
+
`createToolchain().mail({action:'serve', verifySubscription, ...options})`.
|
|
352
|
+
The server then reports `callerAuthentication: 'subscription'`. The callback
|
|
353
|
+
contract is:
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
verifySubscription({appName, subscriptionKey, signal})
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
It may return a promise. Return exactly `true` for a valid subscription;
|
|
360
|
+
return `false` for an invalid one, and throw when the verifier service fails.
|
|
361
|
+
The gateway also treats any other returned value as invalid. `appName` is the
|
|
362
|
+
exact incoming `X-Mail-App` value, `subscriptionKey` is the incoming Bearer
|
|
363
|
+
key, and `signal` follows the request lifecycle. These control fields stay
|
|
364
|
+
separate from the mail report and Resend payload. The verifier runs for each
|
|
365
|
+
POST request before any provider attempt; results are not cached.
|
|
366
|
+
|
|
367
|
+
| Configured-verifier outcome | Gateway response |
|
|
368
|
+
| --- | --- |
|
|
369
|
+
| Missing, empty, or repeated `X-Mail-App` | `400 mail_invalid_headers` |
|
|
370
|
+
| Missing or malformed Bearer credential | `401 mail_subscription_required` |
|
|
371
|
+
| Callback returns anything except `true` | `401 mail_subscription_invalid` |
|
|
372
|
+
| Callback throws | `503 mail_subscription_verification_failed`, retryable with the configured `retryableDelayMs` |
|
|
373
|
+
| Request cancelled while verifying | `408 mail_request_cancelled`, with no provider attempt |
|
|
374
|
+
|
|
375
|
+
The `401` responses include `WWW-Authenticate: Bearer`. A successful callback
|
|
376
|
+
permits the existing mail-delivery path; it is not itself a mail-acceptance
|
|
377
|
+
result. The SDK supplies no default verification URL or built-in Stripe
|
|
378
|
+
endpoint adapter. Connecting the actual subscription endpoint is a separate
|
|
379
|
+
hosting integration.
|
|
380
|
+
|
|
381
|
+
CLI startup output says `Subscription verification: disabled` or
|
|
382
|
+
`Subscription verification: configured`. Structured `server.ready` output
|
|
383
|
+
includes the corresponding `callerAuthentication` value.
|
|
384
|
+
|
|
385
|
+
## Gateway request lifecycle
|
|
285
386
|
|
|
286
387
|
The gateway uses the published `node-http-server` instance lifecycle. Its raw
|
|
287
388
|
request hook hands the original request and response directly to the mail
|
|
@@ -289,17 +390,15 @@ handler before body parsing or static routing. Socket inactivity timeout remains
|
|
|
289
390
|
disabled; caller-selected mail deadlines, cancellation, and complete responses
|
|
290
391
|
remain owned by the mail handler.
|
|
291
392
|
|
|
292
|
-
The gateway
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
- one fixed Resend endpoint with the stable Arcane report key forwarded as
|
|
300
|
-
`Idempotency-Key`.
|
|
393
|
+
The gateway handles POST requests to `/v1/mail` and the corresponding OPTIONS
|
|
394
|
+
preflight. JSON parsing owns request readability; the gateway does not reject
|
|
395
|
+
a parseable body because of its Content-Type spelling. One nonempty
|
|
396
|
+
`Idempotency-Key` is forwarded unchanged to the fixed Resend endpoint. The
|
|
397
|
+
browser and gateway preserve complete request and response content without
|
|
398
|
+
body-size gates. With no event observer, the gateway does not construct event
|
|
399
|
+
payloads or parse a second provider-request representation for observation.
|
|
301
400
|
|
|
302
|
-
The gateway returns `202` only after Resend returns a
|
|
401
|
+
The gateway returns `202` only after Resend returns a nonempty string provider id.
|
|
303
402
|
Transport loss, an explicit caller-selected timeout, an invalid success body,
|
|
304
403
|
or an unreadable provider response returns an explicit uncertain result and never claims
|
|
305
404
|
delivery. Rate limits, concurrent idempotency requests, permanent validation
|
|
@@ -308,8 +407,8 @@ results with the complete available provider response or error detail.
|
|
|
308
407
|
|
|
309
408
|
## Operational verification
|
|
310
409
|
|
|
311
|
-
The focused SDK tests use
|
|
312
|
-
and
|
|
410
|
+
The focused SDK tests use synthetic keys, addresses, responses, storage,
|
|
411
|
+
and local HTTP requests. They do not contact Resend or send email. A live
|
|
313
412
|
acceptance send is a separate operational boundary: use a disposable message,
|
|
314
|
-
the
|
|
413
|
+
the selected recipient configuration and credential profile, then verify both the
|
|
315
414
|
gateway's provider-acceptance id and the intended inbox outcome.
|