@hasna/emails-sdk 0.4.26

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.
@@ -0,0 +1,311 @@
1
+ /**
2
+ * @hasna/emails-sdk
3
+ * Zero-dependency TypeScript client for the @hasna/emails REST API.
4
+ * Works in Node.js, Bun, Deno, and browser environments.
5
+ */
6
+ export interface Provider {
7
+ id: string;
8
+ name: string;
9
+ type: string;
10
+ active: boolean;
11
+ created_at: string;
12
+ updated_at: string;
13
+ }
14
+ export interface Domain {
15
+ id: string;
16
+ provider_id: string;
17
+ domain: string;
18
+ dkim_status: string;
19
+ spf_status: string;
20
+ dmarc_status: string;
21
+ verified_at: string | null;
22
+ created_at: string;
23
+ }
24
+ export interface EmailAddress {
25
+ id: string;
26
+ provider_id: string;
27
+ email: string;
28
+ display_name: string | null;
29
+ verified: boolean;
30
+ created_at: string;
31
+ }
32
+ export interface Email {
33
+ id: string;
34
+ provider_id: string;
35
+ provider_message_id: string | null;
36
+ from_address: string;
37
+ to_addresses: string[];
38
+ subject: string;
39
+ status: string;
40
+ sent_at: string;
41
+ }
42
+ export interface EmailEvent {
43
+ id: string;
44
+ email_id: string | null;
45
+ provider_id: string;
46
+ type: string;
47
+ recipient: string | null;
48
+ occurred_at: string;
49
+ }
50
+ export interface Contact {
51
+ id: string;
52
+ email: string;
53
+ name: string | null;
54
+ send_count: number;
55
+ bounce_count: number;
56
+ suppressed: boolean;
57
+ }
58
+ export interface Template {
59
+ id: string;
60
+ name: string;
61
+ subject_template: string;
62
+ html_template: string | null;
63
+ text_template: string | null;
64
+ created_at: string;
65
+ }
66
+ export interface Group {
67
+ id: string;
68
+ name: string;
69
+ description: string | null;
70
+ created_at: string;
71
+ }
72
+ export interface GroupMember {
73
+ email: string;
74
+ name: string | null;
75
+ }
76
+ export interface ScheduledEmail {
77
+ id: string;
78
+ status: string;
79
+ scheduled_at: string;
80
+ subject: string;
81
+ }
82
+ export interface Sequence {
83
+ id: string;
84
+ name: string;
85
+ status: string;
86
+ created_at: string;
87
+ }
88
+ export interface SequenceStep {
89
+ id: string;
90
+ sequence_id: string;
91
+ step_number: number;
92
+ delay_hours: number;
93
+ template_name: string;
94
+ }
95
+ export interface Enrollment {
96
+ id: string;
97
+ sequence_id: string;
98
+ contact_email: string;
99
+ current_step: number;
100
+ status: string;
101
+ }
102
+ export interface SandboxEmail {
103
+ id: string;
104
+ from_address: string;
105
+ to_addresses: string[];
106
+ subject: string;
107
+ created_at: string;
108
+ }
109
+ export interface InboundEmail {
110
+ id: string;
111
+ from_address: string;
112
+ to_addresses: string[];
113
+ subject: string;
114
+ in_reply_to_email_id: string | null;
115
+ received_at: string;
116
+ }
117
+ export interface WarmingSchedule {
118
+ id: string;
119
+ domain: string;
120
+ target_daily_volume: number;
121
+ start_date: string;
122
+ status: string;
123
+ }
124
+ export interface Stats {
125
+ sent: number;
126
+ delivered: number;
127
+ bounced: number;
128
+ complained: number;
129
+ opened: number;
130
+ clicked: number;
131
+ delivery_rate: number;
132
+ bounce_rate: number;
133
+ open_rate: number;
134
+ }
135
+ export interface Analytics {
136
+ dailyVolume: {
137
+ date: string;
138
+ count: number;
139
+ }[];
140
+ topRecipients: {
141
+ email: string;
142
+ count: number;
143
+ }[];
144
+ busiestHours: {
145
+ hour: number;
146
+ count: number;
147
+ }[];
148
+ deliveryTrend: {
149
+ date: string;
150
+ sent: number;
151
+ delivered: number;
152
+ bounced: number;
153
+ }[];
154
+ }
155
+ export interface DnsRecord {
156
+ type: string;
157
+ name: string;
158
+ value: string;
159
+ purpose: string;
160
+ }
161
+ export interface DoctorCheck {
162
+ name: string;
163
+ status: string;
164
+ message: string;
165
+ }
166
+ export interface EmailsClientOptions {
167
+ /** Base URL of the emails server, e.g. "http://localhost:3900" */
168
+ serverUrl: string;
169
+ }
170
+ export declare class EmailsClient {
171
+ private readonly baseUrl;
172
+ constructor(options: EmailsClientOptions);
173
+ private request;
174
+ listProviders(): Promise<Provider[]>;
175
+ addProvider(body: {
176
+ name: string;
177
+ type: string;
178
+ api_key?: string;
179
+ region?: string;
180
+ access_key?: string;
181
+ secret_key?: string;
182
+ oauth_client_id?: string;
183
+ oauth_client_secret?: string;
184
+ }): Promise<Provider>;
185
+ updateProvider(id: string, body: Record<string, unknown>): Promise<Provider>;
186
+ removeProvider(id: string): Promise<void>;
187
+ reauthProvider(id: string): Promise<{
188
+ ok: boolean;
189
+ provider: Provider;
190
+ }>;
191
+ listDomains(providerId?: string): Promise<Domain[]>;
192
+ addDomain(body: {
193
+ provider_id: string;
194
+ domain: string;
195
+ }): Promise<Domain>;
196
+ getDnsRecords(id: string): Promise<DnsRecord[]>;
197
+ verifyDomain(id: string): Promise<unknown>;
198
+ removeDomain(id: string): Promise<void>;
199
+ listAddresses(): Promise<EmailAddress[]>;
200
+ addAddress(body: {
201
+ provider_id: string;
202
+ email: string;
203
+ display_name?: string;
204
+ }): Promise<EmailAddress>;
205
+ removeAddress(id: string): Promise<void>;
206
+ listEmails(params?: {
207
+ status?: string;
208
+ limit?: number;
209
+ offset?: number;
210
+ provider_id?: string;
211
+ }): Promise<Email[]>;
212
+ getEmail(id: string): Promise<Email>;
213
+ searchEmails(query: string, params?: {
214
+ since?: string;
215
+ limit?: number;
216
+ }): Promise<Email[]>;
217
+ getEmailContent(id: string): Promise<{
218
+ html: string | null;
219
+ text_body: string | null;
220
+ headers: Record<string, string>;
221
+ }>;
222
+ listEvents(params?: {
223
+ type?: string;
224
+ limit?: number;
225
+ }): Promise<EmailEvent[]>;
226
+ getStats(period?: string): Promise<Stats>;
227
+ getAnalytics(params?: {
228
+ period?: string;
229
+ provider_id?: string;
230
+ }): Promise<Analytics>;
231
+ pull(providerId?: string): Promise<Record<string, number>>;
232
+ listContacts(suppressed?: boolean): Promise<Contact[]>;
233
+ suppressContact(email: string): Promise<void>;
234
+ unsuppressContact(email: string): Promise<void>;
235
+ listTemplates(): Promise<Template[]>;
236
+ addTemplate(body: {
237
+ name: string;
238
+ subject_template: string;
239
+ html_template?: string;
240
+ text_template?: string;
241
+ }): Promise<Template>;
242
+ removeTemplate(id: string): Promise<void>;
243
+ listGroups(): Promise<Group[]>;
244
+ createGroup(body: {
245
+ name: string;
246
+ description?: string;
247
+ }): Promise<Group>;
248
+ deleteGroup(id: string): Promise<void>;
249
+ listGroupMembers(id: string): Promise<GroupMember[]>;
250
+ addGroupMember(id: string, body: {
251
+ email: string;
252
+ name?: string;
253
+ }): Promise<void>;
254
+ removeGroupMember(id: string, email: string): Promise<void>;
255
+ listScheduled(status?: string): Promise<ScheduledEmail[]>;
256
+ cancelScheduled(id: string): Promise<void>;
257
+ listSequences(): Promise<Sequence[]>;
258
+ createSequence(body: {
259
+ name: string;
260
+ description?: string;
261
+ }): Promise<Sequence>;
262
+ deleteSequence(id: string): Promise<void>;
263
+ listSequenceSteps(id: string): Promise<SequenceStep[]>;
264
+ addSequenceStep(id: string, body: {
265
+ step_number: number;
266
+ delay_hours: number;
267
+ template_name: string;
268
+ }): Promise<SequenceStep>;
269
+ listEnrollments(id: string): Promise<Enrollment[]>;
270
+ enrollContact(id: string, body: {
271
+ contact_email: string;
272
+ provider_id?: string;
273
+ }): Promise<Enrollment>;
274
+ unenrollContact(id: string, email: string): Promise<void>;
275
+ listSandboxEmails(params?: {
276
+ provider_id?: string;
277
+ limit?: number;
278
+ }): Promise<SandboxEmail[]>;
279
+ getSandboxEmail(id: string): Promise<SandboxEmail>;
280
+ clearSandboxEmails(providerId?: string): Promise<{
281
+ deleted: number;
282
+ }>;
283
+ listInboundEmails(params?: {
284
+ provider_id?: string;
285
+ limit?: number;
286
+ }): Promise<InboundEmail[]>;
287
+ getInboundEmail(id: string): Promise<InboundEmail>;
288
+ clearInboundEmails(providerId?: string): Promise<void>;
289
+ listWarmingSchedules(): Promise<WarmingSchedule[]>;
290
+ createWarmingSchedule(body: {
291
+ domain: string;
292
+ target_daily_volume: number;
293
+ start_date?: string;
294
+ }): Promise<WarmingSchedule>;
295
+ getWarmingStatus(domain: string): Promise<WarmingSchedule & {
296
+ today_limit: number;
297
+ today_sent: number;
298
+ current_day: number;
299
+ }>;
300
+ updateWarmingStatus(domain: string, status: string): Promise<WarmingSchedule>;
301
+ deleteWarmingSchedule(domain: string): Promise<void>;
302
+ exportEmails(format?: "csv" | "json", params?: {
303
+ provider_id?: string;
304
+ since?: string;
305
+ }): Promise<string>;
306
+ exportEvents(format?: "csv" | "json", params?: {
307
+ provider_id?: string;
308
+ since?: string;
309
+ }): Promise<string>;
310
+ runDoctor(): Promise<DoctorCheck[]>;
311
+ }
package/dist/index.js ADDED
@@ -0,0 +1,221 @@
1
+ // src/index.ts
2
+ async function checkResponse(res) {
3
+ if (!res.ok) {
4
+ let message = `HTTP ${res.status}`;
5
+ try {
6
+ const body = await res.json();
7
+ if (body.error)
8
+ message = body.error;
9
+ } catch {}
10
+ throw new Error(message);
11
+ }
12
+ }
13
+ function qs(params) {
14
+ if (!params)
15
+ return "";
16
+ const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== null);
17
+ if (entries.length === 0)
18
+ return "";
19
+ const sp = new URLSearchParams;
20
+ for (const [k, v] of entries)
21
+ sp.set(k, String(v));
22
+ return "?" + sp.toString();
23
+ }
24
+
25
+ class EmailsClient {
26
+ baseUrl;
27
+ constructor(options) {
28
+ this.baseUrl = options.serverUrl.replace(/\/$/, "");
29
+ }
30
+ async request(path, init = {}) {
31
+ const res = await fetch(this.baseUrl + path, {
32
+ ...init,
33
+ headers: { "Content-Type": "application/json", ...init.headers }
34
+ });
35
+ await checkResponse(res);
36
+ return res.json();
37
+ }
38
+ async listProviders() {
39
+ return this.request("/api/providers");
40
+ }
41
+ async addProvider(body) {
42
+ return this.request("/api/providers", { method: "POST", body: JSON.stringify(body) });
43
+ }
44
+ async updateProvider(id, body) {
45
+ return this.request(`/api/providers/${id}`, { method: "PUT", body: JSON.stringify(body) });
46
+ }
47
+ async removeProvider(id) {
48
+ await this.request(`/api/providers/${id}`, { method: "DELETE" });
49
+ }
50
+ async reauthProvider(id) {
51
+ return this.request(`/api/providers/${id}/auth`, { method: "POST" });
52
+ }
53
+ async listDomains(providerId) {
54
+ return this.request(`/api/domains${qs({ provider_id: providerId })}`);
55
+ }
56
+ async addDomain(body) {
57
+ return this.request("/api/domains", { method: "POST", body: JSON.stringify(body) });
58
+ }
59
+ async getDnsRecords(id) {
60
+ return this.request(`/api/domains/${id}/dns`);
61
+ }
62
+ async verifyDomain(id) {
63
+ return this.request(`/api/domains/${id}/verify`, { method: "POST" });
64
+ }
65
+ async removeDomain(id) {
66
+ await this.request(`/api/domains/${id}`, { method: "DELETE" });
67
+ }
68
+ async listAddresses() {
69
+ return this.request("/api/addresses");
70
+ }
71
+ async addAddress(body) {
72
+ return this.request("/api/addresses", { method: "POST", body: JSON.stringify(body) });
73
+ }
74
+ async removeAddress(id) {
75
+ await this.request(`/api/addresses/${id}`, { method: "DELETE" });
76
+ }
77
+ async listEmails(params) {
78
+ return this.request(`/api/emails${qs(params)}`);
79
+ }
80
+ async getEmail(id) {
81
+ return this.request(`/api/emails/${id}`);
82
+ }
83
+ async searchEmails(query, params) {
84
+ return this.request(`/api/emails/search${qs({ q: query, ...params })}`);
85
+ }
86
+ async getEmailContent(id) {
87
+ return this.request(`/api/email-content/${id}`);
88
+ }
89
+ async listEvents(params) {
90
+ return this.request(`/api/events${qs(params)}`);
91
+ }
92
+ async getStats(period) {
93
+ return this.request(`/api/stats${qs({ period })}`);
94
+ }
95
+ async getAnalytics(params) {
96
+ return this.request(`/api/analytics${qs(params)}`);
97
+ }
98
+ async pull(providerId) {
99
+ return this.request("/api/pull", {
100
+ method: "POST",
101
+ body: providerId ? JSON.stringify({ provider_id: providerId }) : "{}"
102
+ });
103
+ }
104
+ async listContacts(suppressed) {
105
+ return this.request(`/api/contacts${qs({ suppressed: suppressed !== undefined ? String(suppressed) : undefined })}`);
106
+ }
107
+ async suppressContact(email) {
108
+ await this.request(`/api/contacts/${encodeURIComponent(email)}/suppress`, { method: "POST" });
109
+ }
110
+ async unsuppressContact(email) {
111
+ await this.request(`/api/contacts/${encodeURIComponent(email)}/unsuppress`, { method: "POST" });
112
+ }
113
+ async listTemplates() {
114
+ return this.request("/api/templates");
115
+ }
116
+ async addTemplate(body) {
117
+ return this.request("/api/templates", { method: "POST", body: JSON.stringify(body) });
118
+ }
119
+ async removeTemplate(id) {
120
+ await this.request(`/api/templates/${id}`, { method: "DELETE" });
121
+ }
122
+ async listGroups() {
123
+ return this.request("/api/groups");
124
+ }
125
+ async createGroup(body) {
126
+ return this.request("/api/groups", { method: "POST", body: JSON.stringify(body) });
127
+ }
128
+ async deleteGroup(id) {
129
+ await this.request(`/api/groups/${id}`, { method: "DELETE" });
130
+ }
131
+ async listGroupMembers(id) {
132
+ return this.request(`/api/groups/${id}/members`);
133
+ }
134
+ async addGroupMember(id, body) {
135
+ await this.request(`/api/groups/${id}/members`, { method: "POST", body: JSON.stringify(body) });
136
+ }
137
+ async removeGroupMember(id, email) {
138
+ await this.request(`/api/groups/${id}/members/${encodeURIComponent(email)}`, { method: "DELETE" });
139
+ }
140
+ async listScheduled(status) {
141
+ return this.request(`/api/scheduled${qs({ status })}`);
142
+ }
143
+ async cancelScheduled(id) {
144
+ await this.request(`/api/scheduled/${id}`, { method: "DELETE" });
145
+ }
146
+ async listSequences() {
147
+ return this.request("/api/sequences");
148
+ }
149
+ async createSequence(body) {
150
+ return this.request("/api/sequences", { method: "POST", body: JSON.stringify(body) });
151
+ }
152
+ async deleteSequence(id) {
153
+ await this.request(`/api/sequences/${id}`, { method: "DELETE" });
154
+ }
155
+ async listSequenceSteps(id) {
156
+ return this.request(`/api/sequences/${id}/steps`);
157
+ }
158
+ async addSequenceStep(id, body) {
159
+ return this.request(`/api/sequences/${id}/steps`, { method: "POST", body: JSON.stringify(body) });
160
+ }
161
+ async listEnrollments(id) {
162
+ return this.request(`/api/sequences/${id}/enrollments`);
163
+ }
164
+ async enrollContact(id, body) {
165
+ return this.request(`/api/sequences/${id}/enroll`, { method: "POST", body: JSON.stringify(body) });
166
+ }
167
+ async unenrollContact(id, email) {
168
+ await this.request(`/api/sequences/${id}/enrollments/${encodeURIComponent(email)}`, {
169
+ method: "DELETE"
170
+ });
171
+ }
172
+ async listSandboxEmails(params) {
173
+ return this.request(`/api/sandbox${qs(params)}`);
174
+ }
175
+ async getSandboxEmail(id) {
176
+ return this.request(`/api/sandbox/${id}`);
177
+ }
178
+ async clearSandboxEmails(providerId) {
179
+ return this.request(`/api/sandbox${qs({ provider_id: providerId })}`, { method: "DELETE" });
180
+ }
181
+ async listInboundEmails(params) {
182
+ return this.request(`/api/inbound${qs(params)}`);
183
+ }
184
+ async getInboundEmail(id) {
185
+ return this.request(`/api/inbound/${id}`);
186
+ }
187
+ async clearInboundEmails(providerId) {
188
+ await this.request(`/api/inbound${qs({ provider_id: providerId })}`, { method: "DELETE" });
189
+ }
190
+ async listWarmingSchedules() {
191
+ return this.request("/api/warming");
192
+ }
193
+ async createWarmingSchedule(body) {
194
+ return this.request("/api/warming", { method: "POST", body: JSON.stringify(body) });
195
+ }
196
+ async getWarmingStatus(domain) {
197
+ return this.request(`/api/warming/${domain}`);
198
+ }
199
+ async updateWarmingStatus(domain, status) {
200
+ return this.request(`/api/warming/${domain}`, { method: "PUT", body: JSON.stringify({ status }) });
201
+ }
202
+ async deleteWarmingSchedule(domain) {
203
+ await this.request(`/api/warming/${domain}`, { method: "DELETE" });
204
+ }
205
+ async exportEmails(format, params) {
206
+ const res = await fetch(this.baseUrl + `/api/export/emails${qs({ format: format || "json", ...params })}`);
207
+ await checkResponse(res);
208
+ return res.text();
209
+ }
210
+ async exportEvents(format, params) {
211
+ const res = await fetch(this.baseUrl + `/api/export/events${qs({ format: format || "json", ...params })}`);
212
+ await checkResponse(res);
213
+ return res.text();
214
+ }
215
+ async runDoctor() {
216
+ return this.request("/api/doctor");
217
+ }
218
+ }
219
+ export {
220
+ EmailsClient
221
+ };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@hasna/emails-sdk",
3
+ "version": "0.4.26",
4
+ "description": "Zero-dependency TypeScript client for @hasna/emails REST API",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist"],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "license": "Apache-2.0",
19
+ "author": "Andrei Hasna <andrei@hasna.com>",
20
+ "scripts": {
21
+ "build": "bun build src/index.ts --outdir dist --target browser && tsc --emitDeclarationOnly --outDir dist",
22
+ "test": "bun test"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.7.3"
26
+ }
27
+ }