sendhive 0.1.0-beta.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/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # sendhive
2
+
3
+ Tiny TypeScript SDK for the Sendhive email API. The shape mirrors Resend's Node SDK, so switching is a one-line change. Zero runtime dependencies; uses the global `fetch` (Node 18+, Bun, Deno, edge runtimes).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add sendhive # or: npm i sendhive
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```ts
14
+ import { Sendhive } from 'sendhive'
15
+
16
+ const sendhive = new Sendhive('sh_...', { baseUrl: 'http://localhost:3300' })
17
+
18
+ const { data, error } = await sendhive.emails.send({
19
+ from: 'Kuldeep <hello@mail.waimers.in>',
20
+ to: 'a@b.com',
21
+ subject: 'Hi',
22
+ html: '<p>Hi</p>',
23
+ })
24
+
25
+ if (error) console.error(error.statusCode, error.name, error.message)
26
+ else console.log(data.id)
27
+ ```
28
+
29
+ The client never throws for API or network failures. It returns `{ data, error }`; exactly one is `null`.
30
+
31
+ Both options fall back to environment variables: `SENDHIVE_API_KEY` and `SENDHIVE_BASE_URL`.
32
+
33
+ No domain yet? Send from `test@sendhive.test` to an address of someone in your workspace (for example your own). It works before you verify anything, up to 10 a day.
34
+
35
+ ## Send
36
+
37
+ `sendhive.emails.send(options)` calls `POST /api/emails`.
38
+
39
+ | Option | Type | Notes |
40
+ | --- | --- | --- |
41
+ | `from` | `string` | `'Name <a@b.com>'` or `'a@b.com'` |
42
+ | `to` | `string \| string[]` | |
43
+ | `subject` | `string` | required, unless you send a `template` (then optional: it overrides the template's subject) |
44
+ | `html` / `text` | `string` | at least one is required, unless you send a `template` |
45
+ | `template` | `string` | slug or id of a saved template; cannot be combined with `html`, `text` or `react` |
46
+ | `data` | `Record<string, string | number>` | values for the template's `{{variables}}` |
47
+ | `cc`, `bcc` | `string \| string[]` | |
48
+ | `replyTo` | `string \| string[]` | sent as `reply_to` |
49
+ | `headers` | `Record<string, string>` | |
50
+ | `tags` | `{ name, value }[]` | max 10 |
51
+ | `react` | React element | optional; needs the optional peer `@react-email/render` |
52
+
53
+ Returns `{ data: { id }, error: null }`.
54
+
55
+ ### Send a template
56
+
57
+ Design a template once in the dashboard, then send it by its API name and fill its variables with `data`:
58
+
59
+ ```ts
60
+ const { data, error } = await sendhive.emails.send({
61
+ from: 'Shop <orders@yourdomain.com>',
62
+ to: 'customer@example.com',
63
+ template: 'order-shipped',
64
+ data: { order_number: '1042', tracking_url: 'https://example.com/track/1042' },
65
+ })
66
+ ```
67
+
68
+ Every variable the template uses must have a value in `data`, otherwise you get `missing_template_variables`. Values are HTML-escaped for you, and a value used as a link must start with `http://`, `https://` or `mailto:`. An unknown template gives `template_not_found`.
69
+
70
+ ## Get
71
+
72
+ ```ts
73
+ const { data, error } = await sendhive.emails.get('email_id')
74
+ // data.status, data.last_event, data.events, data.sent_at, data.delivered_at ...
75
+ ```
76
+
77
+ ## Errors
78
+
79
+ `error` is `{ statusCode, name, message, fix? }`. `fix` is `{ label, url }`: where to go to resolve the problem (a dashboard page or a docs page). Show it to your users or log it.
80
+
81
+ ```ts
82
+ const { data, error } = await sendhive.emails.send({ ... })
83
+ if (error) {
84
+ console.error(error.name, error.message)
85
+ if (error.fix) console.error(`${error.fix.label}: ${error.fix.url}`)
86
+ }
87
+ ```
88
+
89
+ | statusCode | name | Meaning |
90
+ | --- | --- | --- |
91
+ | 0 | `network_error` | request never reached the server (DNS, refused, offline) |
92
+ | 401 | `missing_api_key` | no `Authorization: Bearer` header (no key given) |
93
+ | 403 | `invalid_api_key` | key unknown or revoked |
94
+ | 404 | `not_found` | email id not found for this key's organization |
95
+ | 403 | `test_sender_recipient_not_allowed` | `test@sendhive.test` can only email members of your workspace |
96
+ | 429 | `test_sender_limit_reached` | more than 10 test-sender emails today |
97
+ | 503 | `test_sender_unavailable` | the server has no test sender configured |
98
+ | 404 | `template_not_found` | no template with that slug or id in this organization |
99
+ | 422 | `validation_error` | bad body; message names the field |
100
+ | 422 | `missing_template_variables` | the template needs values that `data` does not have; message lists them |
101
+ | 429 | `rate_limit_exceeded` | per-key rate limit; retry later |
102
+ | other | as returned by the API | passed through unchanged |
103
+
104
+ ## Self-hosted instance
105
+
106
+ Point `baseUrl` at your deployment (no trailing path):
107
+
108
+ ```ts
109
+ new Sendhive(process.env.SENDHIVE_API_KEY, { baseUrl: 'https://mail-api.example.com' })
110
+ ```
111
+
112
+ or set `SENDHIVE_BASE_URL=https://mail-api.example.com` and call `new Sendhive()`.
113
+
114
+ ## Develop
115
+
116
+ ```bash
117
+ bun install && bun test && bun run build && bunx tsc --noEmit
118
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Sendhive: () => Sendhive,
24
+ default: () => index_default
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ function env(name) {
28
+ return typeof process !== "undefined" ? process.env?.[name] : void 0;
29
+ }
30
+ async function renderReact(element) {
31
+ const specifier = "@react-email/render";
32
+ let mod;
33
+ try {
34
+ mod = await import(
35
+ /* @vite-ignore */
36
+ specifier
37
+ );
38
+ } catch {
39
+ throw new Error("The `react` option needs the optional peer dependency @react-email/render.");
40
+ }
41
+ if (!mod.render) throw new Error("@react-email/render has no `render` export.");
42
+ return mod.render(element);
43
+ }
44
+ var Emails = class {
45
+ constructor(client) {
46
+ this.client = client;
47
+ }
48
+ client;
49
+ async send(options) {
50
+ const { replyTo, react, ...rest } = options;
51
+ let html = rest.html;
52
+ if (react !== void 0) {
53
+ try {
54
+ html = await renderReact(react);
55
+ } catch (err) {
56
+ return {
57
+ data: null,
58
+ error: { statusCode: 422, name: "validation_error", message: err.message }
59
+ };
60
+ }
61
+ }
62
+ const body = {
63
+ ...rest,
64
+ ...html !== void 0 ? { html } : {},
65
+ ...replyTo !== void 0 ? { reply_to: replyTo } : {}
66
+ };
67
+ return this.client.request("POST", "/api/emails", body);
68
+ }
69
+ async get(id) {
70
+ return this.client.request("GET", `/api/emails/${encodeURIComponent(id)}`);
71
+ }
72
+ };
73
+ var Sendhive = class {
74
+ emails;
75
+ apiKey;
76
+ baseUrl;
77
+ constructor(apiKey, options = {}) {
78
+ this.apiKey = apiKey ?? env("SENDHIVE_API_KEY");
79
+ const baseUrl = options.baseUrl ?? env("SENDHIVE_BASE_URL");
80
+ if (!baseUrl) throw new Error("Sendhive: set `baseUrl` or SENDHIVE_BASE_URL to your Sendhive server.");
81
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
82
+ this.emails = new Emails(this);
83
+ }
84
+ /** @internal */
85
+ async request(method, path, body) {
86
+ const headers = { Accept: "application/json" };
87
+ if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
88
+ if (body !== void 0) headers["Content-Type"] = "application/json";
89
+ let res;
90
+ try {
91
+ res = await fetch(`${this.baseUrl}${path}`, {
92
+ method,
93
+ headers,
94
+ body: body === void 0 ? void 0 : JSON.stringify(body)
95
+ });
96
+ } catch (err) {
97
+ return {
98
+ data: null,
99
+ error: {
100
+ statusCode: 0,
101
+ name: "network_error",
102
+ message: err instanceof Error ? err.message : String(err)
103
+ }
104
+ };
105
+ }
106
+ let json;
107
+ try {
108
+ json = await res.json();
109
+ } catch {
110
+ json = null;
111
+ }
112
+ if (!res.ok) {
113
+ const e = json ?? {};
114
+ return {
115
+ data: null,
116
+ error: {
117
+ statusCode: e.statusCode ?? res.status,
118
+ name: e.name ?? "application_error",
119
+ message: e.message ?? (res.statusText || "Unexpected response"),
120
+ ...e.fix ? { fix: e.fix } : {}
121
+ }
122
+ };
123
+ }
124
+ return { data: json, error: null };
125
+ }
126
+ };
127
+ var index_default = Sendhive;
128
+ // Annotate the CommonJS export names for ESM import in node:
129
+ 0 && (module.exports = {
130
+ Sendhive
131
+ });
132
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n Email,\n Result,\n SendEmailOptions,\n SendEmailResponse,\n SendhiveError,\n SendhiveOptions,\n} from './types'\n\nexport type {\n Email,\n EmailEvent,\n EmailTag,\n Recipients,\n Result,\n SendEmailOptions,\n SendEmailResponse,\n SendhiveError,\n SendhiveErrorFix,\n SendhiveOptions,\n TemplateData,\n} from './types'\n\nfunction env(name: string): string | undefined {\n return typeof process !== 'undefined' ? process.env?.[name] : undefined\n}\n\n/** Optional peer: only loaded when the caller passes `react`. */\nasync function renderReact(element: unknown): Promise<string> {\n const specifier = '@react-email/render'\n let mod: { render?: (el: unknown) => string | Promise<string> }\n try {\n mod = await import(/* @vite-ignore */ specifier)\n } catch {\n throw new Error('The `react` option needs the optional peer dependency @react-email/render.')\n }\n if (!mod.render) throw new Error('@react-email/render has no `render` export.')\n return mod.render(element)\n}\n\nclass Emails {\n constructor(private readonly client: Sendhive) {}\n\n async send(options: SendEmailOptions): Promise<Result<SendEmailResponse>> {\n const { replyTo, react, ...rest } = options\n let html = rest.html\n if (react !== undefined) {\n try {\n html = await renderReact(react)\n } catch (err) {\n return {\n data: null,\n error: { statusCode: 422, name: 'validation_error', message: (err as Error).message },\n }\n }\n }\n const body = {\n ...rest,\n ...(html !== undefined ? { html } : {}),\n ...(replyTo !== undefined ? { reply_to: replyTo } : {}),\n }\n return this.client.request<SendEmailResponse>('POST', '/api/emails', body)\n }\n\n async get(id: string): Promise<Result<Email>> {\n return this.client.request<Email>('GET', `/api/emails/${encodeURIComponent(id)}`)\n }\n}\n\nexport class Sendhive {\n readonly emails: Emails\n private readonly apiKey: string | undefined\n private readonly baseUrl: string\n\n constructor(apiKey?: string, options: SendhiveOptions = {}) {\n this.apiKey = apiKey ?? env('SENDHIVE_API_KEY')\n // No default host: an API key must never be sent to a server the caller did not choose.\n const baseUrl = options.baseUrl ?? env('SENDHIVE_BASE_URL')\n if (!baseUrl) throw new Error('Sendhive: set `baseUrl` or SENDHIVE_BASE_URL to your Sendhive server.')\n this.baseUrl = baseUrl.replace(/\\/+$/, '')\n this.emails = new Emails(this)\n }\n\n /** @internal */\n async request<T>(method: 'GET' | 'POST', path: string, body?: unknown): Promise<Result<T>> {\n const headers: Record<string, string> = { Accept: 'application/json' }\n if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`\n if (body !== undefined) headers['Content-Type'] = 'application/json'\n\n let res: Response\n try {\n res = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n })\n } catch (err) {\n return {\n data: null,\n error: {\n statusCode: 0,\n name: 'network_error',\n message: err instanceof Error ? err.message : String(err),\n },\n }\n }\n\n let json: unknown\n try {\n json = await res.json()\n } catch {\n json = null\n }\n\n if (!res.ok) {\n const e = (json ?? {}) as Partial<SendhiveError>\n return {\n data: null,\n error: {\n statusCode: e.statusCode ?? res.status,\n name: e.name ?? 'application_error',\n message: e.message ?? (res.statusText || 'Unexpected response'),\n ...(e.fix ? { fix: e.fix } : {}),\n },\n }\n }\n return { data: json as T, error: null }\n }\n}\n\nexport default Sendhive\n"],"mappings":"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","names":[]}
@@ -0,0 +1,113 @@
1
+ /** Where to go to fix an error: a dashboard page or a docs page. */
2
+ interface SendhiveErrorFix {
3
+ label: string;
4
+ url: string;
5
+ }
6
+ interface SendhiveError {
7
+ statusCode: number;
8
+ name: string;
9
+ message: string;
10
+ /** Set on errors from the API; not on `network_error`. */
11
+ fix?: SendhiveErrorFix;
12
+ }
13
+ type Recipients = string | string[];
14
+ interface EmailTag {
15
+ name: string;
16
+ value: string;
17
+ }
18
+ interface SendEmailBase {
19
+ from: string;
20
+ to: Recipients;
21
+ cc?: Recipients;
22
+ bcc?: Recipients;
23
+ replyTo?: Recipients;
24
+ headers?: Record<string, string>;
25
+ tags?: EmailTag[];
26
+ }
27
+ /** Values for a template's {{variables}}. Strings and numbers only; the server escapes them. */
28
+ type TemplateData = Record<string, string | number>;
29
+ /** A message written by you: `subject` and at least one of `html`, `text` or `react`. */
30
+ type SendContent = {
31
+ subject: string;
32
+ template?: never;
33
+ data?: never;
34
+ } & ({
35
+ html: string;
36
+ text?: string;
37
+ react?: unknown;
38
+ } | {
39
+ text: string;
40
+ html?: string;
41
+ react?: unknown;
42
+ } | {
43
+ react: unknown;
44
+ html?: string;
45
+ text?: string;
46
+ });
47
+ /**
48
+ * A saved template, by its slug or id. Every variable the template uses must have a value in
49
+ * `data`. `subject` is optional: the template's own subject is used unless you pass one.
50
+ */
51
+ type SendTemplate = {
52
+ template: string;
53
+ data?: TemplateData;
54
+ subject?: string;
55
+ html?: never;
56
+ text?: never;
57
+ react?: never;
58
+ };
59
+ /** Either your own content (`html` / `text` / `react`) or a saved `template` with `data`, never both. */
60
+ type SendEmailOptions = SendEmailBase & (SendContent | SendTemplate);
61
+ interface SendEmailResponse {
62
+ id: string;
63
+ }
64
+ interface EmailEvent {
65
+ type: string;
66
+ occurred_at: string;
67
+ }
68
+ interface Email {
69
+ object: 'email';
70
+ id: string;
71
+ from: string;
72
+ to: string[];
73
+ cc: string[];
74
+ bcc: string[];
75
+ reply_to: string[];
76
+ subject: string;
77
+ html: string | null;
78
+ text: string | null;
79
+ status: string;
80
+ last_event: string;
81
+ error: string | null;
82
+ created_at: string;
83
+ sent_at: string | null;
84
+ delivered_at: string | null;
85
+ events: EmailEvent[];
86
+ }
87
+ type Result<T> = {
88
+ data: T;
89
+ error: null;
90
+ } | {
91
+ data: null;
92
+ error: SendhiveError;
93
+ };
94
+ interface SendhiveOptions {
95
+ baseUrl?: string;
96
+ }
97
+
98
+ declare class Emails {
99
+ private readonly client;
100
+ constructor(client: Sendhive);
101
+ send(options: SendEmailOptions): Promise<Result<SendEmailResponse>>;
102
+ get(id: string): Promise<Result<Email>>;
103
+ }
104
+ declare class Sendhive {
105
+ readonly emails: Emails;
106
+ private readonly apiKey;
107
+ private readonly baseUrl;
108
+ constructor(apiKey?: string, options?: SendhiveOptions);
109
+ /** @internal */
110
+ request<T>(method: 'GET' | 'POST', path: string, body?: unknown): Promise<Result<T>>;
111
+ }
112
+
113
+ export { type Email, type EmailEvent, type EmailTag, type Recipients, type Result, type SendEmailOptions, type SendEmailResponse, Sendhive, type SendhiveError, type SendhiveErrorFix, type SendhiveOptions, type TemplateData, Sendhive as default };
@@ -0,0 +1,113 @@
1
+ /** Where to go to fix an error: a dashboard page or a docs page. */
2
+ interface SendhiveErrorFix {
3
+ label: string;
4
+ url: string;
5
+ }
6
+ interface SendhiveError {
7
+ statusCode: number;
8
+ name: string;
9
+ message: string;
10
+ /** Set on errors from the API; not on `network_error`. */
11
+ fix?: SendhiveErrorFix;
12
+ }
13
+ type Recipients = string | string[];
14
+ interface EmailTag {
15
+ name: string;
16
+ value: string;
17
+ }
18
+ interface SendEmailBase {
19
+ from: string;
20
+ to: Recipients;
21
+ cc?: Recipients;
22
+ bcc?: Recipients;
23
+ replyTo?: Recipients;
24
+ headers?: Record<string, string>;
25
+ tags?: EmailTag[];
26
+ }
27
+ /** Values for a template's {{variables}}. Strings and numbers only; the server escapes them. */
28
+ type TemplateData = Record<string, string | number>;
29
+ /** A message written by you: `subject` and at least one of `html`, `text` or `react`. */
30
+ type SendContent = {
31
+ subject: string;
32
+ template?: never;
33
+ data?: never;
34
+ } & ({
35
+ html: string;
36
+ text?: string;
37
+ react?: unknown;
38
+ } | {
39
+ text: string;
40
+ html?: string;
41
+ react?: unknown;
42
+ } | {
43
+ react: unknown;
44
+ html?: string;
45
+ text?: string;
46
+ });
47
+ /**
48
+ * A saved template, by its slug or id. Every variable the template uses must have a value in
49
+ * `data`. `subject` is optional: the template's own subject is used unless you pass one.
50
+ */
51
+ type SendTemplate = {
52
+ template: string;
53
+ data?: TemplateData;
54
+ subject?: string;
55
+ html?: never;
56
+ text?: never;
57
+ react?: never;
58
+ };
59
+ /** Either your own content (`html` / `text` / `react`) or a saved `template` with `data`, never both. */
60
+ type SendEmailOptions = SendEmailBase & (SendContent | SendTemplate);
61
+ interface SendEmailResponse {
62
+ id: string;
63
+ }
64
+ interface EmailEvent {
65
+ type: string;
66
+ occurred_at: string;
67
+ }
68
+ interface Email {
69
+ object: 'email';
70
+ id: string;
71
+ from: string;
72
+ to: string[];
73
+ cc: string[];
74
+ bcc: string[];
75
+ reply_to: string[];
76
+ subject: string;
77
+ html: string | null;
78
+ text: string | null;
79
+ status: string;
80
+ last_event: string;
81
+ error: string | null;
82
+ created_at: string;
83
+ sent_at: string | null;
84
+ delivered_at: string | null;
85
+ events: EmailEvent[];
86
+ }
87
+ type Result<T> = {
88
+ data: T;
89
+ error: null;
90
+ } | {
91
+ data: null;
92
+ error: SendhiveError;
93
+ };
94
+ interface SendhiveOptions {
95
+ baseUrl?: string;
96
+ }
97
+
98
+ declare class Emails {
99
+ private readonly client;
100
+ constructor(client: Sendhive);
101
+ send(options: SendEmailOptions): Promise<Result<SendEmailResponse>>;
102
+ get(id: string): Promise<Result<Email>>;
103
+ }
104
+ declare class Sendhive {
105
+ readonly emails: Emails;
106
+ private readonly apiKey;
107
+ private readonly baseUrl;
108
+ constructor(apiKey?: string, options?: SendhiveOptions);
109
+ /** @internal */
110
+ request<T>(method: 'GET' | 'POST', path: string, body?: unknown): Promise<Result<T>>;
111
+ }
112
+
113
+ export { type Email, type EmailEvent, type EmailTag, type Recipients, type Result, type SendEmailOptions, type SendEmailResponse, Sendhive, type SendhiveError, type SendhiveErrorFix, type SendhiveOptions, type TemplateData, Sendhive as default };
package/dist/index.js ADDED
@@ -0,0 +1,107 @@
1
+ // src/index.ts
2
+ function env(name) {
3
+ return typeof process !== "undefined" ? process.env?.[name] : void 0;
4
+ }
5
+ async function renderReact(element) {
6
+ const specifier = "@react-email/render";
7
+ let mod;
8
+ try {
9
+ mod = await import(
10
+ /* @vite-ignore */
11
+ specifier
12
+ );
13
+ } catch {
14
+ throw new Error("The `react` option needs the optional peer dependency @react-email/render.");
15
+ }
16
+ if (!mod.render) throw new Error("@react-email/render has no `render` export.");
17
+ return mod.render(element);
18
+ }
19
+ var Emails = class {
20
+ constructor(client) {
21
+ this.client = client;
22
+ }
23
+ client;
24
+ async send(options) {
25
+ const { replyTo, react, ...rest } = options;
26
+ let html = rest.html;
27
+ if (react !== void 0) {
28
+ try {
29
+ html = await renderReact(react);
30
+ } catch (err) {
31
+ return {
32
+ data: null,
33
+ error: { statusCode: 422, name: "validation_error", message: err.message }
34
+ };
35
+ }
36
+ }
37
+ const body = {
38
+ ...rest,
39
+ ...html !== void 0 ? { html } : {},
40
+ ...replyTo !== void 0 ? { reply_to: replyTo } : {}
41
+ };
42
+ return this.client.request("POST", "/api/emails", body);
43
+ }
44
+ async get(id) {
45
+ return this.client.request("GET", `/api/emails/${encodeURIComponent(id)}`);
46
+ }
47
+ };
48
+ var Sendhive = class {
49
+ emails;
50
+ apiKey;
51
+ baseUrl;
52
+ constructor(apiKey, options = {}) {
53
+ this.apiKey = apiKey ?? env("SENDHIVE_API_KEY");
54
+ const baseUrl = options.baseUrl ?? env("SENDHIVE_BASE_URL");
55
+ if (!baseUrl) throw new Error("Sendhive: set `baseUrl` or SENDHIVE_BASE_URL to your Sendhive server.");
56
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
57
+ this.emails = new Emails(this);
58
+ }
59
+ /** @internal */
60
+ async request(method, path, body) {
61
+ const headers = { Accept: "application/json" };
62
+ if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
63
+ if (body !== void 0) headers["Content-Type"] = "application/json";
64
+ let res;
65
+ try {
66
+ res = await fetch(`${this.baseUrl}${path}`, {
67
+ method,
68
+ headers,
69
+ body: body === void 0 ? void 0 : JSON.stringify(body)
70
+ });
71
+ } catch (err) {
72
+ return {
73
+ data: null,
74
+ error: {
75
+ statusCode: 0,
76
+ name: "network_error",
77
+ message: err instanceof Error ? err.message : String(err)
78
+ }
79
+ };
80
+ }
81
+ let json;
82
+ try {
83
+ json = await res.json();
84
+ } catch {
85
+ json = null;
86
+ }
87
+ if (!res.ok) {
88
+ const e = json ?? {};
89
+ return {
90
+ data: null,
91
+ error: {
92
+ statusCode: e.statusCode ?? res.status,
93
+ name: e.name ?? "application_error",
94
+ message: e.message ?? (res.statusText || "Unexpected response"),
95
+ ...e.fix ? { fix: e.fix } : {}
96
+ }
97
+ };
98
+ }
99
+ return { data: json, error: null };
100
+ }
101
+ };
102
+ var index_default = Sendhive;
103
+ export {
104
+ Sendhive,
105
+ index_default as default
106
+ };
107
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n Email,\n Result,\n SendEmailOptions,\n SendEmailResponse,\n SendhiveError,\n SendhiveOptions,\n} from './types'\n\nexport type {\n Email,\n EmailEvent,\n EmailTag,\n Recipients,\n Result,\n SendEmailOptions,\n SendEmailResponse,\n SendhiveError,\n SendhiveErrorFix,\n SendhiveOptions,\n TemplateData,\n} from './types'\n\nfunction env(name: string): string | undefined {\n return typeof process !== 'undefined' ? process.env?.[name] : undefined\n}\n\n/** Optional peer: only loaded when the caller passes `react`. */\nasync function renderReact(element: unknown): Promise<string> {\n const specifier = '@react-email/render'\n let mod: { render?: (el: unknown) => string | Promise<string> }\n try {\n mod = await import(/* @vite-ignore */ specifier)\n } catch {\n throw new Error('The `react` option needs the optional peer dependency @react-email/render.')\n }\n if (!mod.render) throw new Error('@react-email/render has no `render` export.')\n return mod.render(element)\n}\n\nclass Emails {\n constructor(private readonly client: Sendhive) {}\n\n async send(options: SendEmailOptions): Promise<Result<SendEmailResponse>> {\n const { replyTo, react, ...rest } = options\n let html = rest.html\n if (react !== undefined) {\n try {\n html = await renderReact(react)\n } catch (err) {\n return {\n data: null,\n error: { statusCode: 422, name: 'validation_error', message: (err as Error).message },\n }\n }\n }\n const body = {\n ...rest,\n ...(html !== undefined ? { html } : {}),\n ...(replyTo !== undefined ? { reply_to: replyTo } : {}),\n }\n return this.client.request<SendEmailResponse>('POST', '/api/emails', body)\n }\n\n async get(id: string): Promise<Result<Email>> {\n return this.client.request<Email>('GET', `/api/emails/${encodeURIComponent(id)}`)\n }\n}\n\nexport class Sendhive {\n readonly emails: Emails\n private readonly apiKey: string | undefined\n private readonly baseUrl: string\n\n constructor(apiKey?: string, options: SendhiveOptions = {}) {\n this.apiKey = apiKey ?? env('SENDHIVE_API_KEY')\n // No default host: an API key must never be sent to a server the caller did not choose.\n const baseUrl = options.baseUrl ?? env('SENDHIVE_BASE_URL')\n if (!baseUrl) throw new Error('Sendhive: set `baseUrl` or SENDHIVE_BASE_URL to your Sendhive server.')\n this.baseUrl = baseUrl.replace(/\\/+$/, '')\n this.emails = new Emails(this)\n }\n\n /** @internal */\n async request<T>(method: 'GET' | 'POST', path: string, body?: unknown): Promise<Result<T>> {\n const headers: Record<string, string> = { Accept: 'application/json' }\n if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`\n if (body !== undefined) headers['Content-Type'] = 'application/json'\n\n let res: Response\n try {\n res = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n })\n } catch (err) {\n return {\n data: null,\n error: {\n statusCode: 0,\n name: 'network_error',\n message: err instanceof Error ? err.message : String(err),\n },\n }\n }\n\n let json: unknown\n try {\n json = await res.json()\n } catch {\n json = null\n }\n\n if (!res.ok) {\n const e = (json ?? {}) as Partial<SendhiveError>\n return {\n data: null,\n error: {\n statusCode: e.statusCode ?? res.status,\n name: e.name ?? 'application_error',\n message: e.message ?? (res.statusText || 'Unexpected response'),\n ...(e.fix ? { fix: e.fix } : {}),\n },\n }\n }\n return { data: json as T, error: null }\n }\n}\n\nexport default Sendhive\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "sendhive",
3
+ "version": "0.1.0-beta.0",
4
+ "description": "Tiny TypeScript SDK for the Sendhive email API (Resend-compatible shape).",
5
+ "keywords": [
6
+ "email",
7
+ "email-api",
8
+ "sendhive",
9
+ "transactional-email",
10
+ "amazon-ses",
11
+ "react-email",
12
+ "sdk"
13
+ ],
14
+ "license": "UNLICENSED",
15
+ "type": "module",
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/index.d.cts",
27
+ "default": "./dist/index.cjs"
28
+ }
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "sideEffects": false,
36
+ "engines": {
37
+ "node": ">=18"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "test": "bun test",
45
+ "typecheck": "tsc --noEmit",
46
+ "prepublishOnly": "bun run test && bun run typecheck && bun run build",
47
+ "bump:beta": "npm version prerelease --preid beta --no-git-tag-version",
48
+ "release:beta": "npm publish --tag beta",
49
+ "release": "npm publish"
50
+ },
51
+ "peerDependencies": {
52
+ "@react-email/render": ">=1.0.0"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "@react-email/render": {
56
+ "optional": true
57
+ }
58
+ },
59
+ "devDependencies": {
60
+ "@react-email/render": "^2.0.6",
61
+ "@react-email/components": "^1.0.12",
62
+ "@types/bun": "latest",
63
+ "@types/react": "^19.3.0",
64
+ "react": "^19.3.0",
65
+ "react-dom": "^19.3.0",
66
+ "tsup": "^8.5.0",
67
+ "typescript": "^5.9.0"
68
+ }
69
+ }