@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
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@volter/twin-postmark",
3
+ "version": "0.1.0",
4
+ "description": "Local Postmark twin — a faithful, stateful local Postmark transactional-email API your real `postmark` SDK talks to unmodified. Plain/templated/batch sends, outbound + inbound message activity, bounces and delivery stats, templates, message streams, suppressions, webhooks, servers, domains and sender signatures. Deterministic offline delivery lifecycle and Postmark's real (unsigned, Basic-auth/custom-header) webhooks. Activity mirror UI. Built on @volter/world-core.",
5
+ "keywords": [
6
+ "twin",
7
+ "local",
8
+ "mock",
9
+ "mirror",
10
+ "simulator",
11
+ "fixtures",
12
+ "testing",
13
+ "sdk",
14
+ "api",
15
+ "localstack",
16
+ "postmark",
17
+ "email",
18
+ "transactional-email",
19
+ "webhooks"
20
+ ],
21
+ "author": "Volter (https://github.com/volter-ai)",
22
+ "license": "Apache-2.0",
23
+ "files": [
24
+ "src",
25
+ "client",
26
+ "test-fixtures",
27
+ "README.md",
28
+ "LICENSE",
29
+ "!**/*.test.ts",
30
+ "!**/*.test.tsx",
31
+ "dist"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/volter-ai/twin.git",
36
+ "directory": "packages/twin/postmark"
37
+ },
38
+ "homepage": "https://github.com/volter-ai/twin/tree/main/packages/twin/postmark#readme",
39
+ "type": "module",
40
+ "exports": {
41
+ ".": {
42
+ "types": "./dist/src/index.d.ts",
43
+ "default": "./dist/src/index.js"
44
+ }
45
+ },
46
+ "bin": {
47
+ "world-postmark": "dist/src/cli.js"
48
+ },
49
+ "scripts": {
50
+ "test": "bun test src/*.test.ts",
51
+ "typecheck": "tsc --noEmit",
52
+ "build": "node ../../../scripts/publish/build.mjs",
53
+ "prepack": "node ../../../scripts/publish/prepare-publish.mjs prepack",
54
+ "postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
55
+ },
56
+ "dependencies": {
57
+ "react": "^19.2.7",
58
+ "react-dom": "^19.2.7"
59
+ },
60
+ "peerDependencies": {
61
+ "@volter/world-core": "2.0.0"
62
+ },
63
+ "devDependencies": {
64
+ "@volter/world-core": "2.0.0",
65
+ "@volter/world-tooling": "0.1.0",
66
+ "postmark": "^4.0.5",
67
+ "@types/bun": "^1.2.20",
68
+ "@types/node": "^24.0.0",
69
+ "@types/react": "^19.2.17",
70
+ "@types/react-dom": "^19.2.3",
71
+ "typescript": "^5.9.0"
72
+ },
73
+ "engines": {
74
+ "node": ">=22.3"
75
+ }
76
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import { keepProcessAlive } from '@volter/world-core/lifecycle';
3
+ // world-postmark CLI: serve the Postmark API twin, the activity mirror UI, or run conformance.
4
+ import { hasFlag, optionValue } from '@volter/world-core/args';
5
+ import { createPostmarkTwinServer } from './postmark-server.ts';
6
+ import { createPostmarkMirrorServer } from './postmark-mirror-ui.ts';
7
+
8
+ const [cmd, ...rest] = process.argv.slice(2);
9
+ const port = Number(optionValue(rest, '--port', '0')) || undefined;
10
+ const root = optionValue(rest, '--root') || undefined;
11
+ const readOnly = hasFlag(rest, '--read-only'); // a twin accepts writes unless started read-only
12
+
13
+ if (cmd === 'serve') {
14
+ const s = await createPostmarkTwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}) });
15
+ process.stdout.write(`postmark twin (transactional email API)${readOnly ? ' [read-only]' : ''} at http://127.0.0.1:${s.port}\n`);
16
+ await keepProcessAlive();
17
+ } else if (cmd === 'mirror') {
18
+ const s = await createPostmarkMirrorServer({ ...(root ? { root } : {}), ...(port ? { port } : {}) });
19
+ process.stdout.write(`postmark mirror UI (activity) at http://127.0.0.1:${s.port}\n`);
20
+ await keepProcessAlive();
21
+ } else if (cmd === 'conformance') {
22
+ // dev-only; lazy so the bin runs without @volter/world-tooling
23
+ const { checkPostmarkConformance, postmarkCoverage } = await import('./postmark-conformance.ts');
24
+ const report = checkPostmarkConformance({ ...(root ? { root } : {}) });
25
+ process.stdout.write(`${JSON.stringify({ ...report, coverage: postmarkCoverage({ ...(root ? { root } : {}) }) }, null, 2)}\n`);
26
+ if (!report.ok) process.exitCode = 1;
27
+ } else {
28
+ process.stdout.write('Usage: world-postmark serve|mirror|conformance [--port N] [--root DIR] [--read-only]\n');
29
+ }
package/src/index.ts ADDED
@@ -0,0 +1,89 @@
1
+ // @volter/twin-postmark — the Postmark transactional-email API twin (one vendor, one
2
+ // package), built on the shared @volter/world-core kernel. REST transport over
3
+ // api.postmarkapp.com shapes, `X-Postmark-Server-Token` / `X-Postmark-Account-Token` auth, a
4
+ // deterministic OFFLINE delivery lifecycle that writes real `MessageEvents` and mints Bounce
5
+ // records + suppressions, Postmark's real (unsigned, Basic-auth/custom-header) webhooks, and
6
+ // a React activity-mirror UI. (Conformance/capability tooling lives in @volter/world-tooling,
7
+ // a dev dependency — NOT shipped in the runtime API.)
8
+ export { handlePostmarkTwinRequest, POSTMARK_ERRORS, POSTMARK_RESOURCE_TYPES } from './postmark-twin.ts';
9
+ export type { PostmarkRequest, PostmarkResponse } from './postmark-twin.ts';
10
+ export { createPostmarkTwinFetch, createPostmarkTwinServer, type PostmarkTwinFetchOptions } from './postmark-server.ts';
11
+ export {
12
+ deliveryPlan,
13
+ emitPostmarkWebhook,
14
+ POSTMARK_RECORD_TYPES,
15
+ terminalEvent,
16
+ webhookHeaders,
17
+ webhooksFor,
18
+ } from './postmark-events.ts';
19
+ export type { PostmarkEventType, PostmarkRecordType, PostmarkWebhookDelivery, WebhookContext } from './postmark-events.ts';
20
+ export {
21
+ mapBounce,
22
+ mapMessage,
23
+ mapMessageStream,
24
+ mapServer,
25
+ mapTemplate,
26
+ mapWebhook,
27
+ pullPostmarkBounces,
28
+ pullPostmarkMessages,
29
+ pullPostmarkMessageStreams,
30
+ pullPostmarkServer,
31
+ pullPostmarkTemplates,
32
+ pullPostmarkWebhooks,
33
+ pushPostmarkAction,
34
+ syncPostmarkFromReal,
35
+ } from './postmark-connector.ts';
36
+ export type {
37
+ PostmarkBounce, PostmarkClient, PostmarkMessageStream, PostmarkOutboundMessage,
38
+ PostmarkServer, PostmarkTemplate, PostmarkWebhookConfig,
39
+ } from './postmark-connector.ts';
40
+ export {
41
+ buildPostmarkMirrorClient,
42
+ createPostmarkMirrorServer,
43
+ POSTMARK_MIRROR_SECTIONS,
44
+ postmarkMirrorHtml,
45
+ } from './postmark-mirror-ui.ts';
46
+
47
+ // Registry descriptor: the pack self-describes so tooling can discover it.
48
+ import { registerPack, type TwinPack } from '@volter/world-core';
49
+ import { performPostmarkAction, syncPostmarkFromRemote } from './postmark-connector.ts';
50
+
51
+ export const pack: TwinPack = {
52
+ // PROTOCOL 2 (docs/contributing/architecture.md#protocol-2-the-pack-is-a-plugin): the pack is a plugin — its wire, its tree, and its half of the real
53
+ // state system. Moved 2026-09-08.
54
+ protocol: '2',
55
+ refresh: { every: '5m', webhook: true, onDemand: { atMost: '30s' } },
56
+ stateSystem: { perform: performPostmarkAction, refresh: syncPostmarkFromRemote },
57
+ // the round trip: send a message — Postmark's own write, and a fresh id each time
58
+ roundTrip: { method: 'POST', path: '/email', body: { From: 'round@trip.test', To: 'round@trip.test', Subject: 'round trip', TextBody: 'round trip' }, headers: { 'x-postmark-server-token': 'round-trip' } },
59
+ parityOrigin: 'http://twin',
60
+ vendor: 'postmark',
61
+ transport: 'rest',
62
+ archetype: 'crud',
63
+ bin: 'world-postmark',
64
+ resources: ['message', 'bounce', 'template', 'message_stream', 'webhook', 'suppression', 'server', 'domain', 'sender_signature', 'inbound_rule'],
65
+ specSource: 'postmark-conformance.ts (inline per-object JSON Schemas derived from the official `postmark` SDK models + developer.postmarkapp.com)',
66
+ description: 'Postmark transactional email API twin — send/template/batch sends, outbound message activity, bounces + delivery stats, templates, message streams, suppressions, webhooks, domains and sender signatures, with an activity mirror UI.',
67
+ // Adoption + interception, moved off the central maps unchanged (descriptor-first back-migration, adding-a-twin.md §3,
68
+ // 2026-08-31): the official `postmark` npm client and the POSTMARK_* credential
69
+ // stem, and the one API host the SDK talks to.
70
+ adoption: {
71
+ // COMMUNITY: Postmark ships no official Python SDK; `postmarker` is the de-facto client of the
72
+ // same api.postmarkapp.com surface.
73
+ pypi: ['postmarker'],
74
+ sdks: ['postmark'], envStems: ['POSTMARK'],
75
+ },
76
+ hosts: [{ host: 'api.postmarkapp.com' }],
77
+ // No `browserRouting` — deliberately, for two independent reasons (the algolia precedent).
78
+ // 1. Postmark is a SERVER-SIDE API: the server/account tokens are secrets and there is no
79
+ // browser SDK, so forwarding it from a browser dev proxy is not a flow that exists.
80
+ // 2. There is no single stripeable API path prefix to route on. Postmark's routes sit at
81
+ // the ROOT — /email, /messages/*, /bounces, /templates, /message-streams, /webhooks,
82
+ // /stats/*, /domains, /senders — so any prefix narrow enough to be meaningful (e.g.
83
+ // '/messages/') would silently fail to forward the headline /email send, and the only
84
+ // prefix that covers everything is '/', which would capture the app's own routes too.
85
+ // Node-side zero-edit injection is unaffected: POSTMARK_TWIN_URL + the control-plane
86
+ // injector's `api.postmarkapp.com` matcher redirect the real SDK with no code change.
87
+ };
88
+ // registered at import: the kernel learns the pack's state system (protocol 2)
89
+ registerPack(pack);