@mailiy/sdk 0.0.0-stage → 0.2.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 26LAB.IO, Christian Gudermann
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 CHANGED
@@ -1,3 +1,46 @@
1
- # Temporary Holding Version
1
+ # @mailiy/sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Typed client for the [Mailiy](https://mailiy.com/en/) REST API. No dependencies, runs in Node 18+, Deno, Bun and edge runtimes.
4
+
5
+ ```bash
6
+ npm install @mailiy/sdk
7
+ ```
8
+
9
+ ## Usage
10
+
11
+ Create an API key in the dashboard under API keys. Keep it on the server; it has access to your whole account (or to the websites its scope allows).
12
+
13
+ ```ts
14
+ import { createMailiy, MailiyError } from "@mailiy/sdk";
15
+
16
+ const mailiy = createMailiy({ apiKey: process.env.MAILIY_API_KEY });
17
+
18
+ const site = await mailiy.createWebsite({ name: "example.com" });
19
+ const { id } = await mailiy.createForm({
20
+ name: "Contact",
21
+ website_id: site.id,
22
+ });
23
+ const form = await mailiy.getForm(id);
24
+
25
+ try {
26
+ const { submissions } = await mailiy.pageSubmissions(id, { limit: 50 });
27
+ } catch (error) {
28
+ if (error instanceof MailiyError && error.code === "rate_limited") {
29
+ // 120 requests per minute per key
30
+ }
31
+ }
32
+ ```
33
+
34
+ The client talks to `https://mailiy.com`. Set `baseUrl` (or the `MAILIY_URL` environment variable) to use another instance, and `fetch` to pass your own fetch function.
35
+
36
+ Failed requests throw a `MailiyError` with the HTTP `status` and the API's error `code`.
37
+
38
+ All methods and fields are listed in the [API docs](https://mailiy.com/en/docs/) and the [OpenAPI spec](https://mailiy.com/openapi.json).
39
+
40
+ ## Forms in the browser
41
+
42
+ To send a form from a website you only need the form's public access key, not this SDK. See [@mailiy/react](https://www.npmjs.com/package/@mailiy/react) or the [guides](https://mailiy.com/en/guides/).
43
+
44
+ ## License
45
+
46
+ MIT
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Mailiy SDK: a typed, dependency-free client for the Mailiy REST API.
3
+ *
4
+ * import { createMailiy } from "@mailiy/sdk";
5
+ * const mailiy = createMailiy({ apiKey: process.env.MAILIY_API_KEY });
6
+ * const site = await mailiy.createWebsite({ name: "example.de" });
7
+ * const { id } = await mailiy.createForm({ name: "Contact", website_id: site.id });
8
+ */
9
+ type Retention = 0 | 7 | 30 | 90 | 180 | 365;
10
+ type Captcha = "" | "altcha" | "hcaptcha" | "turnstile" | "recaptcha";
11
+ type SpamFilter = "" | "basic" | "strict";
12
+ export type EmailTemplate = "default" | "plain" | "minimal" | "modern" | "compact" | "dark" | "custom";
13
+ export interface MailiyWebsite {
14
+ id: string;
15
+ name: string;
16
+ domains: string;
17
+ recipients: string[];
18
+ from_name: string;
19
+ retention_days: number;
20
+ captcha: Captcha;
21
+ /** The secret itself is never returned. */
22
+ captcha_secret_set: boolean;
23
+ created_at: number;
24
+ forms?: number;
25
+ /** Usage counters (delivered and blocked spam) since the counters exist. */
26
+ delivered?: number;
27
+ spam?: number;
28
+ }
29
+ export type MailiyWebsitePatch = Partial<{
30
+ domains: string;
31
+ from_name: string;
32
+ name: string;
33
+ recipients: string[];
34
+ retention_days: Retention;
35
+ captcha: Captcha;
36
+ /** Server-side key of hCaptcha, Turnstile or reCAPTCHA; "" clears it. */
37
+ captcha_secret: string;
38
+ }>;
39
+ export interface MailiyForm {
40
+ id: string;
41
+ website_id: string;
42
+ name: string;
43
+ website: string;
44
+ recipients: string[];
45
+ subject: string;
46
+ from_name: string;
47
+ redirect_url: string;
48
+ allowed_domains: string;
49
+ retention_days: number;
50
+ captcha: Captcha;
51
+ captcha_secret_set: boolean;
52
+ require_consent: number;
53
+ autoresponder_subject: string;
54
+ autoresponder_text: string;
55
+ email_template: EmailTemplate;
56
+ /** Used when email_template is "custom". */
57
+ email_html: string;
58
+ intro_text: string;
59
+ spam_filter: SpamFilter;
60
+ /** IPs and CIDR ranges, one per line. */
61
+ blocked_ips: string;
62
+ group_emails: number;
63
+ server_requires_key: number;
64
+ disabled: number;
65
+ /** Submissions are refused before this time (ms since epoch); null: no start. */
66
+ opens_at: number | null;
67
+ /** Submissions are refused from this time on (ms since epoch); null: no end. */
68
+ closes_at: number | null;
69
+ /** Delivered submissions after which the form answers form_full; 0: no limit. */
70
+ max_submissions: number;
71
+ /** Field names, one per line; a submission missing one gets missing_fields. */
72
+ required_fields: string;
73
+ created_at: number;
74
+ submissions?: number;
75
+ delivered?: number;
76
+ }
77
+ export interface MailiySubmission {
78
+ id: string;
79
+ form_id: string;
80
+ created_at: number;
81
+ consent_at: number | null;
82
+ data: [string, string][];
83
+ /** Uploaded files; each url downloads without login for one hour. */
84
+ files: {
85
+ id: string;
86
+ field: string;
87
+ name: string;
88
+ size: number;
89
+ type: string;
90
+ url: string;
91
+ }[];
92
+ spam: boolean;
93
+ }
94
+ export interface MailiySubmissionFilter {
95
+ /** next_cursor of the previous result: opens the next block of 1000 older submissions. */
96
+ cursor?: string;
97
+ form?: string;
98
+ page?: number;
99
+ q?: string;
100
+ status?: "delivered" | "spam";
101
+ }
102
+ export interface MailiySubmissionPage {
103
+ keys: string[];
104
+ /** Set when older submissions exist beyond this block of 1000. */
105
+ next_cursor: string | null;
106
+ page: number;
107
+ pages: number;
108
+ rows: (MailiySubmission & {
109
+ form_name: string;
110
+ })[];
111
+ total: number;
112
+ }
113
+ export interface MailiyStats {
114
+ range: "7d" | "30d" | "12m";
115
+ /** One point per day (YYYY-MM-DD) or month (YYYY-MM), oldest first, gaps filled with 0. */
116
+ points: {
117
+ date: string;
118
+ delivered: number;
119
+ spam: number;
120
+ }[];
121
+ }
122
+ /** Service health. backup only appears where the operator has set up backups. */
123
+ export interface MailiyStatus {
124
+ status: "ok" | "degraded";
125
+ checked_at: number;
126
+ checks: {
127
+ database: boolean;
128
+ jobs: boolean;
129
+ mail: boolean;
130
+ backup?: boolean;
131
+ };
132
+ }
133
+ /** A stored file in a website, newest first. */
134
+ export interface MailiyWebsiteFile {
135
+ id: string;
136
+ field: string;
137
+ name: string;
138
+ size: number;
139
+ type: string;
140
+ /** Downloads without login for one hour. */
141
+ url: string;
142
+ created_at: number;
143
+ form_id: string;
144
+ form_name: string;
145
+ submission_id: string;
146
+ }
147
+ export type MailiyIntegrationKind = "webhook" | "slack" | "discord" | "telegram";
148
+ /** Sends every submission of a website, or of one form, to a webhook or chat. */
149
+ export interface MailiyIntegration {
150
+ id: string;
151
+ website_id: string;
152
+ /** Null: every form of the website. */
153
+ form_id: string | null;
154
+ kind: MailiyIntegrationKind;
155
+ label: string;
156
+ /** Masked destination; the full URL or bot token is never returned. */
157
+ hint: string;
158
+ /** Webhooks only: HMAC key for the Mailiy-Signature header. */
159
+ secret: string | null;
160
+ disabled: boolean;
161
+ created_at: number;
162
+ }
163
+ export type MailiyIntegrationInput = {
164
+ form_id?: string | undefined;
165
+ label?: string | undefined;
166
+ } & ({
167
+ kind: "webhook" | "slack" | "discord";
168
+ url: string;
169
+ } | {
170
+ kind: "telegram";
171
+ bot_token: string;
172
+ chat_id: string;
173
+ });
174
+ /** One send attempt series; failed ones are retried up to 6 times over about 9 hours. */
175
+ export interface MailiyDelivery {
176
+ id: string;
177
+ integration_id: string;
178
+ status: "failed" | "ok" | "pending";
179
+ attempts: number;
180
+ http_status: number | null;
181
+ /** Error code such as EBLOCKED, ETIMEDOUT or http_500; empty on success. */
182
+ error: string;
183
+ next_attempt_at: number;
184
+ created_at: number;
185
+ /** True while the payload is still stored (7 days). */
186
+ can_retry: boolean;
187
+ }
188
+ export interface MailiyRenderedEmail {
189
+ subject: string;
190
+ html: string;
191
+ text: string;
192
+ }
193
+ export type MailiyFormPatch = Partial<{
194
+ allowed_domains: string;
195
+ autoresponder_subject: string;
196
+ autoresponder_text: string;
197
+ blocked_ips: string;
198
+ closes_at: number | null;
199
+ disabled: boolean;
200
+ /** Placeholders: {fields}, {intro}, {form}, {field:name}. */
201
+ email_html: string;
202
+ email_template: EmailTemplate;
203
+ from_name: string;
204
+ group_emails: boolean;
205
+ intro_text: string;
206
+ max_submissions: number;
207
+ name: string;
208
+ opens_at: number | null;
209
+ recipients: string[];
210
+ redirect_url: string;
211
+ require_consent: boolean;
212
+ /** Field names, one per line. */
213
+ required_fields: string;
214
+ retention_days: Retention;
215
+ captcha: Captcha;
216
+ captcha_secret: string;
217
+ server_requires_key: boolean;
218
+ spam_filter: SpamFilter;
219
+ subject: string;
220
+ website: string;
221
+ }>;
222
+ export interface MailiyOptions {
223
+ /** Personal API key from Dashboard → Account → API keys (starts with mk_). */
224
+ apiKey?: string | undefined;
225
+ /** Defaults to MAILIY_URL, then https://mailiy.com. */
226
+ baseUrl?: string | undefined;
227
+ /** Custom fetch, e.g. for tests or proxies. */
228
+ fetch?: typeof fetch;
229
+ }
230
+ export interface MailiyCursorFilter {
231
+ cursor?: string | undefined;
232
+ /** 1 to 500, default 500. */
233
+ limit?: number | undefined;
234
+ spam?: boolean | undefined;
235
+ }
236
+ export declare class MailiyError extends Error {
237
+ name: string;
238
+ readonly code: string;
239
+ readonly status: number;
240
+ constructor(code: string, status: number);
241
+ }
242
+ export declare const DEFAULT_BASE_URL = "https://mailiy.com";
243
+ export declare const createMailiy: (options?: MailiyOptions) => {
244
+ /** Without website_id the form goes into the account's only website. */
245
+ createForm: (input: {
246
+ name: string;
247
+ website?: string | undefined;
248
+ website_id?: string | undefined;
249
+ }) => Promise<{
250
+ id: string;
251
+ website_id: string;
252
+ }>;
253
+ /** Website-wide unless form_id is set. Webhook URLs must be https and public. */
254
+ createIntegration: (websiteId: string, input: MailiyIntegrationInput) => Promise<MailiyIntegration>;
255
+ createWebsite: (input: {
256
+ name: string;
257
+ domains?: string | undefined;
258
+ }) => Promise<{
259
+ id: string;
260
+ }>;
261
+ /** Deletes one uploaded file to free storage; the submission stays. */
262
+ deleteFile: (id: string) => Promise<{
263
+ ok: true;
264
+ }>;
265
+ deleteForm: (id: string) => Promise<{
266
+ ok: true;
267
+ }>;
268
+ deleteIntegration: (id: string) => Promise<{
269
+ ok: true;
270
+ }>;
271
+ deleteSubmission: (formId: string, submissionId: string) => Promise<{
272
+ ok: true;
273
+ }>;
274
+ /** Deletes the website with all its forms and stored submissions. */
275
+ deleteWebsite: (id: string) => Promise<{
276
+ ok: true;
277
+ }>;
278
+ /** Copies a form with all settings under a new access key. */
279
+ duplicateForm: (id: string, name?: string) => Promise<{
280
+ id: string;
281
+ name: string;
282
+ }>;
283
+ getForm: (id: string) => Promise<MailiyForm>;
284
+ /** Public service health, cached for 30 seconds. */
285
+ getStatus: () => Promise<MailiyStatus>;
286
+ getWebsite: (id: string) => Promise<MailiyWebsite>;
287
+ /** The newest 50 deliveries of an integration. */
288
+ listDeliveries: (id: string) => Promise<MailiyDelivery[]>;
289
+ listForms: (websiteId?: string) => Promise<MailiyForm[]>;
290
+ listIntegrations: (websiteId: string) => Promise<MailiyIntegration[]>;
291
+ /** Up to `limit` submissions (default and maximum 500). With spam: true it lists the spam tab (kept for 7 days). */
292
+ listSubmissions: (formId: string, filter?: MailiyCursorFilter) => Promise<MailiySubmission[]>;
293
+ /** Uploaded files of all forms in the website (up to 500). */
294
+ listWebsiteFiles: (id: string) => Promise<MailiyWebsiteFile[]>;
295
+ listWebsites: () => Promise<MailiyWebsite[]>;
296
+ /** Delivers a submission from the spam tab and moves it to the normal list. */
297
+ markNotSpam: (formId: string, submissionId: string) => Promise<{
298
+ ok: true;
299
+ }>;
300
+ pageSubmissions: (formId: string, filter?: MailiyCursorFilter) => Promise<{
301
+ next_cursor: string | null;
302
+ submissions: MailiySubmission[];
303
+ }>;
304
+ /** Renders the notification and auto-reply with sample data; patch fields override the saved ones. */
305
+ previewEmail: (id: string, patch?: MailiyFormPatch) => Promise<MailiyRenderedEmail & {
306
+ autoresponder: MailiyRenderedEmail | null;
307
+ }>;
308
+ /** Sends a failed delivery once more, as long as its payload is still stored. */
309
+ retryDelivery: (id: string) => Promise<MailiyDelivery>;
310
+ /** Replaces the access key; the old one stops working at once. Submissions move to the new id. */
311
+ rotateAccessKey: (id: string) => Promise<{
312
+ id: string;
313
+ }>;
314
+ /** Submissions of all forms in the account (or one website), 25 per page, newest first, in blocks of 1000 (see cursor). */
315
+ searchSubmissions: (filter?: MailiySubmissionFilter & {
316
+ website?: string;
317
+ }) => Promise<MailiySubmissionPage>;
318
+ /** Submissions of all forms in a website, 25 per page, newest first, in blocks of 1000 (see cursor). */
319
+ searchWebsiteSubmissions: (id: string, filter?: MailiySubmissionFilter) => Promise<MailiySubmissionPage>;
320
+ sendTest: (id: string) => Promise<{
321
+ ok: true;
322
+ }>;
323
+ /** Delivered and spam counts per day (7d, 30d) or month (12m), for the account or one website. */
324
+ stats: (range?: "7d" | "30d" | "12m", website?: string) => Promise<MailiyStats>;
325
+ /**
326
+ * Public endpoint: the access key is the form id. With apiKey set, the key is sent too, which
327
+ * forms with server_requires_key need and which skips the origin and captcha checks.
328
+ */
329
+ submit: (accessKey: string, fields: Record<string, string>) => Promise<{
330
+ success: boolean;
331
+ message: string;
332
+ body: {
333
+ data: Record<string, string>;
334
+ message: string;
335
+ };
336
+ }>;
337
+ /** Sends a sample submission and returns the logged delivery. */
338
+ testIntegration: (id: string) => Promise<MailiyDelivery>;
339
+ updateForm: (id: string, patch: MailiyFormPatch) => Promise<MailiyForm>;
340
+ updateIntegration: (id: string, patch: {
341
+ disabled?: boolean;
342
+ label?: string;
343
+ }) => Promise<MailiyIntegration>;
344
+ updateWebsite: (id: string, patch: MailiyWebsitePatch) => Promise<MailiyWebsite>;
345
+ };
346
+ export type Mailiy = ReturnType<typeof createMailiy>;
347
+ /** The HTML snippet that sends a form to Mailiy. */
348
+ export declare const formSnippet: (accessKey: string, baseUrl: string) => string;
349
+ export {};
package/dist/mailiy.js ADDED
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Mailiy SDK: a typed, dependency-free client for the Mailiy REST API.
3
+ *
4
+ * import { createMailiy } from "@mailiy/sdk";
5
+ * const mailiy = createMailiy({ apiKey: process.env.MAILIY_API_KEY });
6
+ * const site = await mailiy.createWebsite({ name: "example.de" });
7
+ * const { id } = await mailiy.createForm({ name: "Contact", website_id: site.id });
8
+ */
9
+ export class MailiyError extends Error {
10
+ name = "MailiyError";
11
+ code;
12
+ status;
13
+ constructor(code, status) {
14
+ super(`Mailiy API error ${status}: ${code}`);
15
+ this.code = code;
16
+ this.status = status;
17
+ }
18
+ }
19
+ const form = (id) => `/forms/${encodeURIComponent(id)}`;
20
+ const integration = (id) => `/integrations/${encodeURIComponent(id)}`;
21
+ const query = (params) => new URLSearchParams(Object.entries(params).flatMap(([key, value]) => value === undefined ? [] : [[key, String(value)]])).toString();
22
+ const site = (id) => `/websites/${encodeURIComponent(id)}`;
23
+ export const DEFAULT_BASE_URL = "https://mailiy.com";
24
+ export const createMailiy = (options = {}) => {
25
+ // Browsers have no process object.
26
+ const envUrl = typeof process === "undefined" ? undefined : process.env.MAILIY_URL;
27
+ const baseUrl = (options.baseUrl ?? envUrl ?? DEFAULT_BASE_URL).replace(/\/$/u, "");
28
+ const doFetch = options.fetch ?? fetch;
29
+ const send = async (path, method = "GET", body) => {
30
+ const headers = { accept: "application/json" };
31
+ if (options.apiKey) {
32
+ headers.authorization = `Bearer ${options.apiKey}`;
33
+ }
34
+ if (body !== undefined) {
35
+ headers["content-type"] = "application/json";
36
+ }
37
+ const res = await doFetch(`${baseUrl}/api${path}`, {
38
+ body: body === undefined ? null : JSON.stringify(body),
39
+ headers,
40
+ method,
41
+ });
42
+ const json = (await res.json().catch(() => ({})));
43
+ if (!res.ok) {
44
+ throw new MailiyError(json.error ?? json.code ?? res.statusText, res.status);
45
+ }
46
+ return { headers: res.headers, json };
47
+ };
48
+ const call = async (path, method = "GET", body) => {
49
+ const { json } = await send(path, method, body);
50
+ return json;
51
+ };
52
+ /** One page of submissions, newest first; pass next_cursor back as cursor for the next one. */
53
+ const pageSubmissions = async (formId, filter = {}) => {
54
+ const params = query({
55
+ cursor: filter.cursor,
56
+ limit: filter.limit,
57
+ spam: filter.spam ? true : undefined,
58
+ });
59
+ const { headers, json } = await send(`${form(formId)}/submissions${params ? `?${params}` : ""}`);
60
+ return { next_cursor: headers.get("x-next-cursor"), submissions: json };
61
+ };
62
+ return {
63
+ /** Without website_id the form goes into the account's only website. */
64
+ createForm: (input) => call("/forms", "POST", input),
65
+ /** Website-wide unless form_id is set. Webhook URLs must be https and public. */
66
+ createIntegration: (websiteId, input) => call(`${site(websiteId)}/integrations`, "POST", input),
67
+ createWebsite: (input) => call("/websites", "POST", input),
68
+ /** Deletes one uploaded file to free storage; the submission stays. */
69
+ deleteFile: (id) => call(`/files/${encodeURIComponent(id)}`, "DELETE"),
70
+ deleteForm: (id) => call(form(id), "DELETE"),
71
+ deleteIntegration: (id) => call(integration(id), "DELETE"),
72
+ deleteSubmission: (formId, submissionId) => call(`${form(formId)}/submissions/${encodeURIComponent(submissionId)}`, "DELETE"),
73
+ /** Deletes the website with all its forms and stored submissions. */
74
+ deleteWebsite: (id) => call(site(id), "DELETE"),
75
+ /** Copies a form with all settings under a new access key. */
76
+ duplicateForm: (id, name) => call(`${form(id)}/duplicate`, "POST", name === undefined ? {} : { name }),
77
+ getForm: (id) => call(form(id)),
78
+ /** Public service health, cached for 30 seconds. */
79
+ getStatus: () => call("/status"),
80
+ getWebsite: (id) => call(site(id)),
81
+ /** The newest 50 deliveries of an integration. */
82
+ listDeliveries: (id) => call(`${integration(id)}/deliveries`),
83
+ listForms: (websiteId) => call(websiteId ? `/forms?website=${encodeURIComponent(websiteId)}` : "/forms"),
84
+ listIntegrations: (websiteId) => call(`${site(websiteId)}/integrations`),
85
+ /** Up to `limit` submissions (default and maximum 500). With spam: true it lists the spam tab (kept for 7 days). */
86
+ listSubmissions: async (formId, filter = {}) => {
87
+ const page = await pageSubmissions(formId, filter);
88
+ return page.submissions;
89
+ },
90
+ /** Uploaded files of all forms in the website (up to 500). */
91
+ listWebsiteFiles: (id) => call(`${site(id)}/files`),
92
+ listWebsites: () => call("/websites"),
93
+ /** Delivers a submission from the spam tab and moves it to the normal list. */
94
+ markNotSpam: (formId, submissionId) => call(`${form(formId)}/submissions/${encodeURIComponent(submissionId)}/not-spam`, "POST"),
95
+ pageSubmissions,
96
+ /** Renders the notification and auto-reply with sample data; patch fields override the saved ones. */
97
+ previewEmail: (id, patch = {}) => call(`${form(id)}/preview`, "POST", patch),
98
+ /** Sends a failed delivery once more, as long as its payload is still stored. */
99
+ retryDelivery: (id) => call(`/deliveries/${encodeURIComponent(id)}/retry`, "POST"),
100
+ /** Replaces the access key; the old one stops working at once. Submissions move to the new id. */
101
+ rotateAccessKey: (id) => call(`${form(id)}/rotate-key`, "POST"),
102
+ /** Submissions of all forms in the account (or one website), 25 per page, newest first, in blocks of 1000 (see cursor). */
103
+ searchSubmissions: (filter = {}) => call(`/submissions?${query(filter)}`),
104
+ /** Submissions of all forms in a website, 25 per page, newest first, in blocks of 1000 (see cursor). */
105
+ searchWebsiteSubmissions: (id, filter = {}) => call(`${site(id)}/submissions?${query(filter)}`),
106
+ sendTest: (id) => call(`${form(id)}/test`, "POST"),
107
+ /** Delivered and spam counts per day (7d, 30d) or month (12m), for the account or one website. */
108
+ stats: (range = "30d", website) => call(`/stats?${query({ range, website })}`),
109
+ /**
110
+ * Public endpoint: the access key is the form id. With apiKey set, the key is sent too, which
111
+ * forms with server_requires_key need and which skips the origin and captcha checks.
112
+ */
113
+ submit: (accessKey, fields) => call("/submit", "POST", {
114
+ ...fields,
115
+ access_key: accessKey,
116
+ }),
117
+ /** Sends a sample submission and returns the logged delivery. */
118
+ testIntegration: (id) => call(`${integration(id)}/test`, "POST"),
119
+ updateForm: (id, patch) => call(form(id), "PATCH", patch),
120
+ updateIntegration: (id, patch) => call(integration(id), "PATCH", patch),
121
+ updateWebsite: (id, patch) => call(site(id), "PATCH", patch),
122
+ };
123
+ };
124
+ /** The HTML snippet that sends a form to Mailiy. */
125
+ export const formSnippet = (accessKey, baseUrl) => `<form action="${baseUrl}/api/submit" method="POST">
126
+ <input type="hidden" name="access_key" value="${accessKey}">
127
+ <input type="email" name="email" required>
128
+ <textarea name="message" required></textarea>
129
+ <input type="checkbox" name="botcheck" style="display:none">
130
+ <button>Send</button>
131
+ </form>`;
package/package.json CHANGED
@@ -1,6 +1,36 @@
1
1
  {
2
2
  "name": "@mailiy/sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Typed, dependency-free client for the Mailiy REST API.",
5
+ "keywords": [
6
+ "api-client",
7
+ "contact-form",
8
+ "form-to-email",
9
+ "forms",
10
+ "mailiy"
11
+ ],
12
+ "homepage": "https://mailiy.com/en/docs/",
13
+ "license": "MIT",
14
+ "author": "26LAB.IO, Christian Gudermann",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "type": "module",
19
+ "sideEffects": false,
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/mailiy.d.ts",
23
+ "default": "./dist/mailiy.js"
24
+ }
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.build.json",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ }
36
+ }