@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.
- package/LICENSE +202 -0
- package/README.md +144 -0
- package/client/postmark-mirror.css +79 -0
- package/client/postmark-mirror.tsx +221 -0
- package/dist/client/postmark-mirror.bundle.js +321 -0
- package/dist/client/postmark-mirror.css +79 -0
- package/dist/client/postmark-mirror.d.ts +18 -0
- package/dist/client/postmark-mirror.js +153 -0
- package/dist/client/postmark-mirror.tsx +221 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +31 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +54 -0
- package/dist/src/postmark-capabilities.d.ts +12 -0
- package/dist/src/postmark-capabilities.js +1502 -0
- package/dist/src/postmark-conformance.d.ts +33 -0
- package/dist/src/postmark-conformance.js +265 -0
- package/dist/src/postmark-connector.d.ts +167 -0
- package/dist/src/postmark-connector.js +251 -0
- package/dist/src/postmark-events.d.ts +85 -0
- package/dist/src/postmark-events.js +169 -0
- package/dist/src/postmark-mirror-ui.d.ts +58 -0
- package/dist/src/postmark-mirror-ui.js +207 -0
- package/dist/src/postmark-perform-harness.d.ts +9 -0
- package/dist/src/postmark-perform-harness.js +24 -0
- package/dist/src/postmark-server.d.ts +14 -0
- package/dist/src/postmark-server.js +29 -0
- package/dist/src/postmark-twin.d.ts +82 -0
- package/dist/src/postmark-twin.js +1575 -0
- package/dist/test-fixtures/postmark-swagger-operations.json +846 -0
- package/package.json +76 -0
- package/src/cli.ts +29 -0
- package/src/index.ts +89 -0
- package/src/postmark-capabilities.ts +1737 -0
- package/src/postmark-conformance.ts +282 -0
- package/src/postmark-connector.ts +312 -0
- package/src/postmark-events.ts +189 -0
- package/src/postmark-mirror-ui.ts +213 -0
- package/src/postmark-perform-harness.ts +21 -0
- package/src/postmark-server.ts +37 -0
- package/src/postmark-twin.ts +1520 -0
- 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);
|