dkimkit 0.0.0-stage → 0.7.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 (132) hide show
  1. package/README.md +161 -2
  2. package/dist/src/cli.d.ts +2 -0
  3. package/dist/src/cli.js +398 -0
  4. package/dist/src/cli.js.map +1 -0
  5. package/dist/src/dns-host.d.ts +10 -0
  6. package/dist/src/dns-host.js +31 -0
  7. package/dist/src/dns-host.js.map +1 -0
  8. package/dist/src/errors.d.ts +17 -0
  9. package/dist/src/errors.js +51 -0
  10. package/dist/src/errors.js.map +1 -0
  11. package/dist/src/hosted/api.d.ts +107 -0
  12. package/dist/src/hosted/api.js +919 -0
  13. package/dist/src/hosted/api.js.map +1 -0
  14. package/dist/src/hosted/auth.d.ts +80 -0
  15. package/dist/src/hosted/auth.js +147 -0
  16. package/dist/src/hosted/auth.js.map +1 -0
  17. package/dist/src/hosted/billing-dodo.d.ts +58 -0
  18. package/dist/src/hosted/billing-dodo.js +139 -0
  19. package/dist/src/hosted/billing-dodo.js.map +1 -0
  20. package/dist/src/hosted/billing-stripe.d.ts +82 -0
  21. package/dist/src/hosted/billing-stripe.js +205 -0
  22. package/dist/src/hosted/billing-stripe.js.map +1 -0
  23. package/dist/src/hosted/billing.d.ts +114 -0
  24. package/dist/src/hosted/billing.js +173 -0
  25. package/dist/src/hosted/billing.js.map +1 -0
  26. package/dist/src/hosted/bulk.d.ts +47 -0
  27. package/dist/src/hosted/bulk.js +66 -0
  28. package/dist/src/hosted/bulk.js.map +1 -0
  29. package/dist/src/hosted/cli.d.ts +2 -0
  30. package/dist/src/hosted/cli.js +318 -0
  31. package/dist/src/hosted/cli.js.map +1 -0
  32. package/dist/src/hosted/crypto.d.ts +22 -0
  33. package/dist/src/hosted/crypto.js +55 -0
  34. package/dist/src/hosted/crypto.js.map +1 -0
  35. package/dist/src/hosted/index.d.ts +7 -0
  36. package/dist/src/hosted/index.js +11 -0
  37. package/dist/src/hosted/index.js.map +1 -0
  38. package/dist/src/hosted/markdown.d.ts +16 -0
  39. package/dist/src/hosted/markdown.js +168 -0
  40. package/dist/src/hosted/markdown.js.map +1 -0
  41. package/dist/src/hosted/net.d.ts +9 -0
  42. package/dist/src/hosted/net.js +60 -0
  43. package/dist/src/hosted/net.js.map +1 -0
  44. package/dist/src/hosted/postgres-store.d.ts +62 -0
  45. package/dist/src/hosted/postgres-store.js +387 -0
  46. package/dist/src/hosted/postgres-store.js.map +1 -0
  47. package/dist/src/hosted/serialize.d.ts +144 -0
  48. package/dist/src/hosted/serialize.js +126 -0
  49. package/dist/src/hosted/serialize.js.map +1 -0
  50. package/dist/src/hosted/service.d.ts +170 -0
  51. package/dist/src/hosted/service.js +603 -0
  52. package/dist/src/hosted/service.js.map +1 -0
  53. package/dist/src/hosted/site.d.ts +84 -0
  54. package/dist/src/hosted/site.js +653 -0
  55. package/dist/src/hosted/site.js.map +1 -0
  56. package/dist/src/hosted/sqlite-store.d.ts +45 -0
  57. package/dist/src/hosted/sqlite-store.js +351 -0
  58. package/dist/src/hosted/sqlite-store.js.map +1 -0
  59. package/dist/src/hosted/store.d.ts +257 -0
  60. package/dist/src/hosted/store.js +181 -0
  61. package/dist/src/hosted/store.js.map +1 -0
  62. package/dist/src/hosted/widget.d.ts +46 -0
  63. package/dist/src/hosted/widget.js +130 -0
  64. package/dist/src/hosted/widget.js.map +1 -0
  65. package/dist/src/hosted/worker.d.ts +45 -0
  66. package/dist/src/hosted/worker.js +142 -0
  67. package/dist/src/hosted/worker.js.map +1 -0
  68. package/dist/src/http.d.ts +14 -0
  69. package/dist/src/http.js +54 -0
  70. package/dist/src/http.js.map +1 -0
  71. package/dist/src/index.d.ts +19 -0
  72. package/dist/src/index.js +19 -0
  73. package/dist/src/index.js.map +1 -0
  74. package/dist/src/orchestrator.d.ts +70 -0
  75. package/dist/src/orchestrator.js +69 -0
  76. package/dist/src/orchestrator.js.map +1 -0
  77. package/dist/src/propagation.d.ts +30 -0
  78. package/dist/src/propagation.js +76 -0
  79. package/dist/src/propagation.js.map +1 -0
  80. package/dist/src/providers/dns/aws.d.ts +28 -0
  81. package/dist/src/providers/dns/aws.js +103 -0
  82. package/dist/src/providers/dns/aws.js.map +1 -0
  83. package/dist/src/providers/dns/cloudflare.d.ts +26 -0
  84. package/dist/src/providers/dns/cloudflare.js +97 -0
  85. package/dist/src/providers/dns/cloudflare.js.map +1 -0
  86. package/dist/src/providers/dns/manual.d.ts +12 -0
  87. package/dist/src/providers/dns/manual.js +18 -0
  88. package/dist/src/providers/dns/manual.js.map +1 -0
  89. package/dist/src/providers/dns/route53.d.ts +34 -0
  90. package/dist/src/providers/dns/route53.js +176 -0
  91. package/dist/src/providers/dns/route53.js.map +1 -0
  92. package/dist/src/providers/email/factory.d.ts +29 -0
  93. package/dist/src/providers/email/factory.js +67 -0
  94. package/dist/src/providers/email/factory.js.map +1 -0
  95. package/dist/src/providers/email/mailgun.d.ts +36 -0
  96. package/dist/src/providers/email/mailgun.js +141 -0
  97. package/dist/src/providers/email/mailgun.js.map +1 -0
  98. package/dist/src/providers/email/postmark.d.ts +37 -0
  99. package/dist/src/providers/email/postmark.js +121 -0
  100. package/dist/src/providers/email/postmark.js.map +1 -0
  101. package/dist/src/providers/email/sendgrid.d.ts +31 -0
  102. package/dist/src/providers/email/sendgrid.js +109 -0
  103. package/dist/src/providers/email/sendgrid.js.map +1 -0
  104. package/dist/src/providers/email/ses.d.ts +39 -0
  105. package/dist/src/providers/email/ses.js +123 -0
  106. package/dist/src/providers/email/ses.js.map +1 -0
  107. package/dist/src/providers/factory.d.ts +14 -0
  108. package/dist/src/providers/factory.js +32 -0
  109. package/dist/src/providers/factory.js.map +1 -0
  110. package/dist/src/records.d.ts +35 -0
  111. package/dist/src/records.js +109 -0
  112. package/dist/src/records.js.map +1 -0
  113. package/dist/src/server.d.ts +67 -0
  114. package/dist/src/server.js +301 -0
  115. package/dist/src/server.js.map +1 -0
  116. package/dist/src/types.d.ts +75 -0
  117. package/dist/src/types.js +2 -0
  118. package/dist/src/types.js.map +1 -0
  119. package/dist/src/xml.d.ts +6 -0
  120. package/dist/src/xml.js +23 -0
  121. package/dist/src/xml.js.map +1 -0
  122. package/docs/billing.md +43 -0
  123. package/docs/getting-started.md +66 -0
  124. package/docs/hosted-api.md +192 -0
  125. package/docs/library.md +253 -0
  126. package/docs/self-hosting.md +153 -0
  127. package/docs/widget.md +121 -0
  128. package/package.json +63 -4
  129. package/web/dashboard.html +669 -0
  130. package/web/index.html +697 -0
  131. package/web/widget-frame.html +480 -0
  132. package/web/widget.js +91 -0
