@volter/twin-postmark 0.1.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.
Files changed (42) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +144 -0
  3. package/client/postmark-mirror.css +79 -0
  4. package/client/postmark-mirror.tsx +221 -0
  5. package/dist/client/postmark-mirror.bundle.js +321 -0
  6. package/dist/client/postmark-mirror.css +79 -0
  7. package/dist/client/postmark-mirror.d.ts +18 -0
  8. package/dist/client/postmark-mirror.js +153 -0
  9. package/dist/client/postmark-mirror.tsx +221 -0
  10. package/dist/src/cli.d.ts +2 -0
  11. package/dist/src/cli.js +31 -0
  12. package/dist/src/index.d.ts +10 -0
  13. package/dist/src/index.js +54 -0
  14. package/dist/src/postmark-capabilities.d.ts +12 -0
  15. package/dist/src/postmark-capabilities.js +1502 -0
  16. package/dist/src/postmark-conformance.d.ts +33 -0
  17. package/dist/src/postmark-conformance.js +265 -0
  18. package/dist/src/postmark-connector.d.ts +167 -0
  19. package/dist/src/postmark-connector.js +251 -0
  20. package/dist/src/postmark-events.d.ts +85 -0
  21. package/dist/src/postmark-events.js +169 -0
  22. package/dist/src/postmark-mirror-ui.d.ts +58 -0
  23. package/dist/src/postmark-mirror-ui.js +207 -0
  24. package/dist/src/postmark-perform-harness.d.ts +9 -0
  25. package/dist/src/postmark-perform-harness.js +24 -0
  26. package/dist/src/postmark-server.d.ts +14 -0
  27. package/dist/src/postmark-server.js +29 -0
  28. package/dist/src/postmark-twin.d.ts +82 -0
  29. package/dist/src/postmark-twin.js +1575 -0
  30. package/dist/test-fixtures/postmark-swagger-operations.json +846 -0
  31. package/package.json +76 -0
  32. package/src/cli.ts +29 -0
  33. package/src/index.ts +89 -0
  34. package/src/postmark-capabilities.ts +1737 -0
  35. package/src/postmark-conformance.ts +282 -0
  36. package/src/postmark-connector.ts +312 -0
  37. package/src/postmark-events.ts +189 -0
  38. package/src/postmark-mirror-ui.ts +213 -0
  39. package/src/postmark-perform-harness.ts +21 -0
  40. package/src/postmark-server.ts +37 -0
  41. package/src/postmark-twin.ts +1520 -0
  42. package/test-fixtures/postmark-swagger-operations.json +846 -0
