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