package/docs/widget.md ADDED
@@ -0,0 +1,121 @@
1
+ # Embeddable widget
2
+
3
+ A drop-in UI that lets your users connect their own domain for sending email from inside your app. It runs on the [hosted API](hosted-api.md). The widget:
4
+ - asks for the domain, or uses one you already registered;
5
+ - detects where the domain's DNS is hosted;
6
+ - either connects Cloudflare or Route 53 and writes the records automatically, or shows the exact records to paste at any other provider;
7
+ - tracks progress until the domain is verified.
8
+
9
+ ```
10
+ Your backend ── POST /v1/widget-sessions (secret key) ──▶ API ──▶ client_secret (wss_…, 1 hour)
11
+ Your frontend ── <script src=".../widget.js"> + DKIMkit.mount(el, { session }) ──▶ iframe on the API's origin
12
+ ```
13
+
14
+ ## 1. Allow your app's origin (once)
15
+
16
+ ```bash
17
+ curl -X PATCH https://dns.example.com/v1/account \
18
+ -H "Authorization: Bearer $DKIMKIT_API_KEY" -H "Content-Type: application/json" \
19
+ -d '{"widget_origins": ["https://app.yourproduct.com", "http://localhost:3000"]}'
20
+ ```
21
+
22
+ Only these origins can embed the widget. `http://` is accepted only for `localhost`.
23
+
24
+ ## 2. Create a session on your backend
25
+
26
+ Never send your API key to the browser. Create a short-lived session for the signed-in user instead:
27
+
28
+ ```ts
29
+ // POST /api/email-domain/session (your route; requires the user to be signed in)
30
+ const res = await fetch('https://dns.example.com/v1/widget-sessions', {
31
+ method: 'POST',
32
+ headers: { Authorization: `Bearer ${process.env.DKIMKIT_API_KEY}`, 'Content-Type': 'application/json' },
33
+ body: JSON.stringify({
34
+ tenant_id: currentUser.workspaceId, // the user enters their domain in the widget
35
+ inbound_url: 'https://api.yourproduct.com/inbound-email', // optional
36
+ metadata: { plan: currentUser.plan }, // optional, never shown to the user
37
+ }),
38
+ });
39
+ const { client_secret } = await res.json();
40
+ return { session: client_secret };
41
+ ```
42
+
43
+ There are two ways to create a session:
44
+ - **`{ tenant_id, … }`:** the user types their domain in the widget. The domain is created with `tenant_id`, `metadata`, `subdomain`, `sendgrid_subuser` and `inbound_url` (its hostname becomes `parse.<domain>`). If the same tenant comes back with the same domain, the widget resumes it. If another tenant already registered that domain, the widget refuses without revealing anything about it.
45
+ - **`{ domain_id }`:** for a domain you already created with `POST /v1/domains`.
46
+
47
+ Sessions last 1 hour by default (`expires_in`: 60 to 86400 seconds). Each session is tied to one domain and can't read or change anything else.
48
+
49
+ ## 3. Mount it
50
+
51
+ ```html
52
+ <div id="email-domain"></div>
53
+ <script src="https://dns.example.com/widget.js"></script>
54
+ <script>
55
+ const { session } = await fetch('/api/email-domain/session', { method: 'POST' }).then((r) => r.json());
56
+ DKIMkit.mount('#email-domain', {
57
+ session,
58
+ onReady: (domain) => {}, // domain is null until the user enters one
59
+ onStatusChange: (domain) => {}, // pending → awaiting_dns → verifying → verified, or conflict / failed
60
+ onVerified: (domain) => showToast(`${domain.domain} is ready`),
61
+ onError: (error) => {}, // { type: 'session_expired', message }
62
+ appearance: { accent: '#4f46e5', mode: 'light', radius: 8 },
63
+ });
64
+ </script>
65
+ ```
66
+
67
+ React:
68
+
69
+ ```tsx
70
+ function EmailDomainSetup({ session }: { session: string }) {
71
+ const ref = useRef<HTMLDivElement>(null);
72
+ useEffect(() => {
73
+ const widget = window.DKIMkit.mount(ref.current!, { session, onVerified: () => refetch() });
74
+ return () => widget.unmount();
75
+ }, [session]);
76
+ return <div ref={ref} />;
77
+ }
78
+ ```
79
+
80
+ `mount` returns `{ iframe, unmount }`. The iframe resizes itself to fit its content.
81
+
82
+ **Appearance:**
83
+ - `accent`: `#rrggbb`. Button text switches between black and white for contrast.
84
+ - `mode`: `light` (default), `dark`, or `auto`. `auto` follows the operating system; light is the default because matching your page matters more than the OS setting.
85
+ - `radius`: 0–24.
86
+
87
+ If you load `widget.js` some other way (a bundler or a module import), pass `apiOrigin: 'https://dns.example.com'`.
88
+
89
+ **Caching:** `widget.js` is served with a 5-minute cache, so updates to the loader take up to 5 minutes to reach browsers.
90
+
91
+ ## What your users see
92
+
93
+ 1. **Domain:** only for tenant sessions.
94
+ 2. **Setup method:** chosen based on where the domain's DNS is hosted, which the widget detects from its nameservers.
95
+ - **Cloudflare or Route 53:** "Your DNS is hosted at Cloudflare. Connect it." The user creates a scoped token, or an IAM key with a minimal policy. The API checks the credentials against the zone before saving them, so a wrong token gets a clear error immediately.
96
+ - **GoDaddy, Namecheap and other recognized hosts:** "Add the records in your GoDaddy account." The records are shown with host names in the form registrars expect (`em1234`, `s1._domainkey`, `@`), each with a copy button.
97
+ 3. **Progress:** each record shows *Waiting* or *Found*, with a "Check now" button. The user can close the page while checks continue in the background, and your webhooks still fire.
98
+ 4. **Conflicts:** if existing records with the same names point elsewhere, the user sees which ones and can choose "Replace them". Records with other names are never touched.
99
+ 5. **Done:** the domain is verified.
100
+
101
+ ## Security model
102
+
103
+ - **Credential isolation.** The widget is an iframe served from the API's origin. The Cloudflare tokens and AWS keys your users type go straight to the API, encrypted at rest. They never pass through your page's JavaScript or your servers.
104
+ - **Session tokens, not API keys, in the browser.** `wss_…` tokens are stored as hashes, expire, cover one domain, and are rejected by the account API. The reverse holds too: your API key doesn't work on widget endpoints.
105
+ - **Clickjacking.** The frame sends `Content-Security-Policy: frame-ancestors <your widget_origins>`, so other sites can't embed it to trick users into typing credentials. An expired or invalid session sends `frame-ancestors 'none'`.
106
+ - **Locked-down frame.** `default-src 'none'`, a per-response nonce on the script, `connect-src 'self'`, no referrer, and theme parameters validated before they reach the page.
107
+ - **Messaging.** The frame only exchanges `postMessage` with a parent whose origin is in `widget_origins`, and the loader only accepts messages from its own iframe on the API origin. Messages carry the end-user view of the domain: no ids, metadata, webhook URLs or credentials.
108
+ - **End-user errors are sanitized.** Email-provider and configuration failures, which are yours to fix, appear to users as a generic message. The full error is in `last_error` on the API and in your `domain.failed` webhook.
109
+
110
+ ## Widget endpoints
111
+
112
+ These are called by the iframe with `Authorization: Bearer wss_…`. You normally don't call them yourself.
113
+
114
+ | | |
115
+ |---|---|
116
+ | `GET /v1/widget/session` | Session state: `needs_domain`, `domain` (end-user view), `dns_host` |
117
+ | `POST /v1/widget/domain` | `{ domain }`, for tenant sessions |
118
+ | `POST /v1/widget/connect` | `{ provider: 'cloudflare' \| 'route53' \| 'manual', … }`. Credentials are verified before they're saved |
119
+ | `POST /v1/widget/verify` | Check now |
120
+ | `POST /v1/widget/resolve-conflicts` | The user approves replacing conflicting records |
121
+ | `GET /widget.js`, `GET /widget/frame` | The loader and the iframe page |
package/package.json CHANGED
@@ -1,6 +1,65 @@
1
1
  {
2
2
  "name": "dkimkit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.7.0",
4
+ "description": "DKIMkit: authenticate sending domains with SendGrid, Postmark, Mailgun or Amazon SES. DNS records written or shown, propagation tracked, verified automatically.",
5
+ "type": "module",
6
+ "main": "dist/src/index.js",
7
+ "types": "dist/src/index.d.ts",
8
+ "bin": {
9
+ "dkimkit": "dist/src/cli.js",
10
+ "dkimkit-api": "dist/src/hosted/cli.js"
11
+ },
12
+ "files": [
13
+ "dist/src",
14
+ "web",
15
+ "docs"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "test": "tsc && node --test \"dist/test/**/*.test.js\"",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "keywords": [
26
+ "cloudflare",
27
+ "dkim",
28
+ "dns",
29
+ "domain-authentication",
30
+ "email",
31
+ "inbound-parse",
32
+ "mailgun",
33
+ "postmark",
34
+ "sendgrid",
35
+ "ses",
36
+ "spf"
37
+ ],
38
+ "license": "MIT",
39
+ "devDependencies": {
40
+ "@types/node": "^22.20.5",
41
+ "@types/pg": "^8.23.1",
42
+ "typescript": "^5.9.3"
43
+ },
44
+ "exports": {
45
+ ".": {
46
+ "types": "./dist/src/index.d.ts",
47
+ "default": "./dist/src/index.js"
48
+ },
49
+ "./hosted": {
50
+ "types": "./dist/src/hosted/index.d.ts",
51
+ "default": "./dist/src/hosted/index.js"
52
+ },
53
+ "./hosted/sqlite": {
54
+ "types": "./dist/src/hosted/sqlite-store.d.ts",
55
+ "default": "./dist/src/hosted/sqlite-store.js"
56
+ },
57
+ "./hosted/postgres": {
58
+ "types": "./dist/src/hosted/postgres-store.d.ts",
59
+ "default": "./dist/src/hosted/postgres-store.js"
60
+ }
61
+ },
62
+ "optionalDependencies": {
63
+ "pg": "^8.23.1"
64
+ }
65
+ }