@@ -0,0 +1,1502 @@
1
+ // Postmark capability manifest — the EXPECTED REAL-PRODUCT SURFACE (the target), authored
2
+ // TOP-DOWN from what the Postmark API actually does — NOT from what this twin has built.
3
+ //
4
+ // THE DENOMINATOR. Postmark's public API is ~86 endpoints across 15 documented groups (the
5
+ // docs sidebar order: Email · Bulk · Templates · Bounce · Server · Servers · Message Streams
6
+ // · Messages · Domains · Sender Signatures · Stats · Inbound Rule Triggers · Webhooks ·
7
+ // Suppressions · Data Removals). Every one is enumerated below, whether or not this twin has
8
+ // reached it. `verify()` is ground truth; `expected: 'done'` appears ONLY where a real,
9
+ // failable verify proves the behavior, so a broken claim surfaces as a regression.
10
+ //
11
+ // AREAS vs GROUPS — they are deliberately NOT 1:1, and `POSTMARK_AREAS` is the smaller set.
12
+ // **Bulk** has no area of its own: its two endpoints are approval-gated surface this twin does
13
+ // not model, so they are filed as `todo`s under the `email` area rather than given a group
14
+ // that would then have to be justified. Everything else maps one group → one area, plus four
15
+ // TWIN-side areas (auth · honesty · ui · connector) that are rungs, not vendor groups.
16
+ //
17
+ // HONEST READING OF THE COVERAGE NUMBER. This pack reads high for a new twin, and part of
18
+ // that is real breadth and part of it is granularity: built areas are enumerated per-operation
19
+ // while some unbuilt ones (notably the 13 `/stats/*` endpoints) are still enumerated per-family.
20
+ // Evening that out is tracked work, and doing it will make the percentage DROP — which is
21
+ // success, not regression (§6). Roughly a fifth of the `done` numerator is also the twin
22
+ // auditing itself (auth · honesty · ui · connector rungs) rather than vendor endpoints.
23
+ //
24
+ // The area census below is the vendor's own docs-nav grouping, enumerated top-down from the
25
+ // published API reference — NOT derived from the entries in this file (which would make the
26
+ // bijection a tautology, per ADDING_A_TWIN §6).
27
+ import { mkdtempSync, rmSync } from 'node:fs';
28
+ import { tmpdir } from 'node:os';
29
+ import { join } from 'node:path';
30
+ import { createElement } from 'react';
31
+ import { renderToStaticMarkup } from 'react-dom/server';
32
+ import { checkCapabilities, uiDataCoupled, verifyBoundary } from '@volter/world-tooling';
33
+ import { handlePostmarkTwinRequest, POSTMARK_ERRORS } from "./postmark-twin.js";
34
+ import { deliveryPlan, webhookHeaders, webhooksFor } from "./postmark-events.js";
35
+ import { mapBounce, mapMessage, mapTemplate, pullPostmarkBounces, pullPostmarkMessages, pullPostmarkMessageStreams, pullPostmarkServer, pullPostmarkTemplates, pullPostmarkWebhooks, pushPostmarkAction, syncPostmarkFromReal, } from "./postmark-connector.js";
36
+ import { performPending } from "./postmark-perform-harness.js";
37
+ import { StatusPill, MessageBody, DeliveryTimeline, NestedLines } from "../client/postmark-mirror.js";
38
+ import { deliveryTimeline, flattenPostmarkValue, messageStatus, POSTMARK_MIRROR_SECTIONS, statusTone, } from "./postmark-mirror-ui.js";
39
+ /** Run a sequence of real Postmark requests against an ISOLATED root; tear it down after. */
40
+ async function withRoot(steps) {
41
+ const root = mkdtempSync(join(tmpdir(), 'postmark-cap-'));
42
+ const h = (s) => handlePostmarkTwinRequest({
43
+ method: s.m, path: s.p,
44
+ body: s.b === undefined ? undefined : JSON.stringify(s.b),
45
+ root, ...(s.h ? { headers: s.h } : {}), ...(s.at ? { occurredAt: s.at } : {}),
46
+ });
47
+ try {
48
+ return await verifyBoundary('postmark.withRoot', () => steps(h, root));
49
+ }
50
+ finally {
51
+ rmSync(root, { recursive: true, force: true });
52
+ }
53
+ }
54
+ const withRootCtx = (fn) => withRoot((h, root) => fn({ h, root }));
55
+ /**
56
+ * Drive real sends with a webhook capture wired in through the HANDLER's own `deliver`
57
+ * injection point — so a webhook verify genuinely routes through the vendor's send-and-
58
+ * deliver code path (a sabotaged handler can never emit anything), and stays fully offline.
59
+ */
60
+ async function withWebhookCapture(steps) {
61
+ const root = mkdtempSync(join(tmpdir(), 'postmark-cap-wh-'));
62
+ const seen = [];
63
+ const deliver = (url, body, headers) => {
64
+ seen.push({ url, body: JSON.parse(body), headers });
65
+ };
66
+ const h = (s) => handlePostmarkTwinRequest({
67
+ method: s.m, path: s.p,
68
+ body: s.b === undefined ? undefined : JSON.stringify(s.b),
69
+ root, deliver, ...(s.h ? { headers: s.h } : {}),
70
+ });
71
+ try {
72
+ return await verifyBoundary('postmark.withWebhookCapture', () => steps(h, seen));
73
+ }
74
+ finally {
75
+ rmSync(root, { recursive: true, force: true });
76
+ }
77
+ }
78
+ const ok = (r) => r.status >= 200 && r.status < 300;
79
+ const field = (r, k) => r.body?.[k];
80
+ const errorCode = (r) => r.body?.ErrorCode;
81
+ /** A failure assertion: the vendor's HTTP status AND its ErrorCode, never one alone. */
82
+ const failedWith = (r, status, code) => r.status === status && errorCode(r) === code;
83
+ const list = (r, key) => (r.body?.[key] ?? []);
84
+ // A minimal valid send payload — the common fixture. NOTE it deliberately carries a Subject
85
+ // even though Postmark does not require one; `postmark.email.subject_optional` proves the
86
+ // vendor's real (surprising) behavior separately.
87
+ const mkMail = (over = {}) => ({
88
+ From: 'Acme <notifications@acme.dev>',
89
+ To: 'ada@twin.test',
90
+ Subject: 'Your login code',
91
+ TextBody: 'Your code is 123456',
92
+ HtmlBody: '<strong>Your code is 123456</strong>',
93
+ ...over,
94
+ });
95
+ function fakePostmarkClient(over = {}) {
96
+ const c = {
97
+ sends: [], templateCreates: [], webhookCreates: [], streamCreates: [],
98
+ getOutboundMessages: async () => ({
99
+ TotalCount: 1,
100
+ Messages: [{
101
+ MessageID: 'real-msg-0001', From: 'real@acme.dev', Subject: 'RealSubject',
102
+ Recipients: ['real-recipient@twin.test'], To: [{ Email: 'real-recipient@twin.test', Name: '' }],
103
+ ReceivedAt: '2026-01-01T00:00:00Z', Status: 'Sent', MessageStream: 'outbound',
104
+ TrackOpens: true, TrackLinks: 'HtmlAndText', Tag: 'real-tag',
105
+ }],
106
+ }),
107
+ getBounces: async () => ({
108
+ TotalCount: 1,
109
+ Bounces: [{ ID: 9001, Type: 'HardBounce', Name: 'Hard bounce', Email: 'real-bounced@twin.test', BouncedAt: '2026-01-01T00:00:00Z', MessageStream: 'outbound', Inactive: true, CanActivate: true, Subject: 'RealBounceSubject' }],
110
+ }),
111
+ getTemplates: async () => ({
112
+ TotalCount: 1,
113
+ Templates: [{ TemplateId: 7001, Name: 'RealTemplate', Alias: 'real-alias', TemplateType: 'Standard', Active: true, Subject: 'Real {{ name }}' }],
114
+ }),
115
+ getMessageStreams: async () => ({
116
+ TotalCount: 1,
117
+ MessageStreams: [{ ID: 'real-stream', Name: 'RealStream', MessageStreamType: 'Broadcast', CreatedAt: '2026-01-01T00:00:00Z' }],
118
+ }),
119
+ getWebhooks: async () => ({ Webhooks: [{ ID: 5001, Url: 'https://real.test/hook', MessageStream: 'outbound', Triggers: { Delivery: { Enabled: true } } }] }),
120
+ getServer: async () => ({ ID: 42, Name: 'RealServer', DeliveryType: 'Live', Color: 'purple', TrackOpens: true }),
121
+ sendEmail: async (m) => { c.sends.push(m); return { MessageID: 'real-sent-0001', ErrorCode: 0, Message: 'OK' }; },
122
+ createTemplate: async (o) => { c.templateCreates.push(o); return { TemplateId: 7777 }; },
123
+ createWebhook: async (o) => { c.webhookCreates.push(o); return { ID: 8888 }; },
124
+ createMessageStream: async (o) => { c.streamCreates.push(o); return { ID: String(o.ID ?? 'real-created') }; },
125
+ ...over,
126
+ };
127
+ return c;
128
+ }
129
+ // ── shorthands (mirror the clerk/stripe/resend manifests) ──
130
+ const done = (id, area, title, dimension, tier, verify) => ({ id, area, title, dimension, tier, expected: 'done', verify });
131
+ const todo = (id, area, title, dimension, tier) => ({ id, area, title, dimension, tier, expected: 'todo' });
132
+ export const POSTMARK_CAPABILITIES = [
133
+ // ── Email API — the headline surface (POST /email is what ships auth OTPs) ────────
134
+ done('postmark.email.send', 'email', 'Email: send (POST /email → MessageID/SubmittedAt/ErrorCode 0)', 'api', 'core', () => withRoot(async (h) => {
135
+ const r = await h({ m: 'POST', p: '/email', b: mkMail() });
136
+ if (!ok(r) || errorCode(r) !== 0 || field(r, 'Message') !== 'OK')
137
+ return false;
138
+ const id = field(r, 'MessageID');
139
+ // Postmark's MessageID is a bare UUID (no vendor prefix) and the response echoes To.
140
+ return typeof id === 'string'
141
+ && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(id)
142
+ && field(r, 'To') === 'ada@twin.test'
143
+ && typeof field(r, 'SubmittedAt') === 'string';
144
+ })),
145
+ done('postmark.email.stored_not_sent', 'email', 'Email: the message is STORED in twin state, never really sent', 'api', 'core', () => withRoot(async (h) => {
146
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail({ Subject: 'Persisted subject', HtmlBody: '<em>persisted-body</em>' }) });
147
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(sent, 'MessageID')}/details` });
148
+ return ok(d) && field(d, 'Subject') === 'Persisted subject' && field(d, 'HtmlBody') === '<em>persisted-body</em>';
149
+ })),
150
+ done('postmark.email.send_text_only', 'email', 'Email: send with TextBody only', 'api', 'core', () => withRoot(async (h) => {
151
+ const r = await h({ m: 'POST', p: '/email', b: { From: 'a@acme.dev', To: 'b@twin.test', Subject: 's', TextBody: 'plain only' } });
152
+ if (!ok(r))
153
+ return false;
154
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(r, 'MessageID')}/details` });
155
+ return ok(d) && field(d, 'TextBody') === 'plain only' && field(d, 'HtmlBody') === null;
156
+ })),
157
+ done('postmark.email.send_cc_bcc_reply_to', 'email', 'Email: Cc/Bcc/ReplyTo/Headers/Metadata/Tag are carried', 'api', 'common', () => withRoot(async (h) => {
158
+ const r = await h({ m: 'POST', p: '/email', b: mkMail({
159
+ Cc: 'cc@twin.test', Bcc: 'Blind <bcc@twin.test>', ReplyTo: 'reply@acme.dev',
160
+ Tag: 'welcome', Metadata: { tenant: 'acme' }, Headers: [{ Name: 'X-Twin', Value: '1' }],
161
+ }) });
162
+ if (!ok(r) || field(r, 'Cc') !== 'cc@twin.test' || field(r, 'Bcc') !== 'bcc@twin.test')
163
+ return false;
164
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(r, 'MessageID')}/details` });
165
+ const cc = field(d, 'Cc');
166
+ const bcc = field(d, 'Bcc');
167
+ return ok(d) && cc?.[0]?.Email === 'cc@twin.test'
168
+ && bcc?.[0]?.Email === 'bcc@twin.test' && bcc?.[0]?.Name === 'Blind'
169
+ && field(d, 'Tag') === 'welcome' && field(d, 'Metadata')?.tenant === 'acme';
170
+ })),
171
+ done('postmark.email.recipient_string_parsing', 'email', 'Email: comma-separated `Name <addr>` recipient parsing', 'api', 'core', () => withRoot(async (h) => {
172
+ const r = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'Ada Lovelace <ada@twin.test>, grace@twin.test' }) });
173
+ if (!ok(r) || field(r, 'To') !== 'ada@twin.test, grace@twin.test')
174
+ return false;
175
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(r, 'MessageID')}/details` });
176
+ const to = field(d, 'To');
177
+ return to?.length === 2 && to[0].Name === 'Ada Lovelace' && to[0].Email === 'ada@twin.test' && to[1].Email === 'grace@twin.test';
178
+ })),
179
+ done('postmark.email.validate_missing_from', 'email', 'Email: 422 ErrorCode 300 on missing/invalid From (validated FIRST)', 'api', 'core', () => withRoot(async (h) => {
180
+ const missing = await h({ m: 'POST', p: '/email', b: { To: 'x@twin.test', Subject: 's', TextBody: 't' } });
181
+ // [WIRE] an EMPTY body also answers the From error — From is validated before recipients.
182
+ const empty = await h({ m: 'POST', p: '/email', b: {} });
183
+ const malformed = await h({ m: 'POST', p: '/email', b: mkMail({ From: 'not-an-address' }) });
184
+ return failedWith(missing, 422, 300) && field(missing, 'Message') === "Invalid 'From' value."
185
+ && failedWith(empty, 422, 300) && field(empty, 'Message') === "Invalid 'From' value."
186
+ && failedWith(malformed, 422, 300);
187
+ })),
188
+ done('postmark.email.validate_zero_recipients', 'email', 'Email: 422 ErrorCode 300 "Zero recipients specified."', 'api', 'core', () => withRoot(async (h) => {
189
+ const r = await h({ m: 'POST', p: '/email', b: { From: 'a@acme.dev', Subject: 's', TextBody: 't' } });
190
+ const blank = await h({ m: 'POST', p: '/email', b: { From: 'a@acme.dev', To: '', Subject: 's', TextBody: 't' } });
191
+ return failedWith(r, 422, 300) && field(r, 'Message') === 'Zero recipients specified.' && failedWith(blank, 422, 300);
192
+ })),
193
+ done('postmark.email.validate_missing_body', 'email', 'Email: 422 ErrorCode 300 when neither HtmlBody nor TextBody is given', 'api', 'core', () => withRoot(async (h) => {
194
+ const r = await h({ m: 'POST', p: '/email', b: { From: 'a@acme.dev', To: 'b@twin.test', Subject: 's' } });
195
+ return failedWith(r, 422, 300) && String(field(r, 'Message')).includes('TextBody');
196
+ })),
197
+ done('postmark.email.subject_optional', 'email', 'Email: Subject is NOT required (a send without one succeeds)', 'api', 'common', () => withRoot(async (h) => {
198
+ // Verified against the LIVE api.postmarkapp.com with the public test token during this
199
+ // pack's build: Postmark accepts a send with no Subject. Guards the twin against the
200
+ // very common (wrong) assumption that Subject is mandatory.
201
+ const r = await h({ m: 'POST', p: '/email', b: { From: 'a@acme.dev', To: 'b@twin.test', TextBody: 'no subject here' } });
202
+ if (!ok(r) || errorCode(r) !== 0)
203
+ return false;
204
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(r, 'MessageID')}/details` });
205
+ return ok(d) && field(d, 'Subject') === '';
206
+ })),
207
+ done('postmark.email.validate_bad_recipient', 'email', 'Email: 422 ErrorCode 300 on an illegal recipient address', 'api', 'common', () => withRoot(async (h) => {
208
+ const r = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'ada@twin.test, not-an-address' }) });
209
+ return failedWith(r, 422, 300) && String(field(r, 'Message')).includes('not-an-address');
210
+ })),
211
+ done('postmark.email.invalid_json', 'email', 'Email: 422 ErrorCode 402 "Invalid JSON" on a malformed body', 'api', 'common', () => withRoot(async () => {
212
+ const root = mkdtempSync(join(tmpdir(), 'postmark-json-'));
213
+ try {
214
+ const r = await handlePostmarkTwinRequest({ method: 'POST', path: '/email', body: '{"From":', root });
215
+ return failedWith(r, 422, 402) && field(r, 'Message') === 'Invalid JSON';
216
+ }
217
+ finally {
218
+ rmSync(root, { recursive: true, force: true });
219
+ }
220
+ })),
221
+ done('postmark.email.attachments', 'email', 'Email: attachments (Name/Content/ContentType) + their 422', 'api', 'common', () => withRoot(async (h) => {
222
+ const good = await h({ m: 'POST', p: '/email', b: mkMail({ Attachments: [{ Name: 'receipt.pdf', Content: 'dGVzdA==', ContentType: 'application/pdf' }] }) });
223
+ if (!ok(good))
224
+ return false;
225
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(good, 'MessageID')}/details` });
226
+ const bad = await h({ m: 'POST', p: '/email', b: mkMail({ Attachments: [{ Name: 'x.pdf' }] }) });
227
+ return ok(d) && field(d, 'Attachments')?.includes('receipt.pdf') && failedWith(bad, 422, 300);
228
+ })),
229
+ done('postmark.email.stream_routing', 'email', 'Email: MessageStream routing + 422 ErrorCode 1235 for an unknown stream', 'api', 'core', () => withRoot(async (h) => {
230
+ const created = await h({ m: 'POST', p: '/message-streams', b: { ID: 'promos', Name: 'Promos', MessageStreamType: 'Broadcast' } });
231
+ if (!ok(created))
232
+ return false;
233
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail({ MessageStream: 'promos' }) });
234
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(sent, 'MessageID')}/details` });
235
+ const missing = await h({ m: 'POST', p: '/email', b: mkMail({ MessageStream: 'does-not-exist' }) });
236
+ return ok(sent) && field(d, 'MessageStream') === 'promos' && failedWith(missing, 422, 1235);
237
+ })),
238
+ done('postmark.email.default_stream', 'email', 'Email: a send with no MessageStream lands on `outbound`', 'api', 'core', () => withRoot(async (h) => {
239
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail() });
240
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(sent, 'MessageID')}/details` });
241
+ return ok(d) && field(d, 'MessageStream') === 'outbound';
242
+ })),
243
+ done('postmark.email.batch', 'email', 'Email: POST /email/batch → one result per message (bare array)', 'api', 'core', () => withRoot(async (h) => {
244
+ const r = await h({ m: 'POST', p: '/email/batch', b: [mkMail({ To: 'one@twin.test' }), mkMail({ To: 'two@twin.test' })] });
245
+ const arr = r.body;
246
+ if (!ok(r) || !Array.isArray(arr) || arr.length !== 2)
247
+ return false;
248
+ if (arr[0].To !== 'one@twin.test' || arr[1].To !== 'two@twin.test')
249
+ return false;
250
+ if (arr.some((e) => e.ErrorCode !== 0 || typeof e.MessageID !== 'string'))
251
+ return false;
252
+ // Both really landed in twin state.
253
+ const listed = await h({ m: 'GET', p: '/messages/outbound' });
254
+ return field(listed, 'TotalCount') === 2;
255
+ })),
256
+ done('postmark.email.batch_per_message_errors', 'email', 'Email: batch reports PER-MESSAGE errors inside a 200, not a top-level 422', 'api', 'core', () => withRoot(async (h) => {
257
+ const r = await h({ m: 'POST', p: '/email/batch', b: [mkMail({ To: 'good@twin.test' }), { To: 'x@twin.test', Subject: 's', TextBody: 't' }] });
258
+ const arr = r.body;
259
+ if (r.status !== 200 || !Array.isArray(arr) || arr.length !== 2)
260
+ return false;
261
+ // Only the good one was actually enacted.
262
+ const listed = await h({ m: 'GET', p: '/messages/outbound' });
263
+ return arr[0].ErrorCode === 0 && arr[1].ErrorCode === 300 && field(listed, 'TotalCount') === 1;
264
+ })),
265
+ done('postmark.email.batch_cap', 'email', 'Email: 422 ErrorCode 410 over the 500-message batch cap', 'api', 'common', () => withRoot(async (h) => {
266
+ const over = Array.from({ length: 501 }, (_v, i) => mkMail({ To: `r${i}@twin.test` }));
267
+ const r = await h({ m: 'POST', p: '/email/batch', b: over });
268
+ const empty = await h({ m: 'POST', p: '/email/batch', b: [] });
269
+ return failedWith(r, 422, 410) && String(field(r, 'Message')).includes('500') && failedWith(empty, 422, 300);
270
+ })),
271
+ done('postmark.email.inactive_recipient', 'email', 'Email: 422 ErrorCode 406 sending to a SUPPRESSED recipient (dirty state)', 'api', 'core', () => withRoot(async (h) => {
272
+ // A DIRTY-STATE capability: it can only fail over PRIOR state. First send bounces and
273
+ // auto-suppresses the address; the SECOND send to the same address must be refused with
274
+ // the vendor's inactive-recipient error. A fresh-root-only test can never reach this.
275
+ const first = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-me@twin.test' }) });
276
+ if (!ok(first))
277
+ return false;
278
+ const second = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-me@twin.test' }) });
279
+ if (!failedWith(second, 422, 406) || !String(field(second, 'Message')).includes('bounce-me@twin.test'))
280
+ return false;
281
+ // …and only ONE message was ever stored (the refusal enacted nothing).
282
+ const listed = await h({ m: 'GET', p: '/messages/outbound' });
283
+ return field(listed, 'TotalCount') === 1;
284
+ })),
285
+ done('postmark.email.send_with_template', 'email', 'Email: POST /email/withTemplate renders a stored template', 'api', 'core', () => withRoot(async (h) => {
286
+ const t = await h({ m: 'POST', p: '/templates', b: { Name: 'Welcome', Alias: 'welcome', Subject: 'Hi {{ name }}', HtmlBody: '<p>Code {{ code }}</p>', TextBody: 'Code {{ code }}' } });
287
+ if (!ok(t))
288
+ return false;
289
+ const sent = await h({ m: 'POST', p: '/email/withTemplate', b: { From: 'a@acme.dev', To: 'b@twin.test', TemplateAlias: 'welcome', TemplateModel: { name: 'Ada', code: '424242' } } });
290
+ if (!ok(sent) || errorCode(sent) !== 0)
291
+ return false;
292
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(sent, 'MessageID')}/details` });
293
+ return field(d, 'Subject') === 'Hi Ada' && field(d, 'HtmlBody') === '<p>Code 424242</p>' && field(d, 'TextBody') === 'Code 424242';
294
+ })),
295
+ done('postmark.email.send_with_template_by_id', 'email', 'Email: withTemplate resolves by numeric TemplateId too', 'api', 'common', () => withRoot(async (h) => {
296
+ const t = await h({ m: 'POST', p: '/templates', b: { Name: 'ById', Subject: 'Sub {{ x }}', TextBody: 'Body {{ x }}' } });
297
+ const sent = await h({ m: 'POST', p: '/email/withTemplate', b: { From: 'a@acme.dev', To: 'b@twin.test', TemplateId: field(t, 'TemplateId'), TemplateModel: { x: 'resolved' } } });
298
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(sent, 'MessageID')}/details` });
299
+ return ok(sent) && field(d, 'Subject') === 'Sub resolved';
300
+ })),
301
+ done('postmark.email.template_not_found', 'email', 'Email: 422 ErrorCode 1101 for an unknown / unspecified template', 'api', 'core', () => withRoot(async (h) => {
302
+ const unknown = await h({ m: 'POST', p: '/email/withTemplate', b: { From: 'a@acme.dev', To: 'b@twin.test', TemplateAlias: 'nope', TemplateModel: {} } });
303
+ const neither = await h({ m: 'POST', p: '/email/withTemplate', b: { From: 'a@acme.dev', To: 'b@twin.test', TemplateModel: {} } });
304
+ return failedWith(unknown, 422, 1101) && failedWith(neither, 422, 1101);
305
+ })),
306
+ done('postmark.email.template_unknown_token_renders_empty', 'email', 'Email: an unmodeled template token renders EMPTY (non-strict Mustachio)', 'api', 'niche', () => withRoot(async (h) => {
307
+ await h({ m: 'POST', p: '/templates', b: { Name: 'Partial', Alias: 'partial', Subject: 'S', TextBody: 'a[{{ known }}]b[{{ unknown }}]c' } });
308
+ const sent = await h({ m: 'POST', p: '/email/withTemplate', b: { From: 'a@acme.dev', To: 'b@twin.test', TemplateAlias: 'partial', TemplateModel: { known: 'X' } } });
309
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(sent, 'MessageID')}/details` });
310
+ return ok(sent) && field(d, 'TextBody') === 'a[X]b[]c';
311
+ })),
312
+ done('postmark.email.batch_with_templates', 'email', 'Email: POST /email/batchWithTemplates ({Messages:[…]} → array)', 'api', 'common', () => withRoot(async (h) => {
313
+ await h({ m: 'POST', p: '/templates', b: { Name: 'Batch', Alias: 'batch-t', Subject: 'S {{ n }}', TextBody: 'B {{ n }}' } });
314
+ const r = await h({ m: 'POST', p: '/email/batchWithTemplates', b: { Messages: [
315
+ { From: 'a@acme.dev', To: 'one@twin.test', TemplateAlias: 'batch-t', TemplateModel: { n: '1' } },
316
+ { From: 'a@acme.dev', To: 'two@twin.test', TemplateAlias: 'batch-t', TemplateModel: { n: '2' } },
317
+ ] } });
318
+ const arr = r.body;
319
+ if (!ok(r) || arr?.length !== 2 || arr.some((e) => e.ErrorCode !== 0))
320
+ return false;
321
+ const d = await h({ m: 'GET', p: `/messages/outbound/${arr[1].MessageID}/details` });
322
+ return field(d, 'Subject') === 'S 2';
323
+ })),
324
+ // §9 DEMOTED from `done`: answering the vendor's approval refusal (ErrorCode 14) on
325
+ // /email/bulk is the twin declining to model the Bulk API, not modeling it. The behavior
326
+ // is still correct and still tested (postmark-twin.test.ts), but it is not coverage.
327
+ todo('postmark.email.bulk_requires_approval', 'email', 'Bulk API: POST /email/bulk approval gate (ErrorCode 14)', 'api', 'niche'),
328
+ todo('postmark.email.recipient_cap_50', 'email', 'Email: enforce the 50-recipients-per-field cap with the vendor message', 'api', 'niche'),
329
+ todo('postmark.email.payload_size_413', 'email', 'Email: 413 over the 10MB (single) / 50MB (batch) payload cap', 'api', 'niche'),
330
+ todo('postmark.email.inline_css_for_templates', 'email', 'Email: InlineCss option on templated sends', 'api', 'niche'),
331
+ // Grounded in the vendor's Bulk API docs page and its own error codes 12 ("Bulk send not
332
+ // found") and 14 ("requires approval to access") — real surface the pinned SDK does not
333
+ // implement. Filed as todo, and the twin answers the vendor's 14 on the two published
334
+ // routes only (postmark-twin.ts), so the rest of the subtree still 404s.
335
+ todo('postmark.email.bulk_send_lifecycle', 'email', 'Bulk API: POST /email/bulk + GET /email/bulk/{id} job lifecycle', 'api', 'niche'),
336
+ todo('postmark.email.smtp_transport', 'email', 'Postmark SMTP transport (smtp.postmarkapp.com) — accept mail over SMTP with the server token as the password', 'api', 'common'),
337
+ // ── Messages API (outbound activity) ─────────────────────────────────────────────
338
+ done('postmark.messages.outbound_list', 'messages', 'Messages: GET /messages/outbound → {TotalCount, Messages[]}', 'api', 'core', () => withRoot(async (h) => {
339
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail({ Subject: 'Listed message' }) });
340
+ const r = await h({ m: 'GET', p: '/messages/outbound' });
341
+ const rows = list(r, 'Messages');
342
+ const row = rows.find((x) => x.MessageID === field(sent, 'MessageID'));
343
+ return ok(r) && field(r, 'TotalCount') === 1 && !!row && row.Subject === 'Listed message'
344
+ // the LIST shape deliberately omits bodies + events (the vendor's own split)
345
+ && row.HtmlBody === undefined && row.MessageEvents === undefined;
346
+ })),
347
+ done('postmark.messages.outbound_recipient_shapes', 'messages', 'Messages: the vendor GENUINELY mixes recipient shapes in ONE payload — To/Cc/Bcc are [{Email,Name}] while Recipients is [string] — pinned so neither side ever "fixes" the other into consistency', 'api', 'core', () => withRoot(async (h) => {
348
+ // GROUNDED twice over (2026-08-20): postmarkapp.com/developer/api/messages-api's outbound
349
+ // search example answers `"To":[{"Email":"john.doe@yahoo.com","Name":null}]` NEXT TO
350
+ // `"Recipients":["john.doe@yahoo.com"]`, and the official postmark.js SDK types the same
351
+ // split (`OutboundMessage.To: Recipient[]` vs `Recipients: string[]`). A blind adoption
352
+ // run read this as a twin bug; it is the vendor's real shape, in list AND details alike.
353
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'Ada L <ada@twin.test>', Cc: 'grace@twin.test' }) });
354
+ if (!ok(sent))
355
+ return false;
356
+ const listed = await h({ m: 'GET', p: '/messages/outbound' });
357
+ const row = list(listed, 'Messages').find((x) => x.MessageID === field(sent, 'MessageID'));
358
+ const details = await h({ m: 'GET', p: `/messages/outbound/${field(sent, 'MessageID')}/details` });
359
+ const shapes = (m) => !!m
360
+ // To/Cc: arrays of {Email, Name} OBJECTS…
361
+ && Array.isArray(m.To) && m.To.length === 1
362
+ && m.To[0].Email === 'ada@twin.test' && m.To[0].Name === 'Ada L'
363
+ && Array.isArray(m.Cc) && m.Cc[0].Email === 'grace@twin.test'
364
+ // …while Recipients, in the SAME payload, is plain email STRINGS across To+Cc+Bcc
365
+ && Array.isArray(m.Recipients) && m.Recipients.every((r) => typeof r === 'string')
366
+ && JSON.stringify(m.Recipients) === '["ada@twin.test","grace@twin.test"]';
367
+ return shapes(row) && shapes(details.body);
368
+ })),
369
+ done('postmark.messages.outbound_filters', 'messages', 'Messages: outbound filters (recipient/tag/subject/fromEmail/stream)', 'api', 'common', () => withRoot(async (h) => {
370
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'alpha@twin.test', Tag: 'alpha', Subject: 'Alpha subject' }) });
371
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'beta@twin.test', Tag: 'beta', Subject: 'Beta subject' }) });
372
+ const byRecipient = await h({ m: 'GET', p: '/messages/outbound?recipient=alpha@twin.test' });
373
+ const byTag = await h({ m: 'GET', p: '/messages/outbound?tag=beta' });
374
+ const bySubject = await h({ m: 'GET', p: '/messages/outbound?subject=Alpha' });
375
+ const byStream = await h({ m: 'GET', p: '/messages/outbound?messageStream=broadcast' });
376
+ return field(byRecipient, 'TotalCount') === 1 && list(byRecipient, 'Messages')[0].Tag === 'alpha'
377
+ && field(byTag, 'TotalCount') === 1 && list(byTag, 'Messages')[0].Subject === 'Beta subject'
378
+ && field(bySubject, 'TotalCount') === 1
379
+ && field(byStream, 'TotalCount') === 0;
380
+ })),
381
+ done('postmark.messages.outbound_pagination', 'messages', 'Messages: count/offset pagination keeps TotalCount whole', 'api', 'common', () => withRoot(async (h) => {
382
+ for (let i = 0; i < 3; i++)
383
+ await h({ m: 'POST', p: '/email', b: mkMail({ Subject: `Page ${i}`, To: `p${i}@twin.test` }) });
384
+ const page = await h({ m: 'GET', p: '/messages/outbound?count=2&offset=1' });
385
+ return field(page, 'TotalCount') === 3 && list(page, 'Messages').length === 2;
386
+ })),
387
+ done('postmark.messages.outbound_details', 'messages', 'Messages: GET /messages/outbound/:id/details (bodies + MessageEvents)', 'api', 'core', () => withRoot(async (h) => {
388
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail({ Subject: 'Detailed', TrackOpens: true }) });
389
+ const d = await h({ m: 'GET', p: `/messages/outbound/${field(sent, 'MessageID')}/details` });
390
+ const missing = await h({ m: 'GET', p: '/messages/outbound/00000000-0000-0000-0000-000000000000/details' });
391
+ const events = field(d, 'MessageEvents');
392
+ return ok(d) && field(d, 'Subject') === 'Detailed'
393
+ && Array.isArray(events) && events.some((e) => e.Type === 'Delivered')
394
+ && typeof field(d, 'Body') === 'string'
395
+ && failedWith(missing, 422, 701);
396
+ })),
397
+ done('postmark.messages.outbound_dump', 'messages', 'Messages: GET /messages/outbound/:id/dump → the raw source', 'api', 'niche', () => withRoot(async (h) => {
398
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail({ Subject: 'Dumped', TextBody: 'dump-marker' }) });
399
+ const dump = await h({ m: 'GET', p: `/messages/outbound/${field(sent, 'MessageID')}/dump` });
400
+ const missing = await h({ m: 'GET', p: '/messages/outbound/nope/dump' });
401
+ const raw = String(field(dump, 'Body') ?? '');
402
+ return ok(dump) && raw.includes('Subject: Dumped') && raw.includes(String(field(sent, 'MessageID'))) && failedWith(missing, 422, 701);
403
+ })),
404
+ done('postmark.messages.delivery_events', 'messages', 'Messages: the deterministic offline delivery lifecycle writes MessageEvents', 'api', 'core', () => withRoot(async (h) => {
405
+ const plain = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'plain@twin.test' }) });
406
+ const tracked = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'tracked@twin.test', TrackOpens: true, TrackLinks: 'HtmlAndText' }) });
407
+ const a = await h({ m: 'GET', p: `/messages/outbound/${field(plain, 'MessageID')}/details` });
408
+ const b = await h({ m: 'GET', p: `/messages/outbound/${field(tracked, 'MessageID')}/details` });
409
+ const types = (r) => field(r, 'MessageEvents').map((e) => e.Type);
410
+ // Tracking is opt-in on Postmark: no TrackOpens ⇒ no Open event, ever.
411
+ return JSON.stringify(types(a)) === JSON.stringify(['Delivered'])
412
+ && JSON.stringify(types(b)) === JSON.stringify(['Delivered', 'Opened', 'LinkClicked']);
413
+ })),
414
+ done('postmark.messages.opens', 'messages', 'Messages: GET /messages/outbound/opens (+ per-message)', 'api', 'common', () => withRoot(async (h) => {
415
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'opener@twin.test', TrackOpens: true }) });
416
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'untracked@twin.test' }) }); // no TrackOpens → no open
417
+ const all = await h({ m: 'GET', p: '/messages/outbound/opens' });
418
+ const one = await h({ m: 'GET', p: `/messages/outbound/opens/${field(sent, 'MessageID')}` });
419
+ const rows = list(all, 'Opens');
420
+ return ok(all) && field(all, 'TotalCount') === 1 && rows[0].RecordType === 'Open'
421
+ && rows[0].Recipient === 'opener@twin.test' && field(one, 'TotalCount') === 1;
422
+ })),
423
+ done('postmark.messages.clicks', 'messages', 'Messages: GET /messages/outbound/clicks (+ per-message)', 'api', 'common', () => withRoot(async (h) => {
424
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'clicker@twin.test', TrackLinks: 'HtmlAndText' }) });
425
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'nolinks@twin.test' }) });
426
+ const all = await h({ m: 'GET', p: '/messages/outbound/clicks' });
427
+ const one = await h({ m: 'GET', p: `/messages/outbound/clicks/${field(sent, 'MessageID')}` });
428
+ const rows = list(all, 'Clicks');
429
+ return ok(all) && field(all, 'TotalCount') === 1 && rows[0].RecordType === 'Click'
430
+ && rows[0].ClickLocation === 'HTML' && field(one, 'TotalCount') === 1;
431
+ })),
432
+ done('postmark.messages.inbound_list', 'messages', 'Messages: GET /messages/inbound + /details', 'api', 'common', () => withRoot(async (h) => {
433
+ // Seeded through the twin-only `_twin/inbound` route (real inbound mail is MX-routed and
434
+ // unreachable offline); the READ path under test is Postmark's real endpoint.
435
+ const seeded = await h({ m: 'POST', p: '/_twin/inbound', b: { From: 'reply@customer.test', Subject: 'Re: your invoice', TextBody: 'inbound-body-marker', MailboxHash: 'abc' } });
436
+ if (!ok(seeded))
437
+ return false;
438
+ const all = await h({ m: 'GET', p: '/messages/inbound' });
439
+ const rows = list(all, 'InboundMessages');
440
+ const d = await h({ m: 'GET', p: `/messages/inbound/${field(seeded, 'MessageID')}/details` });
441
+ const missing = await h({ m: 'GET', p: '/messages/inbound/nope/details' });
442
+ return field(all, 'TotalCount') === 1 && rows[0].Subject === 'Re: your invoice'
443
+ // the list shape omits bodies; details carries them
444
+ && rows[0].TextBody === undefined && field(d, 'TextBody') === 'inbound-body-marker'
445
+ && failedWith(missing, 422, 701);
446
+ })),
447
+ done('postmark.messages.inbound_retry', 'messages', 'Messages: PUT /messages/inbound/:id/retry re-queues, bypass 422s on a non-blocked message', 'api', 'niche', () => withRoot(async (h) => {
448
+ const seeded = await h({ m: 'POST', p: '/_twin/inbound', b: { Subject: 'Retryable' } });
449
+ const id = field(seeded, 'MessageID');
450
+ const retried = await h({ m: 'PUT', p: `/messages/inbound/${id}/retry` });
451
+ const after = await h({ m: 'GET', p: `/messages/inbound/${id}/details` });
452
+ // Only a BLOCKED message can be bypassed — this one is Processed, so the vendor 422s.
453
+ const bypass = await h({ m: 'PUT', p: `/messages/inbound/${id}/bypass` });
454
+ return ok(retried) && errorCode(retried) === 0 && field(after, 'Status') === 'Queued' && failedWith(bypass, 422, 701);
455
+ })),
456
+ todo('postmark.messages.inbound_ingest', 'messages', 'Messages: real inbound ingestion via the server inbound address/webhook', 'api', 'common'),
457
+ todo('postmark.messages.outbound_date_filters', 'messages', 'Messages: fromDate/toDate filtering on outbound activity', 'api', 'niche'),
458
+ todo('postmark.messages.tracking_client_filters', 'messages', 'Messages: opens/clicks filtering by client/OS/platform/geo', 'api', 'niche'),
459
+ // ── Bounce API ───────────────────────────────────────────────────────────────────
460
+ done('postmark.bounces.recorded_on_bounce', 'bounces', 'Bounces: a bounced send mints a real Bounce record', 'api', 'core', () => withRoot(async (h) => {
461
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-hard@twin.test', Subject: 'Bounced subject' }) });
462
+ const r = await h({ m: 'GET', p: '/bounces' });
463
+ const b = list(r, 'Bounces')[0];
464
+ return ok(r) && field(r, 'TotalCount') === 1 && !!b
465
+ && b.Type === 'HardBounce' && b.TypeCode === 1 && b.Email === 'bounce-hard@twin.test'
466
+ && b.MessageID === field(sent, 'MessageID') && b.Inactive === true && b.Subject === 'Bounced subject';
467
+ })),
468
+ done('postmark.bounces.spam_complaint', 'bounces', 'Bounces: a spam complaint is recorded and is NOT reactivatable', 'api', 'common', () => withRoot(async (h) => {
469
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'spam-reporter@twin.test' }) });
470
+ const r = await h({ m: 'GET', p: '/bounces' });
471
+ const b = list(r, 'Bounces')[0];
472
+ const activate = await h({ m: 'PUT', p: `/bounces/${b.ID}/activate` });
473
+ return b.Type === 'SpamComplaint' && b.TypeCode === 100001 && b.CanActivate === false
474
+ && failedWith(activate, 422, 1003);
475
+ })),
476
+ done('postmark.bounces.retrieve', 'bounces', 'Bounces: GET /bounces/:id + 422 ErrorCode 1001 for an unknown id', 'api', 'core', () => withRoot(async (h) => {
477
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-one@twin.test' }) });
478
+ const one = await h({ m: 'GET', p: '/bounces/1' });
479
+ const missing = await h({ m: 'GET', p: '/bounces/9999' });
480
+ return ok(one) && field(one, 'ID') === 1 && field(one, 'Email') === 'bounce-one@twin.test'
481
+ && field(one, 'RecordType') === 'Bounce' && failedWith(missing, 422, 1001);
482
+ })),
483
+ done('postmark.bounces.list_filters', 'bounces', 'Bounces: filter by type/emailFilter/inactive', 'api', 'common', () => withRoot(async (h) => {
484
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-a@twin.test' }) });
485
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'spam-b@twin.test' }) });
486
+ const hard = await h({ m: 'GET', p: '/bounces?type=HardBounce' });
487
+ const byEmail = await h({ m: 'GET', p: '/bounces?emailFilter=spam-b' });
488
+ return field(hard, 'TotalCount') === 1 && list(hard, 'Bounces')[0].Email === 'bounce-a@twin.test'
489
+ && field(byEmail, 'TotalCount') === 1 && list(byEmail, 'Bounces')[0].Type === 'SpamComplaint';
490
+ })),
491
+ done('postmark.bounces.dump', 'bounces', 'Bounces: GET /bounces/:id/dump → the raw bounce body', 'api', 'niche', () => withRoot(async (h) => {
492
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-dump@twin.test' }) });
493
+ const dump = await h({ m: 'GET', p: '/bounces/1/dump' });
494
+ const missing = await h({ m: 'GET', p: '/bounces/42/dump' });
495
+ return ok(dump) && String(field(dump, 'Body')).includes('Undelivered Mail') && failedWith(missing, 422, 1001);
496
+ })),
497
+ done('postmark.bounces.activate_restores_sending', 'bounces', 'Bounces: PUT /bounces/:id/activate clears the suppression (dirty state)', 'api', 'common', () => withRoot(async (h) => {
498
+ // A DIRTY-STATE capability spanning three writes: bounce → refusal → reactivate → send.
499
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-again@twin.test' }) });
500
+ const refused = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-again@twin.test' }) });
501
+ if (!failedWith(refused, 422, 406))
502
+ return false;
503
+ const activated = await h({ m: 'PUT', p: '/bounces/1/activate' });
504
+ if (!ok(activated) || field(activated, 'Bounce')?.Inactive !== false)
505
+ return false;
506
+ // Reactivation clears the suppression…
507
+ const dump = await h({ m: 'GET', p: '/message-streams/outbound/suppressions/dump' });
508
+ if (list(dump, 'Suppressions').length !== 0)
509
+ return false;
510
+ // …so the address is accepted again. (It re-bounces immediately — that is correct: the
511
+ // twin's bounce simulator is recipient-driven — so a SECOND bounce row is now minted,
512
+ // which is exactly what a reactivated-then-still-bad address does on the real vendor.)
513
+ const resent = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-again@twin.test' }) });
514
+ const bounces = await h({ m: 'GET', p: '/bounces' });
515
+ return ok(resent) && errorCode(resent) === 0 && field(bounces, 'TotalCount') === 2
516
+ && list(bounces, 'Bounces').map((b) => b.ID).sort().join(',') === '1,2';
517
+ })),
518
+ done('postmark.bounces.delivery_stats', 'bounces', 'Bounces: GET /deliverystats aggregates real twin bounces', 'api', 'core', () => withRoot(async (h) => {
519
+ const before = await h({ m: 'GET', p: '/deliverystats' });
520
+ if (field(before, 'InactiveMails') !== 0 || field(before, 'Bounces')[0].Count !== 0)
521
+ return false;
522
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-x@twin.test' }) });
523
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-y@twin.test' }) });
524
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'spam-z@twin.test' }) });
525
+ const after = await h({ m: 'GET', p: '/deliverystats' });
526
+ const rows = field(after, 'Bounces');
527
+ const all = rows[0];
528
+ const hard = rows.find((x) => x.Type === 'HardBounce');
529
+ const spam = rows.find((x) => x.Type === 'SpamComplaint');
530
+ // The vendor's first row is the "All" total and carries NO Type key.
531
+ return field(after, 'InactiveMails') === 3 && all.Name === 'All' && all.Count === 3 && all.Type === undefined
532
+ && hard?.Count === 2 && spam?.Count === 1;
533
+ })),
534
+ todo('postmark.bounces.date_filters', 'bounces', 'Bounces: fromDate/toDate + messageStream filtering', 'api', 'niche'),
535
+ todo('postmark.bounces.soft_bounce_types', 'bounces', 'Bounces: the full BounceType taxonomy (Transient, DnsError, DMARCPolicy, …)', 'api', 'common'),
536
+ // ── Templates API ────────────────────────────────────────────────────────────────
537
+ done('postmark.templates.create', 'templates', 'Templates: POST /templates → the short create shape', 'api', 'core', () => withRoot(async (h) => {
538
+ const r = await h({ m: 'POST', p: '/templates', b: { Name: 'Receipt', Alias: 'receipt', Subject: 'Your receipt', HtmlBody: '<p>Thanks</p>' } });
539
+ return ok(r) && field(r, 'TemplateId') === 1 && field(r, 'Name') === 'Receipt'
540
+ && field(r, 'Alias') === 'receipt' && field(r, 'Active') === true
541
+ && field(r, 'TemplateType') === 'Standard'
542
+ // the CREATE response is the short shape — no bodies (SDK: TemplateInList)
543
+ && field(r, 'HtmlBody') === undefined;
544
+ })),
545
+ done('postmark.templates.retrieve_by_id_or_alias', 'templates', 'Templates: GET /templates/:idOrAlias resolves both ways', 'api', 'core', () => withRoot(async (h) => {
546
+ const c = await h({ m: 'POST', p: '/templates', b: { Name: 'Both', Alias: 'both-ways', Subject: 'S', TextBody: 'T' } });
547
+ const byId = await h({ m: 'GET', p: `/templates/${field(c, 'TemplateId')}` });
548
+ const byAlias = await h({ m: 'GET', p: '/templates/both-ways' });
549
+ const missing = await h({ m: 'GET', p: '/templates/nope' });
550
+ return ok(byId) && field(byId, 'Subject') === 'S' && field(byId, 'TextBody') === 'T'
551
+ && field(byAlias, 'TemplateId') === field(byId, 'TemplateId')
552
+ && failedWith(missing, 422, 1101);
553
+ })),
554
+ done('postmark.templates.list', 'templates', 'Templates: GET /templates → {TotalCount, Templates[]} (short shape)', 'api', 'core', () => withRoot(async (h) => {
555
+ await h({ m: 'POST', p: '/templates', b: { Name: 'One', Alias: 'one', Subject: 'S1', TextBody: 'T1' } });
556
+ await h({ m: 'POST', p: '/templates', b: { Name: 'Layout', TemplateType: 'Layout', HtmlBody: '{{{ @content }}}' } });
557
+ const all = await h({ m: 'GET', p: '/templates' });
558
+ const layouts = await h({ m: 'GET', p: '/templates?templateType=Layout' });
559
+ const rows = list(all, 'Templates');
560
+ return field(all, 'TotalCount') === 2 && rows[0].Name === 'One' && rows[0].Subject === undefined
561
+ && field(layouts, 'TotalCount') === 1 && list(layouts, 'Templates')[0].Name === 'Layout';
562
+ })),
563
+ done('postmark.templates.update', 'templates', 'Templates: PUT /templates/:idOrAlias edits content', 'api', 'core', () => withRoot(async (h) => {
564
+ await h({ m: 'POST', p: '/templates', b: { Name: 'Editable', Alias: 'editable', Subject: 'Before', TextBody: 'before' } });
565
+ const upd = await h({ m: 'PUT', p: '/templates/editable', b: { Subject: 'After', TextBody: 'after' } });
566
+ const got = await h({ m: 'GET', p: '/templates/editable' });
567
+ const missing = await h({ m: 'PUT', p: '/templates/nope', b: { Subject: 'x' } });
568
+ const noData = await h({ m: 'PUT', p: '/templates/editable', b: {} });
569
+ return ok(upd) && field(got, 'Subject') === 'After' && field(got, 'TextBody') === 'after'
570
+ && failedWith(missing, 422, 1101) && failedWith(noData, 422, 1109);
571
+ })),
572
+ done('postmark.templates.delete', 'templates', 'Templates: DELETE /templates/:idOrAlias', 'api', 'common', () => withRoot(async (h) => {
573
+ await h({ m: 'POST', p: '/templates', b: { Name: 'Doomed', Alias: 'doomed', Subject: 'S', TextBody: 'T' } });
574
+ const del = await h({ m: 'DELETE', p: '/templates/doomed' });
575
+ const after = await h({ m: 'GET', p: '/templates/doomed' });
576
+ const listed = await h({ m: 'GET', p: '/templates' });
577
+ const again = await h({ m: 'DELETE', p: '/templates/doomed' });
578
+ return ok(del) && errorCode(del) === 0 && failedWith(after, 422, 1101)
579
+ && field(listed, 'TotalCount') === 0 && failedWith(again, 422, 1101);
580
+ })),
581
+ done('postmark.templates.id_ratchet_after_delete', 'templates', 'Templates: a deleted TemplateId is never re-issued (dirty state)', 'api', 'common', () => withRoot(async (h) => {
582
+ // DIRTY-STATE: the id mint must count TOMBSTONES, not just live rows. A naive
583
+ // `liveRows.length + 1` re-issues id 1 here and silently aliases a retired template.
584
+ const first = await h({ m: 'POST', p: '/templates', b: { Name: 'First', Alias: 'first', Subject: 'S', TextBody: 'T' } });
585
+ await h({ m: 'DELETE', p: '/templates/first' });
586
+ const second = await h({ m: 'POST', p: '/templates', b: { Name: 'Second', Alias: 'second', Subject: 'S2', TextBody: 'T2' } });
587
+ const got = await h({ m: 'GET', p: `/templates/${field(second, 'TemplateId')}` });
588
+ const oldId = await h({ m: 'GET', p: `/templates/${field(first, 'TemplateId')}` });
589
+ return field(first, 'TemplateId') === 1 && field(second, 'TemplateId') === 2
590
+ && field(got, 'Name') === 'Second' && failedWith(oldId, 422, 1101);
591
+ })),
592
+ done('postmark.templates.alias_uniqueness', 'templates', 'Templates: a duplicate Alias is rejected', 'api', 'common', () => withRoot(async (h) => {
593
+ await h({ m: 'POST', p: '/templates', b: { Name: 'A', Alias: 'shared', Subject: 'S', TextBody: 'T' } });
594
+ const dup = await h({ m: 'POST', p: '/templates', b: { Name: 'B', Alias: 'shared', Subject: 'S', TextBody: 'T' } });
595
+ const listed = await h({ m: 'GET', p: '/templates' });
596
+ return dup.status === 422 && String(field(dup, 'Message')).includes('shared') && field(listed, 'TotalCount') === 1;
597
+ })),
598
+ done('postmark.templates.validate', 'templates', 'Templates: POST /templates/validate renders + suggests a model', 'api', 'common', () => withRoot(async (h) => {
599
+ const r = await h({ m: 'POST', p: '/templates/validate', b: {
600
+ Subject: 'Hi {{ name }}', HtmlBody: '<p>{{ product.title }}</p>', TextBody: 'plain',
601
+ TestRenderModel: { name: 'Ada', product: { title: 'Widget' } },
602
+ } });
603
+ const html = field(r, 'HtmlBody');
604
+ const suggested = field(r, 'SuggestedTemplateModel');
605
+ const broken = await h({ m: 'POST', p: '/templates/validate', b: { Subject: 'S', HtmlBody: '<p>{{ unterminated', TestRenderModel: {} } });
606
+ return ok(r) && field(r, 'AllContentIsValid') === true
607
+ && html.RenderedContent === '<p>Widget</p>'
608
+ && field(r, 'Subject').RenderedContent === 'Hi Ada'
609
+ && suggested.name === 'name_Value' && suggested.product === 'product_Value'
610
+ && field(broken, 'AllContentIsValid') === false;
611
+ })),
612
+ done('postmark.templates.create_validation', 'templates', 'Templates: 422 ErrorCode 1109 without a Name', 'api', 'common', () => withRoot(async (h) => {
613
+ const r = await h({ m: 'POST', p: '/templates', b: { Subject: 'S', TextBody: 'T' } });
614
+ return failedWith(r, 422, 1109);
615
+ })),
616
+ todo('postmark.templates.push', 'templates', 'Templates: PUT /templates/push between servers (account token)', 'api', 'niche'),
617
+ todo('postmark.templates.layout_composition', 'templates', 'Templates: Layout templates composed into a Standard template ({{{ @content }}})', 'api', 'common'),
618
+ todo('postmark.templates.mustachio_blocks', 'templates', 'Templates: full Mustachio — {{#each}} / {{#if}} blocks and inverted sections', 'api', 'common'),
619
+ todo('postmark.templates.active_limit', 'templates', 'Templates: the per-server active-template limit (ErrorCode 1105)', 'api', 'niche'),
620
+ // ── Message Streams API ──────────────────────────────────────────────────────────
621
+ done('postmark.streams.default_streams', 'streams', 'Streams: a server is born with outbound/broadcast/inbound', 'api', 'core', () => withRoot(async (h) => {
622
+ const r = await h({ m: 'GET', p: '/message-streams' });
623
+ const rows = list(r, 'MessageStreams');
624
+ const byId = Object.fromEntries(rows.map((s) => [s.ID, s]));
625
+ // NB: this is the one list endpoint where the vendor emits TotalCount LAST.
626
+ return ok(r) && field(r, 'TotalCount') === 3
627
+ && byId.outbound?.MessageStreamType === 'Transactional'
628
+ && byId.broadcast?.MessageStreamType === 'Broadcast'
629
+ && byId.broadcast?.SubscriptionManagementConfiguration?.UnsubscribeHandlingType === 'Postmark'
630
+ && byId.inbound?.MessageStreamType === 'Inbound';
631
+ })),
632
+ done('postmark.streams.create', 'streams', 'Streams: POST /message-streams', 'api', 'common', () => withRoot(async (h) => {
633
+ const r = await h({ m: 'POST', p: '/message-streams', b: { ID: 'newsletters', Name: 'Newsletters', MessageStreamType: 'Broadcast', Description: 'Weekly' } });
634
+ const got = await h({ m: 'GET', p: '/message-streams/newsletters' });
635
+ return ok(r) && field(r, 'ID') === 'newsletters' && field(r, 'ServerID') === 1
636
+ && field(got, 'Name') === 'Newsletters' && field(got, 'Description') === 'Weekly'
637
+ && field(got, 'MessageStreamType') === 'Broadcast' && field(got, 'ArchivedAt') === null;
638
+ })),
639
+ done('postmark.streams.create_validation', 'streams', 'Streams: the vendor 422s for a bad ID / type / name / duplicate', 'api', 'common', () => withRoot(async (h) => {
640
+ const badId = await h({ m: 'POST', p: '/message-streams', b: { ID: '9bad id', Name: 'X', MessageStreamType: 'Broadcast' } });
641
+ const reserved = await h({ m: 'POST', p: '/message-streams', b: { ID: 'pm-thing', Name: 'X', MessageStreamType: 'Broadcast' } });
642
+ const dup = await h({ m: 'POST', p: '/message-streams', b: { ID: 'outbound', Name: 'X', MessageStreamType: 'Transactional' } });
643
+ const badType = await h({ m: 'POST', p: '/message-streams', b: { ID: 'okid', Name: 'X', MessageStreamType: 'Nonsense' } });
644
+ const noName = await h({ m: 'POST', p: '/message-streams', b: { ID: 'okid2', MessageStreamType: 'Broadcast' } });
645
+ return failedWith(badId, 422, 1227) && failedWith(reserved, 422, 1233) && failedWith(dup, 422, 1230)
646
+ && failedWith(badType, 422, 1221) && failedWith(noName, 422, 1223);
647
+ })),
648
+ done('postmark.streams.retrieve_and_edit', 'streams', 'Streams: GET/PATCH /message-streams/:id (+ 422 ErrorCode 1226)', 'api', 'core', () => withRoot(async (h) => {
649
+ const upd = await h({ m: 'PATCH', p: '/message-streams/broadcast', b: { Name: 'Renamed Broadcast', Description: 'Edited' } });
650
+ const got = await h({ m: 'GET', p: '/message-streams/broadcast' });
651
+ const missing = await h({ m: 'GET', p: '/message-streams/nope' });
652
+ const missingPatch = await h({ m: 'PATCH', p: '/message-streams/nope', b: { Name: 'x' } });
653
+ return ok(upd) && field(got, 'Name') === 'Renamed Broadcast' && field(got, 'Description') === 'Edited'
654
+ && typeof field(got, 'UpdatedAt') === 'string'
655
+ && failedWith(missing, 422, 1226) && failedWith(missingPatch, 422, 1226);
656
+ })),
657
+ done('postmark.streams.archive_unarchive', 'streams', 'Streams: archive hides a stream from the list; unarchive restores it', 'api', 'common', () => withRoot(async (h) => {
658
+ await h({ m: 'POST', p: '/message-streams', b: { ID: 'temp', Name: 'Temp', MessageStreamType: 'Broadcast' } });
659
+ const arch = await h({ m: 'POST', p: '/message-streams/temp/archive' });
660
+ const listed = await h({ m: 'GET', p: '/message-streams' });
661
+ const withArchived = await h({ m: 'GET', p: '/message-streams?includeArchivedStreams=true' });
662
+ const un = await h({ m: 'POST', p: '/message-streams/temp/unarchive' });
663
+ const after = await h({ m: 'GET', p: '/message-streams' });
664
+ return ok(arch) && typeof field(arch, 'ExpectedPurgeDate') === 'string'
665
+ && !list(listed, 'MessageStreams').some((s) => s.ID === 'temp')
666
+ && list(withArchived, 'MessageStreams').some((s) => s.ID === 'temp')
667
+ && ok(un) && field(un, 'ArchivedAt') === null
668
+ && list(after, 'MessageStreams').some((s) => s.ID === 'temp');
669
+ })),
670
+ done('postmark.streams.archive_guards', 'streams', 'Streams: the default transactional/inbound streams cannot be archived', 'api', 'common', () => withRoot(async (h) => {
671
+ const outbound = await h({ m: 'POST', p: '/message-streams/outbound/archive' });
672
+ const inbound = await h({ m: 'POST', p: '/message-streams/inbound/archive' });
673
+ const unarchiveLive = await h({ m: 'POST', p: '/message-streams/broadcast/unarchive' });
674
+ const stillThere = await h({ m: 'GET', p: '/message-streams/outbound' });
675
+ const stillLive = await h({ m: 'GET', p: '/message-streams/broadcast' });
676
+ // §9: the two archive guards pin the vendor's PUBLISHED code (1229). The
677
+ // unarchive-a-live-stream refusal does NOT — the published table has no entry for it
678
+ // (see postmark-twin.ts), so pinning a number there would pin an invention. Assert the
679
+ // 422 and that the refusal genuinely enacted nothing instead.
680
+ return failedWith(outbound, 422, 1229) && failedWith(inbound, 422, 1229)
681
+ && unarchiveLive.status === 422 && field(stillLive, 'ArchivedAt') === null
682
+ && ok(stillThere) && field(stillThere, 'ArchivedAt') === null;
683
+ })),
684
+ done('postmark.streams.type_filter', 'streams', 'Streams: filter the list by messageStreamType', 'api', 'niche', () => withRoot(async (h) => {
685
+ const broadcast = await h({ m: 'GET', p: '/message-streams?messageStreamType=Broadcast' });
686
+ const all = await h({ m: 'GET', p: '/message-streams?messageStreamType=All' });
687
+ return field(broadcast, 'TotalCount') === 1 && list(broadcast, 'MessageStreams')[0].ID === 'broadcast'
688
+ && field(all, 'TotalCount') === 3;
689
+ })),
690
+ todo('postmark.streams.custom_unsubscribe_handling', 'streams', 'Streams: Custom/Postmark UnsubscribeHandlingType semantics on broadcast sends', 'api', 'common'),
691
+ todo('postmark.streams.purge_after_archive', 'streams', 'Streams: the 45-day purge window actually retiring an archived stream', 'api', 'niche'),
692
+ // ── Suppressions API ─────────────────────────────────────────────────────────────
693
+ done('postmark.suppressions.add', 'suppressions', 'Suppressions: POST /message-streams/:id/suppressions', 'api', 'common', () => withRoot(async (h) => {
694
+ const r = await h({ m: 'POST', p: '/message-streams/outbound/suppressions', b: { Suppressions: [{ EmailAddress: 'optout@twin.test' }, { EmailAddress: 'garbage' }] } });
695
+ const rows = list(r, 'Suppressions');
696
+ return ok(r) && rows[0].EmailAddress === 'optout@twin.test' && rows[0].Status === 'Suppressed'
697
+ && rows[1].Status === 'Failed' && String(rows[1].Message).includes('invalid email');
698
+ })),
699
+ done('postmark.suppressions.dump', 'suppressions', 'Suppressions: GET /message-streams/:id/suppressions/dump (+ filters)', 'api', 'common', () => withRoot(async (h) => {
700
+ await h({ m: 'POST', p: '/message-streams/outbound/suppressions', b: { Suppressions: [{ EmailAddress: 'manual@twin.test' }] } });
701
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-sup@twin.test' }) });
702
+ const all = await h({ m: 'GET', p: '/message-streams/outbound/suppressions/dump' });
703
+ const manualOnly = await h({ m: 'GET', p: '/message-streams/outbound/suppressions/dump?suppressionReason=ManualSuppression' });
704
+ const missingStream = await h({ m: 'GET', p: '/message-streams/nope/suppressions/dump' });
705
+ const rows = list(all, 'Suppressions');
706
+ return rows.length === 2
707
+ && rows.some((s) => s.EmailAddress === 'manual@twin.test' && s.SuppressionReason === 'ManualSuppression' && s.Origin === 'Customer')
708
+ && rows.some((s) => s.EmailAddress === 'bounce-sup@twin.test' && s.SuppressionReason === 'HardBounce' && s.Origin === 'Recipient')
709
+ && list(manualOnly, 'Suppressions').length === 1
710
+ && failedWith(missingStream, 422, 1226);
711
+ })),
712
+ done('postmark.suppressions.blocks_sending', 'suppressions', 'Suppressions: a manual suppression blocks a later send (dirty state)', 'api', 'common', () => withRoot(async (h) => {
713
+ const before = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'later-optout@twin.test' }) });
714
+ await h({ m: 'POST', p: '/message-streams/outbound/suppressions', b: { Suppressions: [{ EmailAddress: 'later-optout@twin.test' }] } });
715
+ const after = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'later-optout@twin.test' }) });
716
+ return ok(before) && failedWith(after, 422, 406);
717
+ })),
718
+ done('postmark.suppressions.stream_scoped', 'suppressions', 'Suppressions: a suppression is scoped to ONE message stream', 'api', 'common', () => withRoot(async (h) => {
719
+ await h({ m: 'POST', p: '/message-streams/outbound/suppressions', b: { Suppressions: [{ EmailAddress: 'scoped@twin.test' }] } });
720
+ const blocked = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'scoped@twin.test' }) });
721
+ const allowed = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'scoped@twin.test', MessageStream: 'broadcast' }) });
722
+ const otherDump = await h({ m: 'GET', p: '/message-streams/broadcast/suppressions/dump' });
723
+ return failedWith(blocked, 422, 406) && ok(allowed) && list(otherDump, 'Suppressions').length === 0;
724
+ })),
725
+ done('postmark.suppressions.delete_manual_only', 'suppressions', 'Suppressions: POST …/suppressions/delete removes MANUAL entries only (dirty state)', 'api', 'common', () => withRoot(async (h) => {
726
+ // DIRTY-STATE: a bounce-born suppression must NOT be deletable through this endpoint —
727
+ // Postmark requires the Bounce API's activate for that. Both entries exist first.
728
+ await h({ m: 'POST', p: '/message-streams/outbound/suppressions', b: { Suppressions: [{ EmailAddress: 'manual-del@twin.test' }] } });
729
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-del@twin.test' }) });
730
+ const r = await h({ m: 'POST', p: '/message-streams/outbound/suppressions/delete', b: { Suppressions: [{ EmailAddress: 'manual-del@twin.test' }, { EmailAddress: 'bounce-del@twin.test' }] } });
731
+ const rows = list(r, 'Suppressions');
732
+ const dump = await h({ m: 'GET', p: '/message-streams/outbound/suppressions/dump' });
733
+ const remaining = list(dump, 'Suppressions');
734
+ const noBody = await h({ m: 'POST', p: '/message-streams/outbound/suppressions/delete', b: {} });
735
+ return rows[0].Status === 'Deleted' && rows[1].Status === 'Failed'
736
+ && remaining.length === 1 && remaining[0].EmailAddress === 'bounce-del@twin.test'
737
+ && failedWith(noBody, 422, 1409);
738
+ })),
739
+ todo('postmark.suppressions.origin_filters', 'suppressions', 'Suppressions: filter the dump by Origin/date range', 'api', 'niche'),
740
+ todo('postmark.suppressions.bulk_limit', 'suppressions', 'Suppressions: the per-request maximum-entries guard (ErrorCode 1410)', 'api', 'niche'),
741
+ // ── Webhooks API + webhook DELIVERY ──────────────────────────────────────────────
742
+ done('postmark.webhooks.create', 'webhooks', 'Webhooks: POST /webhooks with Triggers/HttpAuth/HttpHeaders', 'api', 'core', () => withRoot(async (h) => {
743
+ const r = await h({ m: 'POST', p: '/webhooks', b: {
744
+ Url: 'https://app.test/hook', MessageStream: 'outbound',
745
+ HttpAuth: { Username: 'u', Password: 'p' },
746
+ HttpHeaders: [{ Name: 'X-Secret', Value: 's3cret' }],
747
+ Triggers: { Delivery: { Enabled: true }, Open: { Enabled: true, PostFirstOpenOnly: true } },
748
+ } });
749
+ const t = field(r, 'Triggers');
750
+ return ok(r) && field(r, 'ID') === 1 && field(r, 'Url') === 'https://app.test/hook'
751
+ && t.Delivery.Enabled === true && t.Open.PostFirstOpenOnly === true
752
+ // omitted triggers are defaulted OFF, not dropped
753
+ && t.Bounce.Enabled === false && t.SubscriptionChange.Enabled === false
754
+ && field(r, 'HttpAuth').Username === 'u';
755
+ })),
756
+ done('postmark.webhooks.crud', 'webhooks', 'Webhooks: GET/PUT/DELETE /webhooks/:id (+ 422 ErrorCode 1352)', 'api', 'core', () => withRoot(async (h) => {
757
+ await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://app.test/one', Triggers: { Delivery: { Enabled: true } } } });
758
+ const got = await h({ m: 'GET', p: '/webhooks/1' });
759
+ const upd = await h({ m: 'PUT', p: '/webhooks/1', b: { Url: 'https://app.test/updated' } });
760
+ const after = await h({ m: 'GET', p: '/webhooks/1' });
761
+ const listed = await h({ m: 'GET', p: '/webhooks' });
762
+ const del = await h({ m: 'DELETE', p: '/webhooks/1' });
763
+ const gone = await h({ m: 'GET', p: '/webhooks/1' });
764
+ return ok(got) && field(upd, 'Url') === 'https://app.test/updated' && field(after, 'Url') === 'https://app.test/updated'
765
+ // the webhooks collection is the one list with NO TotalCount
766
+ && list(listed, 'Webhooks').length === 1 && field(listed, 'TotalCount') === undefined
767
+ && ok(del) && failedWith(gone, 422, 1352);
768
+ })),
769
+ done('postmark.webhooks.create_validation', 'webhooks', 'Webhooks: 422 ErrorCode 1354 without a Url; 1226/1350 for a bad stream', 'api', 'common', () => withRoot(async (h) => {
770
+ const noUrl = await h({ m: 'POST', p: '/webhooks', b: { Triggers: { Delivery: { Enabled: true } } } });
771
+ const badStream = await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://app.test/x', MessageStream: 'nope' } });
772
+ await h({ m: 'POST', p: '/message-streams', b: { ID: 'archived', Name: 'Arch', MessageStreamType: 'Broadcast' } });
773
+ await h({ m: 'POST', p: '/message-streams/archived/archive' });
774
+ const onArchived = await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://app.test/x', MessageStream: 'archived' } });
775
+ return failedWith(noUrl, 422, 1354) && failedWith(badStream, 422, 1226) && failedWith(onArchived, 422, 1350);
776
+ })),
777
+ done('postmark.webhooks.delivery_payload', 'webhooks', 'Webhooks: a send POSTs the vendor Delivery payload to a subscriber', 'api', 'core', () => withWebhookCapture(async (h, seen) => {
778
+ await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://app.test/hook', Triggers: { Delivery: { Enabled: true } } } });
779
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'hooked@twin.test', Tag: 'hooked' }) });
780
+ const d = seen.find((x) => x.body.RecordType === 'Delivery');
781
+ return seen.length === 1 && !!d && d.url === 'https://app.test/hook'
782
+ && d.body.MessageID === field(sent, 'MessageID') && d.body.Recipient === 'hooked@twin.test'
783
+ && d.body.Tag === 'hooked' && d.body.MessageStream === 'outbound' && typeof d.body.DeliveredAt === 'string';
784
+ })),
785
+ done('postmark.webhooks.trigger_gating', 'webhooks', 'Webhooks: only ENABLED triggers fire (per RecordType)', 'api', 'core', () => withWebhookCapture(async (h, seen) => {
786
+ // Bounce-only subscriber: it must NOT see the Delivery/Open of a healthy send, but it
787
+ // MUST see the Bounce of a bouncing one.
788
+ await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://app.test/bounces', Triggers: { Bounce: { Enabled: true } } } });
789
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'fine@twin.test', TrackOpens: true }) });
790
+ const quietSoFar = seen.length;
791
+ if (quietSoFar !== 0)
792
+ return false;
793
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-hook@twin.test' }) });
794
+ return seen.length === 1 && seen[0].body.RecordType === 'Bounce'
795
+ && seen[0].body.Email === 'bounce-hook@twin.test' && seen[0].body.Type === 'HardBounce'
796
+ && typeof seen[0].body.ID === 'number';
797
+ })),
798
+ done('postmark.webhooks.open_click_payloads', 'webhooks', 'Webhooks: Open + Click payloads carry the vendor tracking fields', 'api', 'common', () => withWebhookCapture(async (h, seen) => {
799
+ await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://app.test/track', Triggers: { Open: { Enabled: true }, Click: { Enabled: true } } } });
800
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'tracked@twin.test', TrackOpens: true, TrackLinks: 'HtmlAndText' }) });
801
+ const open = seen.find((x) => x.body.RecordType === 'Open');
802
+ const click = seen.find((x) => x.body.RecordType === 'Click');
803
+ return seen.length === 2 && !!open && !!click
804
+ && open.body.FirstOpen === true && typeof open.body.ReadSeconds === 'number' && open.body.Client?.Name === 'Twin Mail'
805
+ && click.body.ClickLocation === 'HTML' && typeof click.body.OriginalLink === 'string';
806
+ })),
807
+ done('postmark.webhooks.basic_auth_and_headers', 'webhooks', 'Webhooks: HttpAuth → Basic header, HttpHeaders → custom headers (NOT a signature)', 'api', 'common', () => withWebhookCapture(async (h, seen) => {
808
+ // Postmark does NOT sign webhooks. Its two real authentication affordances are Basic
809
+ // auth and caller-chosen headers — asserted here so nobody "adds a signature" later.
810
+ await h({ m: 'POST', p: '/webhooks', b: {
811
+ Url: 'https://app.test/secure',
812
+ HttpAuth: { Username: 'hookuser', Password: 'hookpass' },
813
+ HttpHeaders: [{ Name: 'X-Shared-Secret', Value: 'sh4red' }],
814
+ Triggers: { Delivery: { Enabled: true } },
815
+ } });
816
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'secure@twin.test' }) });
817
+ const got = seen[0];
818
+ const expected = `Basic ${Buffer.from('hookuser:hookpass').toString('base64')}`;
819
+ return seen.length === 1 && got.headers.Authorization === expected
820
+ && got.headers['X-Shared-Secret'] === 'sh4red'
821
+ && got.headers['content-type'] === 'application/json'
822
+ // …and NOTHING signature-shaped is invented.
823
+ && !Object.keys(got.headers).some((k) => /signature|svix/i.test(k));
824
+ })),
825
+ done('postmark.webhooks.stream_scoped', 'webhooks', 'Webhooks: a subscriber only sees ITS OWN message stream', 'api', 'common', () => withWebhookCapture(async (h, seen) => {
826
+ await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://app.test/broadcast-only', MessageStream: 'broadcast', Triggers: { Delivery: { Enabled: true } } } });
827
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'tx@twin.test' }) }); // outbound → not delivered
828
+ const quietSoFar = seen.length;
829
+ if (quietSoFar !== 0)
830
+ return false;
831
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bc@twin.test', MessageStream: 'broadcast' }) });
832
+ return seen.length === 1 && seen[0].body.MessageStream === 'broadcast';
833
+ })),
834
+ done('postmark.webhooks.deleted_stops_firing', 'webhooks', 'Webhooks: a DELETED webhook stops receiving events (dirty state)', 'api', 'common', () => withWebhookCapture(async (h, seen) => {
835
+ await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://app.test/short-lived', Triggers: { Delivery: { Enabled: true } } } });
836
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'one@twin.test' }) });
837
+ if (seen.length !== 1)
838
+ return false;
839
+ await h({ m: 'DELETE', p: '/webhooks/1' });
840
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'two@twin.test' }) });
841
+ return seen.length === 1; // the tombstoned subscriber is genuinely gone from the fan-out
842
+ })),
843
+ // GROUNDING NOTE (a §9 review flagged these two as possibly-fabricated because the
844
+ // official SDK has neither — the SDK's webhook surface is list/get/create/edit/delete only).
845
+ // They are REAL but NEWER than the pinned SDK v4.0.7: both appear in the vendor's published
846
+ // Webhooks API nav, and the error table's "Webhooks API — new" block carries code 1364,
847
+ // "Webhook verification failed; nothing was saved. Fix the endpoint and retry, or send
848
+ // ?verify=false to save it unverified" — which only makes sense with a verify feature.
849
+ // Keep them; the SDK lagging the API is not evidence the API lacks them.
850
+ todo('postmark.webhooks.verify_endpoint', 'webhooks', 'Webhooks: POST /webhooks/:id/verify (endpoint reachability check)', 'api', 'niche'),
851
+ todo('postmark.webhooks.statistics', 'webhooks', 'Webhooks: GET /webhooks/:id/statistics (delivery success/failure counts)', 'api', 'niche'),
852
+ todo('postmark.webhooks.subscription_change', 'webhooks', 'Webhooks: SubscriptionChange payloads on broadcast unsubscribes', 'api', 'common'),
853
+ todo('postmark.webhooks.inbound_payload', 'webhooks', 'Webhooks: the Inbound RecordType payload delivered to InboundHookUrl', 'api', 'common'),
854
+ todo('postmark.webhooks.retry_schedule', 'webhooks', "Webhooks: Postmark's retry schedule for a failing endpoint", 'api', 'niche'),
855
+ // ── Server API ───────────────────────────────────────────────────────────────────
856
+ done('postmark.server.retrieve', 'server', 'Server: GET /server', 'api', 'core', () => withRoot(async (h) => {
857
+ const r = await h({ m: 'GET', p: '/server' });
858
+ return ok(r) && field(r, 'ID') === 1 && typeof field(r, 'Name') === 'string'
859
+ && field(r, 'DeliveryType') === 'Live' && Array.isArray(field(r, 'ApiTokens'))
860
+ && field(r, 'TrackLinks') === 'None' && typeof field(r, 'InboundAddress') === 'string';
861
+ })),
862
+ done('postmark.server.edit', 'server', 'Server: PUT /server updates the modeled settings', 'api', 'common', () => withRoot(async (h) => {
863
+ const upd = await h({ m: 'PUT', p: '/server', b: { Name: 'Renamed Server', TrackOpens: true, TrackLinks: 'HtmlOnly', Color: 'red' } });
864
+ const got = await h({ m: 'GET', p: '/server' });
865
+ const noData = await h({ m: 'PUT', p: '/server', b: {} });
866
+ return ok(upd) && field(got, 'Name') === 'Renamed Server' && field(got, 'TrackOpens') === true
867
+ && field(got, 'TrackLinks') === 'HtmlOnly' && field(got, 'Color') === 'red'
868
+ && failedWith(noData, 422, 609);
869
+ })),
870
+ done('postmark.servers.crud', 'servers', 'Servers: account-level CRUD (GET/POST/PUT/DELETE /servers)', 'api', 'common', () => withRoot(async (h) => {
871
+ const created = await h({ m: 'POST', p: '/servers', b: { Name: 'Staging', Color: 'green' } });
872
+ const id = field(created, 'ID');
873
+ const listed = await h({ m: 'GET', p: '/servers' });
874
+ const got = await h({ m: 'GET', p: `/servers/${id}` });
875
+ const upd = await h({ m: 'PUT', p: `/servers/${id}`, b: { Name: 'Staging 2' } });
876
+ const del = await h({ m: 'DELETE', p: `/servers/${id}` });
877
+ const gone = await h({ m: 'GET', p: `/servers/${id}` });
878
+ return id === 2 && field(got, 'Name') === 'Staging' && field(got, 'Color') === 'green'
879
+ && field(listed, 'TotalCount') === 2 && field(upd, 'Name') === 'Staging 2'
880
+ // §9: was `gone.status === 422` alone, which hid a wrong ErrorCode (608, "name invalid
881
+ // or missing") on a NOT-FOUND path. Pin the vendor's real server-not-found code.
882
+ && ok(del) && failedWith(gone, 422, 1453);
883
+ })),
884
+ done('postmark.servers.name_uniqueness', 'servers', 'Servers: 422 ErrorCode 603 on a duplicate server name', 'api', 'niche', () => withRoot(async (h) => {
885
+ await h({ m: 'POST', p: '/servers', b: { Name: 'Unique' } });
886
+ const dup = await h({ m: 'POST', p: '/servers', b: { Name: 'Unique' } });
887
+ const noName = await h({ m: 'POST', p: '/servers', b: {} });
888
+ return failedWith(dup, 422, 603) && failedWith(noName, 422, 608);
889
+ })),
890
+ todo('postmark.server.hook_url_validation', 'server', 'Server: reject an invalid hook URL (ErrorCode 606)', 'api', 'niche'),
891
+ todo('postmark.server.smtp_tokens', 'server', 'Server: the SMTP Tokens surface (error codes 1450-1460)', 'api', 'niche'),
892
+ todo('postmark.servers.inbound_domain_binding', 'servers', 'Servers: inbound-domain registration + its uniqueness rules', 'api', 'niche'),
893
+ // ── Stats API ────────────────────────────────────────────────────────────────────
894
+ done('postmark.stats.outbound_overview', 'stats', 'Stats: GET /stats/outbound computed from real twin messages', 'api', 'common', () => withRoot(async (h) => {
895
+ const empty = await h({ m: 'GET', p: '/stats/outbound' });
896
+ if (field(empty, 'Sent') !== 0 || field(empty, 'BounceRate') !== 0)
897
+ return false;
898
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'a@twin.test', TrackOpens: true, TrackLinks: 'HtmlAndText' }) });
899
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'b@twin.test' }) });
900
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-stat@twin.test' }) });
901
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'spam-stat@twin.test' }) });
902
+ const r = await h({ m: 'GET', p: '/stats/outbound' });
903
+ return ok(r) && field(r, 'Sent') === 4 && field(r, 'Bounced') === 1 && field(r, 'SpamComplaints') === 1
904
+ && field(r, 'Opens') === 1 && field(r, 'TotalClicks') === 1
905
+ && field(r, 'WithOpenTracking') === 1 && field(r, 'WithLinkTracking') === 1
906
+ && field(r, 'BounceRate') === 25 && field(r, 'SpamComplaintsRate') === 25;
907
+ })),
908
+ done('postmark.stats.sends_by_day', 'stats', 'Stats: GET /stats/outbound/sends → {Days:[{Date,Sent}], Sent}', 'api', 'common', () => withRoot(async (h) => {
909
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'd1@twin.test' }) });
910
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'd2@twin.test' }) });
911
+ const r = await h({ m: 'GET', p: '/stats/outbound/sends' });
912
+ const days = field(r, 'Days');
913
+ return ok(r) && field(r, 'Sent') === 2 && days.length === 1
914
+ && days[0].Sent === 2 && /^\d{4}-\d{2}-\d{2}$/.test(String(days[0].Date));
915
+ })),
916
+ done('postmark.stats.bounces_spam_tracked', 'stats', 'Stats: /bounces, /spam and /tracked per-day counts', 'api', 'common', () => withRoot(async (h) => {
917
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-s@twin.test' }) });
918
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'spam-s@twin.test' }) });
919
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'ok-s@twin.test', TrackOpens: true }) });
920
+ const bounces = await h({ m: 'GET', p: '/stats/outbound/bounces' });
921
+ const spam = await h({ m: 'GET', p: '/stats/outbound/spam' });
922
+ const tracked = await h({ m: 'GET', p: '/stats/outbound/tracked' });
923
+ return field(bounces, 'HardBounce') === 1 && field(spam, 'SpamComplaint') === 1 && field(tracked, 'Tracked') === 1;
924
+ })),
925
+ done('postmark.stats.opens_and_clicks', 'stats', 'Stats: the opens/clicks families (counts, platforms, clients, locations)', 'api', 'common', () => withRoot(async (h) => {
926
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'stat@twin.test', TrackOpens: true, TrackLinks: 'HtmlAndText' }) });
927
+ const opens = await h({ m: 'GET', p: '/stats/outbound/opens' });
928
+ const platforms = await h({ m: 'GET', p: '/stats/outbound/opens/platforms' });
929
+ const clients = await h({ m: 'GET', p: '/stats/outbound/opens/emailClients' });
930
+ const readTimes = await h({ m: 'GET', p: '/stats/outbound/opens/readTimes' });
931
+ const clicks = await h({ m: 'GET', p: '/stats/outbound/clicks' });
932
+ const browsers = await h({ m: 'GET', p: '/stats/outbound/clicks/browserFamilies' });
933
+ const clickPlatforms = await h({ m: 'GET', p: '/stats/outbound/clicks/platforms' });
934
+ const location = await h({ m: 'GET', p: '/stats/outbound/clicks/location' });
935
+ return field(opens, 'Opens') === 1 && field(platforms, 'WebMail') === 1
936
+ && field(clients, 'Twin Mail') === 1 && field(readTimes, '7s+') === 1
937
+ && field(clicks, 'Clicks') === 1 && field(browsers, 'Twin Browser') === 1
938
+ && field(clickPlatforms, 'Desktop') === 1 && field(location, 'HTML') === 1 && field(location, 'Text') === 0;
939
+ })),
940
+ todo('postmark.stats.date_and_tag_filters', 'stats', 'Stats: fromDate/toDate/tag/messageStream filtering across the stats family', 'api', 'common'),
941
+ todo('postmark.stats.real_client_taxonomy', 'stats', 'Stats: the real email-client / OS / browser taxonomy rather than a twin placeholder', 'api', 'niche'),
942
+ // ── Inbound rule triggers ────────────────────────────────────────────────────────
943
+ done('postmark.inbound_rules.crud', 'inbound_rules', 'Inbound rules: GET/POST/DELETE /triggers/inboundRules', 'api', 'common', () => withRoot(async (h) => {
944
+ const created = await h({ m: 'POST', p: '/triggers/inboundRules', b: { Rule: 'spammer.test' } });
945
+ const listed = await h({ m: 'GET', p: '/triggers/inboundRules' });
946
+ const dup = await h({ m: 'POST', p: '/triggers/inboundRules', b: { Rule: 'spammer.test' } });
947
+ const noData = await h({ m: 'POST', p: '/triggers/inboundRules', b: {} });
948
+ const del = await h({ m: 'DELETE', p: `/triggers/inboundRules/${field(created, 'ID')}` });
949
+ const gone = await h({ m: 'DELETE', p: `/triggers/inboundRules/${field(created, 'ID')}` });
950
+ const after = await h({ m: 'GET', p: '/triggers/inboundRules' });
951
+ return field(created, 'ID') === 1 && field(created, 'Rule') === 'spammer.test'
952
+ && field(listed, 'TotalCount') === 1 && list(listed, 'InboundRules')[0].Rule === 'spammer.test'
953
+ && failedWith(dup, 422, 810) && failedWith(noData, 422, 809)
954
+ && ok(del) && failedWith(gone, 422, 812) && field(after, 'TotalCount') === 0;
955
+ })),
956
+ todo('postmark.inbound_rules.blocking_effect', 'inbound_rules', 'Inbound rules: an inbound message from a blocked sender is actually Blocked', 'api', 'common'),
957
+ // ── Domains API (account token) ──────────────────────────────────────────────────
958
+ done('postmark.domains.create', 'domains', 'Domains: POST /domains returns the DNS detail block', 'api', 'common', () => withRoot(async (h) => {
959
+ const r = await h({ m: 'POST', p: '/domains', b: { Name: 'acme.dev' } });
960
+ return ok(r) && field(r, 'ID') === 1 && field(r, 'Name') === 'acme.dev'
961
+ && field(r, 'SPFVerified') === false && field(r, 'DKIMVerified') === false
962
+ && String(field(r, 'SPFTextValue')).includes('spf.mtasv.net')
963
+ && String(field(r, 'DKIMHost')).includes('_domainkey.acme.dev')
964
+ && field(r, 'ReturnPathDomainCNAMEValue') === 'pm.mtasv.net';
965
+ })),
966
+ done('postmark.domains.list_and_retrieve', 'domains', 'Domains: GET /domains (short shape) + GET /domains/:id (detail)', 'api', 'common', () => withRoot(async (h) => {
967
+ await h({ m: 'POST', p: '/domains', b: { Name: 'acme.dev' } });
968
+ const listed = await h({ m: 'GET', p: '/domains' });
969
+ const got = await h({ m: 'GET', p: '/domains/1' });
970
+ const missing = await h({ m: 'GET', p: '/domains/99' });
971
+ const row = list(listed, 'Domains')[0];
972
+ return field(listed, 'TotalCount') === 1 && row.Name === 'acme.dev'
973
+ // the list shape omits the DNS detail block the retrieve carries
974
+ && row.SPFTextValue === undefined && typeof field(got, 'SPFTextValue') === 'string'
975
+ && failedWith(missing, 422, 510);
976
+ })),
977
+ done('postmark.domains.verify_dkim_spf_return_path', 'domains', 'Domains: verifyDKIM / verifySPF / verifyReturnPath flip the flags', 'api', 'common', () => withRoot(async (h) => {
978
+ await h({ m: 'POST', p: '/domains', b: { Name: 'verify.dev' } });
979
+ const dkim = await h({ m: 'PUT', p: '/domains/1/verifyDKIM' });
980
+ const spf = await h({ m: 'POST', p: '/domains/1/verifySPF' });
981
+ // ⚠ TWIN-LOCAL precondition, not a vendor-documented ordering (§9): reporting a return
982
+ // path "verified" when none is configured would be a fake success, so the twin refuses.
983
+ // Asserted as a 422 + unchanged state; the CODE is not pinned because it is borrowed.
984
+ const tooEarly = await h({ m: 'PUT', p: '/domains/1/verifyReturnPath' });
985
+ await h({ m: 'PUT', p: '/domains/1', b: { ReturnPathDomain: 'pm-bounces.verify.dev' } });
986
+ const rp = await h({ m: 'PUT', p: '/domains/1/verifyReturnPath' });
987
+ return field(dkim, 'DKIMVerified') === true && field(spf, 'SPFVerified') === true
988
+ && tooEarly.status === 422 && field(tooEarly, 'ReturnPathDomainVerified') === undefined
989
+ && field(rp, 'ReturnPathDomainVerified') === true
990
+ && field(rp, 'ReturnPathDomain') === 'pm-bounces.verify.dev';
991
+ })),
992
+ done('postmark.domains.rotate_dkim', 'domains', 'Domains: POST /domains/:id/rotateDKIM stages a PENDING key', 'api', 'common', () => withRoot(async (h) => {
993
+ await h({ m: 'POST', p: '/domains', b: { Name: 'rotate.dev' } });
994
+ await h({ m: 'PUT', p: '/domains/1/verifyDKIM' });
995
+ const rot = await h({ m: 'POST', p: '/domains/1/rotateDKIM' });
996
+ const reVerified = await h({ m: 'PUT', p: '/domains/1/verifyDKIM' });
997
+ return field(rot, 'DKIMUpdateStatus') === 'Pending' && field(rot, 'DKIMVerified') === false
998
+ && String(field(rot, 'DKIMPendingHost')).includes('_domainkey.rotate.dev')
999
+ && field(reVerified, 'DKIMVerified') === true && field(reVerified, 'DKIMPendingHost') === '';
1000
+ })),
1001
+ done('postmark.domains.delete_and_validation', 'domains', 'Domains: DELETE + the vendor 422s (512 duplicate, 514 no Name)', 'api', 'common', () => withRoot(async (h) => {
1002
+ await h({ m: 'POST', p: '/domains', b: { Name: 'dup.dev' } });
1003
+ const dup = await h({ m: 'POST', p: '/domains', b: { Name: 'dup.dev' } });
1004
+ const noName = await h({ m: 'POST', p: '/domains', b: {} });
1005
+ const del = await h({ m: 'DELETE', p: '/domains/1' });
1006
+ const gone = await h({ m: 'GET', p: '/domains/1' });
1007
+ return failedWith(dup, 422, 512) && failedWith(noName, 422, 514)
1008
+ && ok(del) && failedWith(gone, 422, 510);
1009
+ })),
1010
+ done('postmark.domains.repeat_transition_applies', 'domains', 'Domains: a REPEATED identical transition applies again (no same-ms write dedupe)', 'api', 'common', () => withRoot(async (h) => {
1011
+ // REGRESSION PIN, and a deliberately DETERMINISTIC one. The kernel dedupes an action by
1012
+ // `operation:subjectId:occurredAt(ms):contentHash`, so two identical verifyDKIM calls in
1013
+ // the SAME millisecond used to collapse into one and leave the domain stuck on the
1014
+ // rotated PENDING key — silent data loss (see `_seq` in postmark-twin.ts). Pinning one
1015
+ // `occurredAt` across the whole cycle reproduces that collision every single run instead
1016
+ // of racing the clock, so this pin genuinely fails if the fix is reverted.
1017
+ const at = '2026-01-01T00:00:00.000Z';
1018
+ await h({ m: 'POST', p: '/domains', b: { Name: 'repeat.dev' }, at });
1019
+ for (let i = 0; i < 3; i++) {
1020
+ await h({ m: 'PUT', p: '/domains/1/verifyDKIM', at });
1021
+ const rotated = await h({ m: 'POST', p: '/domains/1/rotateDKIM', at });
1022
+ if (field(rotated, 'DKIMUpdateStatus') !== 'Pending' || field(rotated, 'DKIMVerified') !== false)
1023
+ return false;
1024
+ const reVerified = await h({ m: 'PUT', p: '/domains/1/verifyDKIM', at });
1025
+ // Every cycle must land back on Verified with the pending key cleared.
1026
+ if (field(reVerified, 'DKIMVerified') !== true || field(reVerified, 'DKIMUpdateStatus') !== 'Verified')
1027
+ return false;
1028
+ if (field(reVerified, 'DKIMPendingHost') !== '')
1029
+ return false;
1030
+ }
1031
+ return true;
1032
+ })),
1033
+ todo('postmark.domains.real_dns_lookup', 'domains', 'Domains: verification driven by a real DNS lookup rather than a local flip', 'api', 'niche'),
1034
+ // ── Sender Signatures API (account token) ────────────────────────────────────────
1035
+ done('postmark.senders.create', 'senders', 'Senders: POST /senders creates an UNCONFIRMED signature', 'api', 'common', () => withRoot(async (h) => {
1036
+ const r = await h({ m: 'POST', p: '/senders', b: { FromEmail: 'hello@acme.dev', Name: 'Acme Hello', ReplyToEmail: 'support@acme.dev' } });
1037
+ return ok(r) && field(r, 'ID') === 1 && field(r, 'EmailAddress') === 'hello@acme.dev'
1038
+ && field(r, 'Domain') === 'acme.dev' && field(r, 'Confirmed') === false
1039
+ && field(r, 'ReplyToEmailAddress') === 'support@acme.dev';
1040
+ })),
1041
+ done('postmark.senders.list_and_retrieve', 'senders', 'Senders: GET /senders → {TotalCount, SenderSignatures[]}', 'api', 'common', () => withRoot(async (h) => {
1042
+ await h({ m: 'POST', p: '/senders', b: { FromEmail: 'a@acme.dev', Name: 'A' } });
1043
+ const listed = await h({ m: 'GET', p: '/senders' });
1044
+ const got = await h({ m: 'GET', p: '/senders/1' });
1045
+ const missing = await h({ m: 'GET', p: '/senders/99' });
1046
+ // The collection key is `SenderSignatures`, NOT `Senders`, despite the path.
1047
+ return field(listed, 'TotalCount') === 1 && list(listed, 'SenderSignatures')[0].EmailAddress === 'a@acme.dev'
1048
+ && field(listed, 'Senders') === undefined
1049
+ && field(got, 'Name') === 'A' && failedWith(missing, 422, 501);
1050
+ })),
1051
+ done('postmark.senders.validation', 'senders', 'Senders: 422s for missing/invalid/public-domain/duplicate FromEmail', 'api', 'common', () => withRoot(async (h) => {
1052
+ const noEmail = await h({ m: 'POST', p: '/senders', b: { Name: 'X' } });
1053
+ const bad = await h({ m: 'POST', p: '/senders', b: { FromEmail: 'nope' } });
1054
+ const publicDomain = await h({ m: 'POST', p: '/senders', b: { FromEmail: 'someone@gmail.com' } });
1055
+ await h({ m: 'POST', p: '/senders', b: { FromEmail: 'dup@acme.dev' } });
1056
+ const dup = await h({ m: 'POST', p: '/senders', b: { FromEmail: 'dup@acme.dev' } });
1057
+ return failedWith(noEmail, 422, 520) && failedWith(bad, 422, 522)
1058
+ && failedWith(publicDomain, 422, 503) && failedWith(dup, 422, 504);
1059
+ })),
1060
+ done('postmark.senders.edit_delete_resend', 'senders', 'Senders: PUT / DELETE / resend-confirmation', 'api', 'common', () => withRoot(async (h) => {
1061
+ await h({ m: 'POST', p: '/senders', b: { FromEmail: 'edit@acme.dev', Name: 'Before' } });
1062
+ const upd = await h({ m: 'PUT', p: '/senders/1', b: { Name: 'After', ReplyToEmail: 'new-reply@acme.dev' } });
1063
+ const resent = await h({ m: 'POST', p: '/senders/1/resend' });
1064
+ const del = await h({ m: 'DELETE', p: '/senders/1' });
1065
+ const gone = await h({ m: 'GET', p: '/senders/1' });
1066
+ return field(upd, 'Name') === 'After' && field(upd, 'ReplyToEmailAddress') === 'new-reply@acme.dev'
1067
+ && ok(resent) && errorCode(resent) === 0 && ok(del) && failedWith(gone, 422, 501);
1068
+ })),
1069
+ done('postmark.senders.verify_spf_and_dkim', 'senders', 'Senders: verifySpf + requestNewDkim on a signature', 'api', 'niche', () => withRoot(async (h) => {
1070
+ await h({ m: 'POST', p: '/senders', b: { FromEmail: 'dns@acme.dev' } });
1071
+ const spf = await h({ m: 'POST', p: '/senders/1/verifySpf' });
1072
+ const dkim = await h({ m: 'POST', p: '/senders/1/requestNewDkim' });
1073
+ const after = await h({ m: 'GET', p: '/senders/1' });
1074
+ return field(spf, 'SPFVerified') === true && ok(dkim)
1075
+ && field(after, 'DKIMUpdateStatus') === 'Pending'
1076
+ && String(field(after, 'DKIMPendingHost')).includes('acme.dev');
1077
+ })),
1078
+ todo('postmark.senders.confirmation_flow', 'senders', 'Senders: the real confirmation-email round trip that sets Confirmed', 'api', 'common'),
1079
+ todo('postmark.senders.sending_requires_signature', 'senders', 'Senders: refuse a send whose From has no confirmed signature (ErrorCode 400/401)', 'api', 'common'),
1080
+ // ── Data removals (GDPR, account token) ──────────────────────────────────────────
1081
+ done('postmark.data_removals.request_and_status', 'data_removals', 'Data removals: POST /data-removals + GET /data-removals/:id', 'api', 'niche', () => withRoot(async (h) => {
1082
+ const r = await h({ m: 'POST', p: '/data-removals', b: { RequestedBy: 'admin@acme.dev', RequestedFor: 'user@customer.test', NotifyWhenCompleted: true } });
1083
+ const got = await h({ m: 'GET', p: `/data-removals/${field(r, 'ID')}` });
1084
+ const bad = await h({ m: 'POST', p: '/data-removals', b: { RequestedBy: 'admin@acme.dev' } });
1085
+ const missing = await h({ m: 'GET', p: '/data-removals/999' });
1086
+ return ok(r) && field(r, 'ID') === 1 && field(r, 'Status') === 'Pending'
1087
+ && field(got, 'Status') === 'Pending' && bad.status === 422 && failedWith(missing, 422, 1301);
1088
+ })),
1089
+ todo('postmark.data_removals.actual_purge', 'data_removals', 'Data removals: the request actually purging the subject\'s stored messages', 'api', 'niche'),
1090
+ // ── Auth ─────────────────────────────────────────────────────────────────────────
1091
+ done('postmark.auth.server_token_header', 'auth', 'Auth: X-Postmark-Server-Token (NOT Bearer); missing/empty → 401 ErrorCode 10', 'api', 'core', () => withRoot(async (h) => {
1092
+ const good = await h({ m: 'POST', p: '/email', b: mkMail(), h: { 'X-Postmark-Server-Token': '11111111-2222-3333-4444-555555555555' } });
1093
+ const missing = await h({ m: 'POST', p: '/email', b: mkMail(), h: { 'content-type': 'application/json' } });
1094
+ const empty = await h({ m: 'POST', p: '/email', b: mkMail(), h: { 'X-Postmark-Server-Token': ' ' } });
1095
+ // A BEARER token is not Postmark's scheme and must not be accepted as one.
1096
+ const bearer = await h({ m: 'POST', p: '/email', b: mkMail(), h: { authorization: 'Bearer 11111111-2222-3333-4444-555555555555' } });
1097
+ return ok(good) && errorCode(good) === 0
1098
+ && failedWith(missing, 401, 10) && field(missing, 'Message') === 'Request does not contain a valid Server token.'
1099
+ && failedWith(empty, 401, 10) && failedWith(bearer, 401, 10);
1100
+ })),
1101
+ done('postmark.auth.header_case_insensitive', 'auth', 'Auth: the token header is matched case-insensitively', 'api', 'common', () => withRoot(async (h) => {
1102
+ // Asserts real VALUES, not just `ok()` — a status-only version of this passed against
1103
+ // the dead-twin saboteur (caught by scripts/mutation-test.ts) because an empty 200 is
1104
+ // still a 200. Each casing must return the SERVER OBJECT, and the missing-token case
1105
+ // must still fail, so both the positive and negative paths have teeth.
1106
+ const lower = await h({ m: 'GET', p: '/server', h: { 'x-postmark-server-token': 'tok-lower' } });
1107
+ const upper = await h({ m: 'GET', p: '/server', h: { 'X-POSTMARK-SERVER-TOKEN': 'tok-upper' } });
1108
+ const mixed = await h({ m: 'GET', p: '/server', h: { 'X-Postmark-Server-Token': 'tok-mixed' } });
1109
+ const none = await h({ m: 'GET', p: '/server', h: { 'x-postmark-account-token': 'wrong-scope' } });
1110
+ const served = (r) => ok(r) && field(r, 'ID') === 1 && field(r, 'DeliveryType') === 'Live' && Array.isArray(field(r, 'ApiTokens'));
1111
+ return served(lower) && served(upper) && served(mixed) && failedWith(none, 401, 10);
1112
+ })),
1113
+ done('postmark.auth.account_token_scope', 'auth', 'Auth: account routes need X-Postmark-Account-Token, and say so', 'api', 'core', () => withRoot(async (h) => {
1114
+ // [WIRE] the 401 message is ENDPOINT-SCOPED — "Account token" here, "Server token" above.
1115
+ const serverTokenOnly = await h({ m: 'GET', p: '/domains', h: { 'X-Postmark-Server-Token': 'srv-token' } });
1116
+ const withAccount = await h({ m: 'GET', p: '/domains', h: { 'X-Postmark-Account-Token': 'acct-token' } });
1117
+ const sendersNoToken = await h({ m: 'GET', p: '/senders', h: { 'content-type': 'application/json' } });
1118
+ return failedWith(serverTokenOnly, 401, 10)
1119
+ && field(serverTokenOnly, 'Message') === 'Request does not contain a valid Account token.'
1120
+ && ok(withAccount) && failedWith(sendersNoToken, 401, 10);
1121
+ })),
1122
+ done('postmark.auth.test_token_email_only', 'auth', 'Auth: POSTMARK_API_TEST works on /email only (403 ErrorCode 10 elsewhere)', 'api', 'common', () => withRoot(async (h) => {
1123
+ // Verified live during this pack's build: the public test token is /email-scoped, the
1124
+ // refusal is a 403 (not 401), and a test-token send answers "Test job accepted".
1125
+ const send = await h({ m: 'POST', p: '/email', b: mkMail(), h: { 'X-Postmark-Server-Token': 'POSTMARK_API_TEST' } });
1126
+ const elsewhere = await h({ m: 'GET', p: '/server', h: { 'X-Postmark-Server-Token': 'POSTMARK_API_TEST' } });
1127
+ const bounces = await h({ m: 'GET', p: '/bounces', h: { 'X-Postmark-Server-Token': 'POSTMARK_API_TEST' } });
1128
+ return ok(send) && errorCode(send) === 0 && field(send, 'Message') === 'Test job accepted'
1129
+ && elsewhere.status === 403 && errorCode(elsewhere) === 10
1130
+ && String(field(elsewhere, 'Message')).includes('/email')
1131
+ && bounces.status === 403;
1132
+ })),
1133
+ todo('postmark.auth.rate_limit_headers', 'auth', 'Auth: the RateLimit-*/X-RateLimit-*-Second response headers and the 429 path', 'api', 'niche'),
1134
+ // GROUNDING NOTE: yes, a `problem+json` body really is unlike Postmark's usual
1135
+ // `{ErrorCode, Message}` envelope — that IS the finding. Observed live during this pack's
1136
+ // build: a request without `Content-Type: application/json` never reaches Postmark's own
1137
+ // error layer and falls through to the ASP.NET Core host, which answers RFC-9110
1138
+ // problem+json. A twin that "tidied" this into the Postmark envelope would be less faithful,
1139
+ // which is exactly why it is filed as surface to model rather than quietly normalized.
1140
+ todo('postmark.auth.unsupported_media_type', 'auth', 'Auth/protocol: 415 RFC-9110 problem+json when Content-Type is not application/json', 'api', 'niche'),
1141
+ // ── Honesty rungs ────────────────────────────────────────────────────────────────
1142
+ done('postmark.honesty.readonly_rejects_writes', 'honesty', 'read-only twin rejects writes (405)', 'api', 'common', async () => {
1143
+ const root = mkdtempSync(join(tmpdir(), 'postmark-cap-ro-'));
1144
+ try {
1145
+ const write = await handlePostmarkTwinRequest({ method: 'POST', path: '/email', body: JSON.stringify(mkMail()), readOnly: true, root });
1146
+ const read = await handlePostmarkTwinRequest({ method: 'GET', path: '/messages/outbound', readOnly: true, root });
1147
+ return write.status === 405 && read.status === 200;
1148
+ }
1149
+ finally {
1150
+ rmSync(root, { recursive: true, force: true });
1151
+ }
1152
+ }),
1153
+ done('postmark.honesty.unmodeled_404', 'honesty', 'an unmodeled route fails like the vendor (404), never a fake success', 'api', 'core', () => withRoot(async (h) => {
1154
+ const bogus = await h({ m: 'POST', p: '/segments', b: {} });
1155
+ // A route family the twin serves, but a shape the vendor does not:
1156
+ const wrongShape = await h({ m: 'DELETE', p: '/messages/outbound' });
1157
+ // Real routes the twin deliberately does NOT model must 404 rather than answer a
1158
+ // plausible-but-wrong error from a neighbouring branch (§9 finding 7): `PUT
1159
+ // /templates/push` used to be swallowed by /templates/:idOrAlias and answer 1101
1160
+ // "template not found", and the whole /email/bulk subtree used to answer 422/14 on
1161
+ // EVERY verb, so nothing under it could ever 404.
1162
+ const unmodeledPush = await h({ m: 'PUT', p: '/templates/push', b: { SourceServerID: 1, DestinationServerID: 2, PerformChanges: true } });
1163
+ const bulkWrongVerb = await h({ m: 'DELETE', p: '/email/bulk/abc' });
1164
+ // …while the two routes the vendor DOES publish there still answer its approval refusal.
1165
+ const bulkStart = await h({ m: 'POST', p: '/email/bulk', b: {} });
1166
+ return bogus.status === 404 && wrongShape.status === 404
1167
+ && unmodeledPush.status === 404 && errorCode(unmodeledPush) !== POSTMARK_ERRORS.templateNotFound
1168
+ && bulkWrongVerb.status === 404 && failedWith(bulkStart, 422, 14)
1169
+ && typeof bogus.body.ErrorCode === 'number';
1170
+ })),
1171
+ done('postmark.honesty.every_response_has_envelope', 'honesty', 'every failure carries Postmark\'s {ErrorCode, Message} envelope', 'api', 'common', () => withRoot(async (h) => {
1172
+ const failures = [
1173
+ await h({ m: 'POST', p: '/email', b: {} }),
1174
+ await h({ m: 'GET', p: '/templates/nope' }),
1175
+ await h({ m: 'GET', p: '/bounces/9' }),
1176
+ await h({ m: 'GET', p: '/message-streams/nope' }),
1177
+ await h({ m: 'GET', p: '/webhooks/9' }),
1178
+ await h({ m: 'GET', p: '/domains/9' }),
1179
+ await h({ m: 'GET', p: '/senders/9' }),
1180
+ await h({ m: 'POST', p: '/nonexistent', b: {} }),
1181
+ ];
1182
+ return failures.every((r) => {
1183
+ const b = r.body;
1184
+ return r.status >= 400 && typeof b?.ErrorCode === 'number' && b.ErrorCode !== 0 && typeof b?.Message === 'string' && b.Message.length > 0;
1185
+ });
1186
+ })),
1187
+ done('postmark.honesty.no_webhook_signature', 'honesty', 'the twin never invents a webhook signature Postmark does not send', 'api', 'common', () => withWebhookCapture(async (h, seen) => {
1188
+ // A deliberate NEGATIVE fidelity assertion: Postmark webhooks are UNSIGNED. If someone
1189
+ // later bolts on an svix/HMAC scheme copied from a sibling pack, this goes red.
1190
+ await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://app.test/plain', Triggers: { Delivery: { Enabled: true } } } });
1191
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'plainhook@twin.test' }) });
1192
+ const headers = seen[0]?.headers ?? {};
1193
+ const body = seen[0]?.body ?? {};
1194
+ return seen.length === 1
1195
+ && !Object.keys(headers).some((k) => /signature|svix|hmac/i.test(k))
1196
+ && !Object.keys(body).some((k) => /signature/i.test(k));
1197
+ })),
1198
+ done('postmark.honesty.conformance_schemas', 'honesty', 'every served resource validates against the published object schemas', 'api', 'common', async () => {
1199
+ const root = mkdtempSync(join(tmpdir(), 'postmark-cap-conf-'));
1200
+ try {
1201
+ const h = (s) => handlePostmarkTwinRequest({ method: s.m, path: s.p, body: s.b === undefined ? undefined : JSON.stringify(s.b), root });
1202
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-conf@twin.test' }) });
1203
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'ok-conf@twin.test', TrackOpens: true }) });
1204
+ await h({ m: 'POST', p: '/templates', b: { Name: 'Conf', Alias: 'conf', Subject: 'S', TextBody: 'T' } });
1205
+ await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://app.test/conf' } });
1206
+ await h({ m: 'POST', p: '/domains', b: { Name: 'conf.dev' } });
1207
+ await h({ m: 'POST', p: '/senders', b: { FromEmail: 'conf@conf.dev' } });
1208
+ await h({ m: 'POST', p: '/triggers/inboundRules', b: { Rule: 'conf.test' } });
1209
+ const { checkPostmarkConformance } = await import("./postmark-conformance.js");
1210
+ const report = checkPostmarkConformance({ root });
1211
+ // Failable both ways: it must have found real resources AND found zero violations.
1212
+ return report.ok && report.resourcesChecked >= 9 && report.fieldsChecked > 40 && report.violations.length === 0;
1213
+ }
1214
+ finally {
1215
+ rmSync(root, { recursive: true, force: true });
1216
+ }
1217
+ }),
1218
+ // ── UI mirror (data-coupled — archetype A, every screen reads a real vendor path) ─
1219
+ done('postmark.ui.activity_screen', 'ui', 'UI mirror: Activity list of sent messages from twin state (data-coupled)', 'ui', 'core', uiDataCoupled({
1220
+ withRoot: withRootCtx,
1221
+ seed: ({ h }) => h({ m: 'POST', p: '/email', b: mkMail({ Subject: 'Activity proof', To: 'activity@twin.test' }) }),
1222
+ fetch: async ({ h }) => list(await h({ m: 'GET', p: '/messages/outbound' }), 'Messages'),
1223
+ assert: (rows) => rows.some((r) => r.Subject === 'Activity proof' && r.Recipients?.includes('activity@twin.test')),
1224
+ })),
1225
+ done('postmark.ui.message_body_render', 'ui', 'UI mirror: renders a message\'s html/text body (data-coupled)', 'ui', 'core', uiDataCoupled({
1226
+ withRoot: withRootCtx,
1227
+ seed: async ({ h }) => {
1228
+ await h({ m: 'POST', p: '/email', b: mkMail({ Subject: 'HtmlOne', HtmlBody: '<em>seeded-html-marker</em>', TextBody: undefined }) });
1229
+ await h({ m: 'POST', p: '/email', b: mkMail({ Subject: 'TextOne', HtmlBody: undefined, TextBody: 'seeded-text-marker' }) });
1230
+ },
1231
+ fetch: async ({ h }) => {
1232
+ // Fetch through the SAME two-step path the detail pane uses: list, then per-id details.
1233
+ const rows = list(await h({ m: 'GET', p: '/messages/outbound' }), 'Messages');
1234
+ const detail = async (subject) => {
1235
+ const row = rows.find((r) => r.Subject === subject);
1236
+ return (await h({ m: 'GET', p: `/messages/outbound/${row?.MessageID}/details` })).body;
1237
+ };
1238
+ return { html: await detail('HtmlOne'), text: await detail('TextOne') };
1239
+ },
1240
+ render: ({ html, text }) => JSON.stringify({
1241
+ html: renderToStaticMarkup(createElement(MessageBody, { message: html })),
1242
+ text: renderToStaticMarkup(createElement(MessageBody, { message: text })),
1243
+ }),
1244
+ assert: (_data, markup) => !!markup
1245
+ && markup.includes('message-html') && markup.includes('seeded-html-marker')
1246
+ && markup.includes('message-text') && markup.includes('seeded-text-marker'),
1247
+ })),
1248
+ done('postmark.ui.delivery_timeline', 'ui', 'UI mirror: MessageEvents timeline, incl. a bounce (data-coupled)', 'ui', 'core', uiDataCoupled({
1249
+ withRoot: withRootCtx,
1250
+ seed: async ({ h }) => {
1251
+ await h({ m: 'POST', p: '/email', b: mkMail({ Subject: 'TimelineBounce', To: 'bounce-ui@twin.test' }) });
1252
+ await h({ m: 'POST', p: '/email', b: mkMail({ Subject: 'TimelineTracked', To: 'ui-tracked@twin.test', TrackOpens: true, TrackLinks: 'HtmlAndText' }) });
1253
+ },
1254
+ fetch: async ({ h }) => {
1255
+ const rows = list(await h({ m: 'GET', p: '/messages/outbound' }), 'Messages');
1256
+ const detail = async (subject) => (await h({ m: 'GET', p: `/messages/outbound/${rows.find((r) => r.Subject === subject)?.MessageID}/details` })).body;
1257
+ return { bounced: await detail('TimelineBounce'), tracked: await detail('TimelineTracked') };
1258
+ },
1259
+ render: ({ bounced, tracked }) => JSON.stringify({
1260
+ bounced: renderToStaticMarkup(createElement(DeliveryTimeline, { message: bounced })),
1261
+ tracked: renderToStaticMarkup(createElement(DeliveryTimeline, { message: tracked })),
1262
+ }),
1263
+ assert: (data, markup) => {
1264
+ // The mirror's OWN timeline helper must agree with the twin's recorded events…
1265
+ const bouncedSteps = deliveryTimeline(data.bounced);
1266
+ const trackedSteps = deliveryTimeline(data.tracked);
1267
+ return JSON.stringify(bouncedSteps) === JSON.stringify(['Sent', 'Bounced'])
1268
+ && JSON.stringify(trackedSteps) === JSON.stringify(['Sent', 'Delivered', 'Opened', 'LinkClicked'])
1269
+ // …and the rendered markup must carry the seeded outcome with its tone class.
1270
+ && !!markup && markup.includes('event bad') && markup.includes('Bounced')
1271
+ && markup.includes('event ok') && markup.includes('LinkClicked');
1272
+ },
1273
+ })),
1274
+ done('postmark.ui.status_pill', 'ui', 'UI mirror: the status pill tone comes from the mirror\'s own mapper (data-coupled)', 'ui', 'common', uiDataCoupled({
1275
+ withRoot: withRootCtx,
1276
+ seed: async ({ h }) => {
1277
+ await h({ m: 'POST', p: '/email', b: mkMail({ Subject: 'PillBounce', To: 'bounce-pill@twin.test' }) });
1278
+ await h({ m: 'POST', p: '/email', b: mkMail({ Subject: 'PillOk', To: 'pill-ok@twin.test' }) });
1279
+ },
1280
+ fetch: async ({ h }) => {
1281
+ const rows = list(await h({ m: 'GET', p: '/messages/outbound' }), 'Messages');
1282
+ const detail = async (subject) => (await h({ m: 'GET', p: `/messages/outbound/${rows.find((r) => r.Subject === subject)?.MessageID}/details` })).body;
1283
+ return { bounced: await detail('PillBounce'), delivered: await detail('PillOk') };
1284
+ },
1285
+ render: ({ bounced, delivered }) => renderToStaticMarkup(createElement('div', null, createElement(StatusPill, { value: messageStatus(bounced) }), createElement(StatusPill, { value: messageStatus(delivered) }))),
1286
+ assert: (data, markup) => messageStatus(data.bounced) === 'Bounced' && messageStatus(data.delivered) === 'Delivered'
1287
+ && statusTone('Bounced') === 'bad' && statusTone('Delivered') === 'ok'
1288
+ && !!markup && markup.includes('pill bad') && markup.includes('pill ok')
1289
+ && markup.includes('>Bounced<') && markup.includes('>Delivered<'),
1290
+ })),
1291
+ done('postmark.ui.bounces_screen', 'ui', 'UI mirror: Bounces screen (data-coupled)', 'ui', 'common', uiDataCoupled({
1292
+ withRoot: withRootCtx,
1293
+ seed: ({ h }) => h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-screen@twin.test', Subject: 'Bounce screen proof' }) }),
1294
+ fetch: async ({ h }) => list(await h({ m: 'GET', p: '/bounces' }), 'Bounces'),
1295
+ assert: (rows) => rows.some((b) => b.Email === 'bounce-screen@twin.test' && b.Subject === 'Bounce screen proof' && b.Type === 'HardBounce'),
1296
+ })),
1297
+ done('postmark.ui.templates_screen', 'ui', 'UI mirror: Templates screen (data-coupled)', 'ui', 'common', uiDataCoupled({
1298
+ withRoot: withRootCtx,
1299
+ seed: ({ h }) => h({ m: 'POST', p: '/templates', b: { Name: 'UI Check Template', Alias: 'ui-check', Subject: 'S', TextBody: 'T' } }),
1300
+ fetch: async ({ h }) => list(await h({ m: 'GET', p: '/templates' }), 'Templates'),
1301
+ assert: (rows) => rows.some((t) => t.Name === 'UI Check Template' && t.Alias === 'ui-check' && t.Active === true),
1302
+ })),
1303
+ done('postmark.ui.streams_screen', 'ui', 'UI mirror: Streams screen (data-coupled)', 'ui', 'common', uiDataCoupled({
1304
+ withRoot: withRootCtx,
1305
+ seed: ({ h }) => h({ m: 'POST', p: '/message-streams', b: { ID: 'uicheck', Name: 'UI Check Stream', MessageStreamType: 'Broadcast' } }),
1306
+ fetch: async ({ h }) => list(await h({ m: 'GET', p: '/message-streams' }), 'MessageStreams'),
1307
+ assert: (rows) => rows.some((s) => s.ID === 'uicheck' && s.Name === 'UI Check Stream') && rows.some((s) => s.ID === 'outbound'),
1308
+ })),
1309
+ done('postmark.ui.webhooks_screen', 'ui', 'UI mirror: Webhooks screen (data-coupled)', 'ui', 'common', uiDataCoupled({
1310
+ withRoot: withRootCtx,
1311
+ seed: ({ h }) => h({ m: 'POST', p: '/webhooks', b: { Url: 'https://ui-check.test/hook', Triggers: { Delivery: { Enabled: true } } } }),
1312
+ fetch: async ({ h }) => list(await h({ m: 'GET', p: '/webhooks' }), 'Webhooks'),
1313
+ assert: (rows) => rows.some((w) => w.Url === 'https://ui-check.test/hook' && w.MessageStream === 'outbound'),
1314
+ })),
1315
+ done('postmark.ui.suppressions_screen', 'ui', 'UI mirror: Suppressions screen (data-coupled)', 'ui', 'common', uiDataCoupled({
1316
+ withRoot: withRootCtx,
1317
+ seed: ({ h }) => h({ m: 'POST', p: '/message-streams/outbound/suppressions', b: { Suppressions: [{ EmailAddress: 'ui-suppressed@twin.test' }] } }),
1318
+ fetch: async ({ h }) => list(await h({ m: 'GET', p: '/message-streams/outbound/suppressions/dump' }), 'Suppressions'),
1319
+ assert: (rows) => rows.some((s) => s.EmailAddress === 'ui-suppressed@twin.test' && s.SuppressionReason === 'ManualSuppression'),
1320
+ })),
1321
+ done('postmark.ui.nav', 'ui', 'UI mirror: the nav shell — EVERY section it routes to reads live twin state', 'ui', 'core', uiDataCoupled({
1322
+ withRoot: withRootCtx,
1323
+ // A nav shell's honest data-coupling is proving every screen it routes to reads live
1324
+ // data (the sentry.ui.nav precedent) — so seed one row per section, then walk the
1325
+ // mirror's OWN declared section table and fetch each section's real API path.
1326
+ seed: async ({ h }) => {
1327
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'bounce-nav@twin.test' }) }); // messages + bounces
1328
+ await h({ m: 'POST', p: '/templates', b: { Name: 'Nav', Alias: 'nav', Subject: 'S', TextBody: 'T' } });
1329
+ await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://nav.test/hook' } });
1330
+ await h({ m: 'POST', p: '/message-streams/outbound/suppressions', b: { Suppressions: [{ EmailAddress: 'nav@twin.test' }] } });
1331
+ },
1332
+ fetch: async ({ h }) => {
1333
+ const counts = {};
1334
+ for (const s of POSTMARK_MIRROR_SECTIONS) {
1335
+ const r = await h({ m: 'GET', p: s.path });
1336
+ counts[s.key] = ok(r) ? list(r, s.collection).length : -1;
1337
+ }
1338
+ return counts;
1339
+ },
1340
+ assert: (counts) => POSTMARK_MIRROR_SECTIONS.length === 6
1341
+ && POSTMARK_MIRROR_SECTIONS.every((s) => (counts[s.key] ?? 0) > 0),
1342
+ })),
1343
+ done('postmark.ui.detail_panel', 'ui', 'UI mirror: the detail panel flattens nested vendor fields (data-coupled)', 'ui', 'common', uiDataCoupled({
1344
+ withRoot: withRootCtx,
1345
+ seed: ({ h }) => h({ m: 'POST', p: '/email', b: mkMail({
1346
+ Subject: 'Detail check', To: 'Ada <detail-to@twin.test>', Cc: 'detail-cc@twin.test',
1347
+ Tag: 'detail-tag', Metadata: { tenant: 'detail-tenant' }, TrackOpens: true,
1348
+ }) }),
1349
+ fetch: async ({ h }) => {
1350
+ const rows = list(await h({ m: 'GET', p: '/messages/outbound' }), 'Messages');
1351
+ const row = rows.find((r) => r.Subject === 'Detail check');
1352
+ return (await h({ m: 'GET', p: `/messages/outbound/${row?.MessageID}/details` })).body;
1353
+ },
1354
+ render: (message) => renderToStaticMarkup(createElement(NestedLines, { lines: flattenPostmarkValue(message) })),
1355
+ assert: (message, markup) => !!markup && markup.includes('detail-line')
1356
+ // nested arrays/objects the vendor returns are flattened into dotted paths
1357
+ && markup.includes('To[0].Email') && markup.includes('detail-to@twin.test')
1358
+ && markup.includes('Ada') && markup.includes('detail-cc@twin.test')
1359
+ && markup.includes('Metadata.tenant') && markup.includes('detail-tenant')
1360
+ && markup.includes('MessageEvents[0].Type')
1361
+ && String(message.Tag) === 'detail-tag',
1362
+ })),
1363
+ todo('postmark.ui.stats_dashboard', 'ui', 'UI mirror: a stats/overview screen over GET /stats/outbound', 'ui', 'common'),
1364
+ todo('postmark.ui.message_search', 'ui', 'UI mirror: an activity search box driving the outbound filters', 'ui', 'common'),
1365
+ todo('postmark.ui.inbound_screen', 'ui', 'UI mirror: an inbound-message screen', 'ui', 'niche'),
1366
+ todo('postmark.ui.domains_screen', 'ui', 'UI mirror: a Domains/Sender-Signatures DNS screen', 'ui', 'niche'),
1367
+ // ── Connector ────────────────────────────────────────────────────────────────────
1368
+ done('postmark.connector.pull_messages', 'connector', 'Connector: pull outbound messages from real Postmark', 'connector', 'core', () => withRoot(async (h, root) => {
1369
+ const mapped = await pullPostmarkMessages(fakePostmarkClient());
1370
+ if (mapped[0]?.id !== 'real-msg-0001' || mapped[0].fields.Subject !== 'RealSubject')
1371
+ return false;
1372
+ await syncPostmarkFromReal(fakePostmarkClient(), { root, occurredAt: '2026-01-01T00:00:00.000Z' });
1373
+ const listed = await h({ m: 'GET', p: '/messages/outbound' });
1374
+ const row = list(listed, 'Messages').find((m) => m.MessageID === 'real-msg-0001');
1375
+ return ok(listed) && !!row && row.Subject === 'RealSubject' && row.Tag === 'real-tag' && row.TrackOpens === true;
1376
+ })),
1377
+ done('postmark.connector.pull_bounces', 'connector', 'Connector: pull bounces from real Postmark', 'connector', 'common', () => withRoot(async (h, root) => {
1378
+ const mapped = await pullPostmarkBounces(fakePostmarkClient());
1379
+ if (mapped[0].fields.Email !== 'real-bounced@twin.test')
1380
+ return false;
1381
+ await syncPostmarkFromReal(fakePostmarkClient(), { root, occurredAt: '2026-01-01T00:00:00.000Z' });
1382
+ const one = await h({ m: 'GET', p: '/bounces/9001' });
1383
+ const stats = await h({ m: 'GET', p: '/deliverystats' });
1384
+ return ok(one) && field(one, 'Email') === 'real-bounced@twin.test' && field(one, 'Type') === 'HardBounce'
1385
+ && field(stats, 'Bounces')[0].Count === 1;
1386
+ })),
1387
+ done('postmark.connector.pull_templates', 'connector', 'Connector: pull templates from real Postmark', 'connector', 'common', () => withRoot(async (h, root) => {
1388
+ const mapped = await pullPostmarkTemplates(fakePostmarkClient());
1389
+ if (mapped[0].fields.Alias !== 'real-alias')
1390
+ return false;
1391
+ await syncPostmarkFromReal(fakePostmarkClient(), { root, occurredAt: '2026-01-01T00:00:00.000Z' });
1392
+ const got = await h({ m: 'GET', p: '/templates/real-alias' });
1393
+ return ok(got) && field(got, 'TemplateId') === 7001 && field(got, 'Name') === 'RealTemplate';
1394
+ })),
1395
+ done('postmark.connector.pull_streams_webhooks_server', 'connector', 'Connector: pull message streams, webhooks and the server', 'connector', 'common', () => withRoot(async (h, root) => {
1396
+ const streams = await pullPostmarkMessageStreams(fakePostmarkClient());
1397
+ const hooks = await pullPostmarkWebhooks(fakePostmarkClient());
1398
+ const server = await pullPostmarkServer(fakePostmarkClient());
1399
+ if (streams[0]?.id !== 'real-stream' || hooks[0]?.id !== '5001' || server[0]?.id !== '42')
1400
+ return false;
1401
+ await syncPostmarkFromReal(fakePostmarkClient(), { root, occurredAt: '2026-01-01T00:00:00.000Z' });
1402
+ const s = await h({ m: 'GET', p: '/message-streams/real-stream' });
1403
+ const w = await h({ m: 'GET', p: '/webhooks/5001' });
1404
+ const servers = await h({ m: 'GET', p: '/servers' });
1405
+ return field(s, 'Name') === 'RealStream' && field(w, 'Url') === 'https://real.test/hook'
1406
+ && list(servers, 'Servers').some((x) => x.ID === 42 && x.Name === 'RealServer');
1407
+ })),
1408
+ done('postmark.connector.pull_idempotent', 'connector', 'Connector: a re-pull of identical state appends NO deltas', 'connector', 'common', () => withRoot(async (h, root) => {
1409
+ const first = await syncPostmarkFromReal(fakePostmarkClient(), { root, occurredAt: '2026-01-01T00:00:00.000Z' });
1410
+ const second = await syncPostmarkFromReal(fakePostmarkClient(), { root, occurredAt: '2026-01-02T00:00:00.000Z' });
1411
+ const listed = await h({ m: 'GET', p: '/messages/outbound' });
1412
+ return first.observed > 0 && first.deltasAppended > 0 && second.deltasAppended === 0
1413
+ && field(listed, 'TotalCount') === 1; // no duplicate rows from the second pull
1414
+ })),
1415
+ done('postmark.connector.push_message', 'connector', 'Connector: push a local send to real Postmark, ONCE', 'connector', 'core', () => withRoot(async (h, root) => {
1416
+ await h({ m: 'POST', p: '/email', b: mkMail({ To: 'Pushed <pushed@twin.test>', Subject: 'Pushed subject', Tag: 'pushed' }) });
1417
+ const client = fakePostmarkClient();
1418
+ const first = await performPending(client, { root, occurredAt: '2026-01-01T00:00:00.000Z' });
1419
+ const sent = client.sends.find((s) => s.Subject === 'Pushed subject');
1420
+ if (!sent || sent.To !== 'pushed@twin.test' || sent.Tag !== 'pushed' || sent.From !== 'Acme <notifications@acme.dev>')
1421
+ return false;
1422
+ // A re-push must enact NOTHING — confirmed actions are no longer pending.
1423
+ const before = client.sends.length;
1424
+ const second = await performPending(client, { root, occurredAt: '2026-01-02T00:00:00.000Z' });
1425
+ return first.pushed >= 1 && second.pushed === 0 && client.sends.length === before;
1426
+ })),
1427
+ done('postmark.connector.push_template_and_webhook', 'connector', 'Connector: push local templates/webhooks/streams', 'connector', 'common', () => withRoot(async (h, root) => {
1428
+ await h({ m: 'POST', p: '/templates', b: { Name: 'PushMe', Alias: 'push-me', Subject: 'PS', HtmlBody: '<p>ph</p>' } });
1429
+ await h({ m: 'POST', p: '/webhooks', b: { Url: 'https://push.test/hook', Triggers: { Delivery: { Enabled: true } } } });
1430
+ await h({ m: 'POST', p: '/message-streams', b: { ID: 'pushed', Name: 'Pushed Stream', MessageStreamType: 'Broadcast' } });
1431
+ const client = fakePostmarkClient();
1432
+ const res = await performPending(client, { root, occurredAt: '2026-01-01T00:00:00.000Z' });
1433
+ return res.pushed === 3
1434
+ && client.templateCreates[0].Name === 'PushMe' && client.templateCreates[0].Alias === 'push-me'
1435
+ && client.webhookCreates[0].Url === 'https://push.test/hook'
1436
+ && client.streamCreates[0].ID === 'pushed' && client.streamCreates[0].MessageStreamType === 'Broadcast';
1437
+ })),
1438
+ done('postmark.connector.push_refuses_unmodeled', 'connector', 'Connector: push REFUSES an unmodeled op rather than dropping it', 'connector', 'common', async () => {
1439
+ // A silent drop is the failure mode this guards: the connector must throw with the op name.
1440
+ try {
1441
+ await pushPostmarkAction(fakePostmarkClient(), { operation: 'bounce.activate', subject: { type: 'bounce', id: '1' }, fields: {} });
1442
+ return false;
1443
+ }
1444
+ catch (error) {
1445
+ return String(error).includes("unsupported operation 'bounce.activate'") && String(error).includes('refusing to silently drop');
1446
+ }
1447
+ }),
1448
+ done('postmark.connector.map_shapes', 'connector', 'Connector: the pure vendor→twin shape mappers', 'connector', 'niche', async () => {
1449
+ const m = mapMessage({ MessageID: 'm1', From: 'f@x.dev', Subject: 'S', Recipients: ['r@x.dev'] });
1450
+ const b = mapBounce({ ID: 7, Type: 'Transient', Email: 'b@x.dev' });
1451
+ const t = mapTemplate({ TemplateId: 3, Name: 'T', Alias: null });
1452
+ return m.type === 'message' && m.id === 'm1' && m.fields.MessageStream === 'outbound'
1453
+ && b.type === 'bounce' && b.id === '7' && b.fields.RecordType === 'Bounce'
1454
+ && t.type === 'template' && t.id === '3' && t.fields.Active === true;
1455
+ }),
1456
+ done('postmark.connector.send_after_pull', 'connector', 'Connector: a PULLED twin is still sendable — defaults survive a pull (dirty state)', 'connector', 'core', () => withRoot(async (h, root) => {
1457
+ // §9 REGRESSION PIN for a real bug the gate was structurally blind to. `bootstrap()`
1458
+ // used to materialize the default streams/server only when the resource TYPE was
1459
+ // empty — so after a pull brought in ONE real stream and ONE real server, the
1460
+ // `outbound` stream and server 1 were never created, and a pulled twin answered
1461
+ // "server not found" on GET /server and ErrorCode 1235 on a plain POST /email.
1462
+ // Nothing in the pull_* verifies touched either path, which is exactly why an
1463
+ // independent skeptic found it and the mutation gate could not.
1464
+ await syncPostmarkFromReal(fakePostmarkClient(), { root, occurredAt: '2026-01-01T00:00:00.000Z' });
1465
+ // The pulled objects are there…
1466
+ const pulledStream = await h({ m: 'GET', p: '/message-streams/real-stream' });
1467
+ const pulledServer = await h({ m: 'GET', p: '/servers/42' });
1468
+ if (!ok(pulledStream) || !ok(pulledServer))
1469
+ return false;
1470
+ // …AND the defaults a real Postmark server always has are still there beside them.
1471
+ const outbound = await h({ m: 'GET', p: '/message-streams/outbound' });
1472
+ const server = await h({ m: 'GET', p: '/server' });
1473
+ if (!ok(outbound) || field(server, 'ID') !== 1)
1474
+ return false;
1475
+ // …so the headline flow still works on a pulled twin.
1476
+ const sent = await h({ m: 'POST', p: '/email', b: mkMail({ To: 'after-pull@twin.test' }) });
1477
+ const listed = await h({ m: 'GET', p: '/messages/outbound' });
1478
+ return ok(sent) && errorCode(sent) === 0
1479
+ && list(listed, 'Messages').some((m) => m.Recipients?.includes('after-pull@twin.test'))
1480
+ // the pulled message is still there too — bootstrap did not trample observed state
1481
+ && list(listed, 'Messages').some((m) => m.MessageID === 'real-msg-0001');
1482
+ })),
1483
+ todo('postmark.connector.pull_domains_senders', 'connector', 'Connector: pull account-level domains + sender signatures', 'connector', 'common'),
1484
+ todo('postmark.connector.pull_suppressions', 'connector', 'Connector: pull each stream\'s suppression list', 'connector', 'common'),
1485
+ todo('postmark.connector.pull_inbound_messages', 'connector', 'Connector: pull inbound messages', 'connector', 'niche'),
1486
+ todo('postmark.connector.pull_pagination', 'connector', 'Connector: page through >500 outbound messages/bounces on pull', 'connector', 'common'),
1487
+ todo('postmark.connector.rate_budget', 'connector', 'Connector: a persistent rolling-window rate budget guarding live calls', 'connector', 'common'),
1488
+ ];
1489
+ /** The vendor's own docs-nav API groups, enumerated TOP-DOWN from developer.postmarkapp.com
1490
+ * (not derived from the manifest above — that would make any bijection a tautology). */
1491
+ export const POSTMARK_AREAS = [
1492
+ 'email', 'messages', 'bounces', 'templates', 'streams', 'suppressions', 'webhooks',
1493
+ 'server', 'servers', 'stats', 'inbound_rules', 'domains', 'senders', 'data_removals',
1494
+ // Twin-side rungs, not vendor groups:
1495
+ 'auth', 'honesty', 'ui', 'connector',
1496
+ ];
1497
+ export function postmarkCapabilities() {
1498
+ return checkCapabilities('postmark', POSTMARK_CAPABILITIES);
1499
+ }
1500
+ // Referenced so the mirror's webhook helpers stay part of the pack's typed surface even when
1501
+ // a verify() exercises them only indirectly through the handler.
1502
+ export const __postmarkInternals = { deliveryPlan, webhookHeaders, webhooksFor };