@productcraft/mail 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ProductCraft
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @productcraft/mail
2
+
3
+ Typed Node.js SDK for [ProductCraft Mail](https://productcraft.co) — receive-and-store mail platform: send template-rendered messages, manage mailboxes + domains + DKIM, lint templates, track deliveries, configure outbound webhooks, hold suppression lists.
4
+
5
+ ```bash
6
+ npm install @productcraft/mail
7
+ ```
8
+
9
+ Server-side only. The SDK ships a Platform API Key in the `Authorization` header — never embed it in a browser bundle.
10
+
11
+ ## Quick start
12
+
13
+ ```ts
14
+ import { Mail } from "@productcraft/mail";
15
+
16
+ const mail = new Mail({
17
+ auth: { type: "apiKey", key: process.env.PCFT_KEY! },
18
+ });
19
+
20
+ // Send a template-rendered message
21
+ const { data, error } = await mail.client.POST(
22
+ "/v1/workspaces/{workspace_id}/templates/{name}/send",
23
+ {
24
+ params: {
25
+ path: { workspace_id: "ws_...", name: "welcome" },
26
+ // Make retries safe — replays return the original response
27
+ // with `Idempotent-Replay: true` for 24h.
28
+ header: { "Idempotency-Key": "welcome-2026-05-22-alice" },
29
+ },
30
+ body: {
31
+ from: "hello@yourbrand.com",
32
+ to: "alice@example.com",
33
+ subject: "Welcome", // optional — falls back to template subject
34
+ data: { name: "Alice" }, // rendered into the template
35
+ },
36
+ },
37
+ );
38
+ ```
39
+
40
+ The `client` property is an [`openapi-fetch`](https://openapi-ts.dev/openapi-fetch/) instance bound to `https://api.mail.productcraft.co` and your auth credential. Every endpoint in the published OpenAPI spec is reachable through it — your editor's autocomplete lists paths + method shapes + body fields.
41
+
42
+ ## Configuration
43
+
44
+ ```ts
45
+ new Mail({
46
+ // The auth credential the SDK presents to Mail
47
+ auth: { type: "apiKey", key: "pcft_live_..." }
48
+ | { type: "bearer", token: "eyJ..." }
49
+ | { type: "cookie", value: "auth_token=..." },
50
+ // Override the prod base URL — useful for staging / a local proxy
51
+ baseUrl: "https://api.mail.example.test",
52
+ // Custom fetch implementation (undici with retry, mock in tests, ...)
53
+ fetch: customFetch,
54
+ });
55
+ ```
56
+
57
+ ## Common operations
58
+
59
+ Every path is workspace-scoped — `{workspace_id}` is the UUID of the workspace that owns the resource, returned by `@productcraft/platform-auth`'s introspect endpoint.
60
+
61
+ ### Templates
62
+
63
+ ```ts
64
+ // List templates
65
+ await mail.client.GET(
66
+ "/v1/workspaces/{workspace_id}/templates",
67
+ { params: { path: { workspace_id: "ws_..." } } },
68
+ );
69
+
70
+ // Lint a template before saving — the body field is `body_html`
71
+ // (wire) / `bodyHtml` (camelCase TS DTO), NOT `html`.
72
+ await mail.client.POST(
73
+ "/v1/workspaces/{workspace_id}/templates/lint",
74
+ {
75
+ params: { path: { workspace_id: "<workspace-uuid>" } },
76
+ body: { body_html: "<p>Hello {{name}}</p>" },
77
+ },
78
+ );
79
+ // → { score, findings: [...] }
80
+
81
+ // Preview / render with sample data
82
+ await mail.client.POST(
83
+ "/v1/workspaces/{workspace_id}/templates/{name}/render",
84
+ {
85
+ params: { path: { workspace_id: "ws_...", name: "welcome" } },
86
+ body: { data: { name: "Alice" } },
87
+ },
88
+ );
89
+
90
+ // Send to one address (template-rendered)
91
+ await mail.client.POST(
92
+ "/v1/workspaces/{workspace_id}/templates/{name}/send",
93
+ { params: { ... }, body: { from, to, data } },
94
+ );
95
+
96
+ // Send the same template to many addresses (batch)
97
+ await mail.client.POST(
98
+ "/v1/workspaces/{workspace_id}/templates/{name}/send-batch",
99
+ { params: { ... }, body: { recipients: [{ to, data }, ...] } },
100
+ );
101
+ ```
102
+
103
+ ### Domains + DKIM
104
+
105
+ ```ts
106
+ // List domains
107
+ await mail.client.GET("/v1/workspaces/{workspace_id}/domains", { ... });
108
+
109
+ // Add a domain (then publish the DKIM TXT record Mail prints)
110
+ await mail.client.POST(
111
+ "/v1/workspaces/{workspace_id}/domains",
112
+ { params: { ... }, body: { name: "mail.yourbrand.com" } },
113
+ );
114
+
115
+ // Verify after DNS propagation
116
+ await mail.client.POST(
117
+ "/v1/workspaces/{workspace_id}/domains/{domain_id}/verify",
118
+ { params: { path: { workspace_id, domain_id } } },
119
+ );
120
+ ```
121
+
122
+ ### Messages
123
+
124
+ ```ts
125
+ // Workspace-wide message listing + filters
126
+ await mail.client.GET(
127
+ "/v1/workspaces/{workspace_id}/messages",
128
+ { params: { path: { workspace_id }, query: { limit: 50 } } },
129
+ );
130
+
131
+ // Read body / attachments / raw RFC822
132
+ await mail.client.GET("/v1/workspaces/{workspace_id}/messages/{id}/body", { ... });
133
+ await mail.client.GET(
134
+ "/v1/workspaces/{workspace_id}/mailboxes/{id}/messages/{mid}/attachments/{position}",
135
+ { ... },
136
+ );
137
+ ```
138
+
139
+ ### Suppression list
140
+
141
+ ```ts
142
+ // Add an email to the workspace suppression list
143
+ await mail.client.POST(
144
+ "/v1/workspaces/{workspace_id}/suppression",
145
+ { params: { ... }, body: { email: "bounced@example.com", reason: "hard_bounce" } },
146
+ );
147
+ ```
148
+
149
+ ### Outbound webhooks
150
+
151
+ ```ts
152
+ // Configure the workspace's default webhook (delivery / open / bounce events)
153
+ await mail.client.PUT(
154
+ "/v1/workspaces/{workspace_id}/webhooks/default",
155
+ {
156
+ params: { ... },
157
+ body: { url: "https://yourapp.com/hooks/mail", events: ["message.delivered"] },
158
+ },
159
+ );
160
+
161
+ // Per-domain webhook overrides + secret rotation
162
+ await mail.client.POST(
163
+ "/v1/workspaces/{workspace_id}/webhooks/domains/{domain_id}/rotate-secret",
164
+ { ... },
165
+ );
166
+ ```
167
+
168
+ ## Idempotency
169
+
170
+ Send endpoints accept an `Idempotency-Key` header (1–256 chars, `[A-Za-z0-9_\-:]`). Retries with the same key + same body replay the original response with `Idempotent-Replay: true` for 24h. Same key + different body returns `409 IDEMPOTENCY_KEY_REUSE`.
171
+
172
+ ## How this SDK is built
173
+
174
+ Generated from the live OpenAPI spec at `https://api.mail.productcraft.co/docs-json` via [`openapi-typescript`](https://openapi-ts.dev/) (types) + [`openapi-fetch`](https://openapi-ts.dev/openapi-fetch/) (runtime). The nightly `spec-refresh` workflow opens a PR whenever the spec changes; merging publishes a patch bump so type-safe consumers stay current automatically.
175
+
176
+ ## License
177
+
178
+ [MIT](https://github.com/clauderanelagh/productcraft-node/blob/main/LICENSE).
package/dist/index.cjs ADDED
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ var core = require('@productcraft/core');
4
+
5
+ // src/index.ts
6
+ var Mail = class {
7
+ /** The underlying typed client. v0 surface — every endpoint reachable. */
8
+ client;
9
+ constructor(config = {}) {
10
+ this.client = core.makeClient(core.PC_BASE_URL.mail, config);
11
+ }
12
+ };
13
+
14
+ exports.Mail = Mail;
15
+ //# sourceMappingURL=index.cjs.map
16
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["makeClient","PC_BASE_URL"],"mappings":";;;;;AASO,IAAM,OAAN,MAAW;AAAA;AAAA,EAEA,MAAA;AAAA,EAEhB,WAAA,CAAY,MAAA,GAAyB,EAAC,EAAG;AACvC,IAAA,IAAA,CAAK,MAAA,GAASA,eAAA,CAAkBC,gBAAA,CAAY,IAAA,EAAM,MAAM,CAAA;AAAA,EAC1D;AACF","file":"index.cjs","sourcesContent":["import type { paths } from \"./_generated.js\";\nimport { makeClient, PC_BASE_URL, type PCClientConfig } from \"@productcraft/core\";\n\n/**\n * Mail — typed `openapi-fetch` client for every endpoint in\n * `Specs/mail.json`. Reach for `client.GET(\"/v1/...\")` or\n * `client.POST(...)`; request / response types come from the\n * generated `paths` interface.\n */\nexport class Mail {\n /** The underlying typed client. v0 surface — every endpoint reachable. */\n public readonly client: ReturnType<typeof makeClient<paths>>;\n\n constructor(config: PCClientConfig = {}) {\n this.client = makeClient<paths>(PC_BASE_URL.mail, config);\n }\n}\n\nexport type { paths } from \"./_generated.js\";\n"]}