resend 1.1.0 → 2.0.0-canary.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resend",
3
- "version": "1.1.0",
3
+ "version": "2.0.0-canary.0",
4
4
  "description": "Node.js library for the Resend API",
5
5
  "main": "build/src/index.js",
6
6
  "typings": "build/src/index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "engines": {
11
11
  "node": ">=18"
12
12
  },
13
- "packageManager": "yarn@3.6.3",
13
+ "packageManager": "yarn@3.6.4",
14
14
  "scripts": {
15
15
  "build": "tsc -p .",
16
16
  "test": "jest",
@@ -31,23 +31,20 @@
31
31
  },
32
32
  "homepage": "https://github.com/resendlabs/resend-node#readme",
33
33
  "dependencies": {
34
- "@react-email/render": "0.0.7",
35
- "type-fest": "3.13.0"
34
+ "@react-email/render": "0.0.8"
36
35
  },
37
36
  "devDependencies": {
38
- "@types/jest": "29.5.3",
39
- "@types/node": "18.17.5",
40
- "@types/react": "18.2.20",
41
- "@typescript-eslint/eslint-plugin": "6.4.0",
42
- "@typescript-eslint/parser": "6.4.0",
43
- "eslint": "8.47.0",
44
- "fetch-mock": "9.11.0",
45
- "jest": "29.6.2",
37
+ "@types/jest": "29.5.6",
38
+ "@types/node": "18.18.7",
39
+ "@types/react": "18.2.33",
40
+ "@typescript-eslint/eslint-plugin": "6.9.0",
41
+ "@typescript-eslint/parser": "6.9.0",
42
+ "eslint": "8.52.0",
43
+ "jest": "29.7.0",
46
44
  "jest-fetch-mock": "3.0.3",
47
- "prettier": "3.0.2",
48
- "prettier-2": "npm:prettier@2",
49
- "supertest": "6.3.3",
45
+ "prettier": "3.0.3",
50
46
  "ts-jest": "29.1.1",
51
- "typescript": "5.1.6"
47
+ "ts-node": "10.9.1",
48
+ "typescript": "5.2.2"
52
49
  }
53
50
  }
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`Resend throws API key error 1`] = `"Missing API key. Pass it to the constructor \`new Resend("re_123")\`"`;
@@ -3,7 +3,7 @@ import { CreateApiKeyOptions, CreateApiKeyRequestOptions, CreateApiKeyResponse,
3
3
  export declare class ApiKeys {
4
4
  private readonly resend;
5
5
  constructor(resend: Resend);
6
- create(payload: CreateApiKeyOptions, options?: CreateApiKeyRequestOptions): Promise<CreateApiKeyResponse>;
7
- list(): Promise<ListApiKeysResponse>;
6
+ create(payload: CreateApiKeyOptions, options?: CreateApiKeyRequestOptions): Promise<import("..").ErrorResponse | CreateApiKeyResponse>;
7
+ list(): Promise<import("..").ErrorResponse | ListApiKeysResponse>;
8
8
  remove(id: string): Promise<void>;
9
9
  }
@@ -36,11 +36,14 @@ describe('API Keys', () => {
36
36
  `);
37
37
  }));
38
38
  it('throws error when missing name', () => __awaiter(void 0, void 0, void 0, function* () {
39
- fetchMock.mockOnce(JSON.stringify({
40
- statusCode: 422,
41
- name: 'missing_required_field',
42
- message: 'Missing "name" field',
43
- }), {
39
+ const errorResponse = {
40
+ error: {
41
+ statusCode: 422,
42
+ name: 'missing_required_field',
43
+ message: 'Missing "name" field',
44
+ },
45
+ };
46
+ fetchMock.mockOnce(JSON.stringify(errorResponse), {
44
47
  status: 422,
45
48
  headers: {
46
49
  'content-type': 'application/json',
@@ -48,12 +51,14 @@ describe('API Keys', () => {
48
51
  },
49
52
  });
50
53
  const resend = new resend_1.Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
51
- yield expect(resend.apiKeys.create({ name: '' })).resolves
52
- .toMatchInlineSnapshot(`
54
+ const result = resend.apiKeys.create({ name: '' });
55
+ yield expect(result).resolves.toMatchInlineSnapshot(`
53
56
  {
54
- "message": "Missing "name" field",
55
- "name": "missing_required_field",
56
- "statusCode": 422,
57
+ "error": {
58
+ "message": "Missing "name" field",
59
+ "name": "missing_required_field",
60
+ "statusCode": 422,
61
+ },
57
62
  }
58
63
  `);
59
64
  }));
@@ -100,11 +105,14 @@ describe('API Keys', () => {
100
105
  `);
101
106
  }));
102
107
  it('throws error with wrong access', () => __awaiter(void 0, void 0, void 0, function* () {
103
- fetchMock.mockOnce(JSON.stringify({
104
- statusCode: 422,
105
- name: 'invalid_access',
106
- message: 'Access must be "full_access" | "sending_access"',
107
- }), {
108
+ const errorResponse = {
109
+ error: {
110
+ statusCode: 422,
111
+ name: 'invalid_access',
112
+ message: 'Access must be "full_access" | "sending_access"',
113
+ },
114
+ };
115
+ fetchMock.mockOnce(JSON.stringify(errorResponse), {
108
116
  status: 422,
109
117
  headers: {
110
118
  'content-type': 'application/json',
@@ -115,7 +123,15 @@ describe('API Keys', () => {
115
123
  yield expect(resend.apiKeys.create({
116
124
  name: 'Test',
117
125
  permission: 'wrong_access',
118
- })).resolves.toThrowErrorMatchingInlineSnapshot('"Access must be "full_access" | "sending_access""');
126
+ })).resolves.toMatchInlineSnapshot(`
127
+ {
128
+ "error": {
129
+ "message": "Access must be "full_access" | "sending_access"",
130
+ "name": "invalid_access",
131
+ "statusCode": 422,
132
+ },
133
+ }
134
+ `);
119
135
  }));
120
136
  });
121
137
  describe('restricted by domain', () => {
@@ -143,11 +159,14 @@ describe('API Keys', () => {
143
159
  `);
144
160
  }));
145
161
  it('throws error with wrong access', () => __awaiter(void 0, void 0, void 0, function* () {
146
- fetchMock.mockOnce(JSON.stringify({
147
- name: 'application_error',
148
- message: 'Something went wrong',
149
- statusCode: 500,
150
- }), {
162
+ const errorResponse = {
163
+ error: {
164
+ name: 'application_error',
165
+ message: 'Something went wrong',
166
+ statusCode: 500,
167
+ },
168
+ };
169
+ fetchMock.mockOnce(JSON.stringify(errorResponse), {
151
170
  status: 500,
152
171
  headers: {
153
172
  'content-type': 'application/json',
@@ -159,7 +178,15 @@ describe('API Keys', () => {
159
178
  name: 'Test',
160
179
  permission: 'sending_access',
161
180
  domain_id: '1234',
162
- })).resolves.toThrowErrorMatchingInlineSnapshot('"Something went wrong"');
181
+ })).resolves.toMatchInlineSnapshot(`
182
+ {
183
+ "error": {
184
+ "message": "Something went wrong",
185
+ "name": "application_error",
186
+ "statusCode": 500,
187
+ },
188
+ }
189
+ `);
163
190
  }));
164
191
  });
165
192
  });
@@ -217,11 +244,14 @@ describe('API Keys', () => {
217
244
  yield expect(resend.apiKeys.remove('5262504e-8ed7-4fac-bd16-0d4be94bc9f2')).resolves.toMatchInlineSnapshot('undefined');
218
245
  }));
219
246
  it('throws error when missing id', () => __awaiter(void 0, void 0, void 0, function* () {
220
- fetchMock.mockOnce(JSON.stringify({
221
- name: 'application_error',
222
- message: 'Something went wrong',
223
- statusCode: 500,
224
- }), {
247
+ const errorResponse = {
248
+ error: {
249
+ name: 'application_error',
250
+ message: 'Something went wrong',
251
+ statusCode: 500,
252
+ },
253
+ };
254
+ fetchMock.mockOnce(JSON.stringify(errorResponse), {
225
255
  status: 500,
226
256
  headers: {
227
257
  'content-type': 'application/json',
@@ -229,14 +259,18 @@ describe('API Keys', () => {
229
259
  },
230
260
  });
231
261
  const resend = new resend_1.Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
232
- yield expect(resend.apiKeys.remove('')).resolves.toMatchInlineSnapshot('undefined');
262
+ const result = resend.apiKeys.remove('');
263
+ yield expect(result).resolves.toMatchInlineSnapshot('undefined');
233
264
  }));
234
265
  it('throws error when wrong id', () => __awaiter(void 0, void 0, void 0, function* () {
235
- fetchMock.mockOnce(JSON.stringify({
236
- name: 'not_found',
237
- message: 'API key not found',
238
- statusCode: 404,
239
- }), {
266
+ const errorResponse = {
267
+ error: {
268
+ name: 'not_found',
269
+ message: 'API key not found',
270
+ statusCode: 404,
271
+ },
272
+ };
273
+ fetchMock.mockOnce(JSON.stringify(errorResponse), {
240
274
  status: 404,
241
275
  headers: {
242
276
  'content-type': 'application/json',
@@ -244,7 +278,8 @@ describe('API Keys', () => {
244
278
  },
245
279
  });
246
280
  const resend = new resend_1.Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
247
- yield expect(resend.apiKeys.remove('34bd250e-615a-400c-be11-5912572ee15b')).resolves.toMatchInlineSnapshot('undefined');
281
+ const result = resend.apiKeys.remove('34bd250e-615a-400c-be11-5912572ee15b');
282
+ yield expect(result).resolves.toMatchInlineSnapshot('undefined');
248
283
  }));
249
284
  });
250
285
  });
@@ -0,0 +1,8 @@
1
+ import { Resend } from '../resend';
2
+ import { CreateBatchOptions, CreateBatchRequestOptions, CreateBatchResponse } from './interfaces';
3
+ export declare class Batch {
4
+ private readonly resend;
5
+ constructor(resend: Resend);
6
+ send(payload: CreateBatchOptions, options?: CreateBatchRequestOptions): Promise<CreateBatchResponse>;
7
+ create(payload: CreateBatchOptions, options?: CreateBatchRequestOptions): Promise<CreateBatchResponse>;
8
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Batch = void 0;
13
+ const render_1 = require("@react-email/render");
14
+ class Batch {
15
+ constructor(resend) {
16
+ this.resend = resend;
17
+ }
18
+ send(payload, options = {}) {
19
+ return __awaiter(this, void 0, void 0, function* () {
20
+ return this.create(payload, options);
21
+ });
22
+ }
23
+ create(payload, options = {}) {
24
+ return __awaiter(this, void 0, void 0, function* () {
25
+ for (const email of payload) {
26
+ if (email.react) {
27
+ email.html = (0, render_1.render)(email.react);
28
+ delete email.react;
29
+ }
30
+ }
31
+ const data = yield this.resend.post('/emails/batch', payload, options);
32
+ return data;
33
+ });
34
+ }
35
+ }
36
+ exports.Batch = Batch;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const jest_fetch_mock_1 = require("jest-fetch-mock");
13
+ const resend_1 = require("../resend");
14
+ (0, jest_fetch_mock_1.enableFetchMocks)();
15
+ const resend = new resend_1.Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
16
+ describe('Batch', () => {
17
+ afterEach(() => fetchMock.resetMocks());
18
+ describe('create', () => {
19
+ it('sends multiple emails', () => __awaiter(void 0, void 0, void 0, function* () {
20
+ const payload = [
21
+ {
22
+ from: 'bu@resend.com',
23
+ to: 'zeno@resend.com',
24
+ subject: 'Hello World',
25
+ html: '<h1>Hello world</h1>',
26
+ },
27
+ {
28
+ from: 'vitor@resend.com',
29
+ to: 'zeno@resend.com',
30
+ subject: 'Olá mundo',
31
+ html: '<h1>olá mundo</h1>',
32
+ },
33
+ {
34
+ from: 'bu@resend.com',
35
+ to: 'vitor@resend.com',
36
+ subject: 'Hi there',
37
+ html: '<h1>Hi there</h1>',
38
+ },
39
+ ];
40
+ fetchMock.mockOnce(JSON.stringify({
41
+ data: [{ id: '1234' }, { id: '4567' }, { id: '4242' }],
42
+ }), {
43
+ status: 200,
44
+ headers: {
45
+ 'content-type': 'application/json',
46
+ Authorization: 'Bearer re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop',
47
+ },
48
+ });
49
+ const data = yield resend.batch.create(payload);
50
+ expect(data).toMatchObject({
51
+ data: [{ id: '1234' }, { id: '4567' }, { id: '4242' }],
52
+ });
53
+ }));
54
+ });
55
+ describe('send', () => {
56
+ it('sends multiple emails', () => __awaiter(void 0, void 0, void 0, function* () {
57
+ const payload = [
58
+ {
59
+ from: 'bu@resend.com',
60
+ to: 'zeno@resend.com',
61
+ subject: 'Hello World',
62
+ html: '<h1>Hello world</h1>',
63
+ },
64
+ {
65
+ from: 'vitor@resend.com',
66
+ to: 'zeno@resend.com',
67
+ subject: 'Olá mundo',
68
+ html: '<h1>olá mundo</h1>',
69
+ },
70
+ {
71
+ from: 'bu@resend.com',
72
+ to: 'vitor@resend.com',
73
+ subject: 'Hi there',
74
+ html: '<h1>Hi there</h1>',
75
+ },
76
+ ];
77
+ fetchMock.mockOnce(JSON.stringify({
78
+ data: [{ id: '1234' }, { id: '4567' }, { id: '4242' }],
79
+ }), {
80
+ status: 200,
81
+ headers: {
82
+ 'content-type': 'application/json',
83
+ Authorization: 'Bearer re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop',
84
+ },
85
+ });
86
+ const data = yield resend.batch.send(payload);
87
+ expect(data).toMatchObject({
88
+ data: [{ id: '1234' }, { id: '4567' }, { id: '4242' }],
89
+ });
90
+ }));
91
+ });
92
+ });
@@ -0,0 +1,14 @@
1
+ import { PostOptions } from '../../common/interfaces';
2
+ import { CreateEmailOptions } from '../../emails/interfaces/create-email-options.interface';
3
+ import { ErrorResponse } from '../../interfaces';
4
+ export type CreateBatchOptions = CreateEmailOptions[];
5
+ export interface CreateBatchRequestOptions extends PostOptions {
6
+ }
7
+ interface CreateBatchSuccessResponse {
8
+ data: {
9
+ /** The ID of the newly created email. */
10
+ id: string;
11
+ }[];
12
+ }
13
+ export type CreateBatchResponse = CreateBatchSuccessResponse | ErrorResponse;
14
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export * from './create-batch-options.interface';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./create-batch-options.interface"), exports);
@@ -4,7 +4,7 @@ export declare class Domains {
4
4
  private readonly resend;
5
5
  constructor(resend: Resend);
6
6
  create(payload: CreateDomainOptions, options?: CreateDomainRequestOptions): Promise<CreateDomainResponse>;
7
- list(): Promise<ListDomainsResponse>;
7
+ list(): Promise<import("..").ErrorResponse | ListDomainsResponse>;
8
8
  get(id: string): Promise<GetDomainResponse>;
9
9
  remove(id: string): Promise<void>;
10
10
  verify(id: string): Promise<void>;
@@ -128,11 +128,14 @@ describe('Domains', () => {
128
128
  `);
129
129
  }));
130
130
  it('throws error when missing name', () => __awaiter(void 0, void 0, void 0, function* () {
131
- fetchMock.mockOnce(JSON.stringify({
132
- statusCode: 422,
133
- name: 'missing_required_field',
134
- message: 'Missing "name" field',
135
- }), {
131
+ const errorResponse = {
132
+ error: {
133
+ statusCode: 422,
134
+ name: 'missing_required_field',
135
+ message: 'Missing "name" field',
136
+ },
137
+ };
138
+ fetchMock.mockOnce(JSON.stringify(errorResponse), {
136
139
  status: 422,
137
140
  headers: {
138
141
  'content-type': 'application/json',
@@ -140,14 +143,16 @@ describe('Domains', () => {
140
143
  },
141
144
  });
142
145
  const resend = new resend_1.Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
143
- yield expect(resend.domains.create({ name: '' })).resolves
144
- .toMatchInlineSnapshot(`
145
- {
146
+ const result = resend.domains.create({ name: '' });
147
+ yield expect(result).resolves.toMatchInlineSnapshot(`
148
+ {
149
+ "error": {
146
150
  "message": "Missing "name" field",
147
151
  "name": "missing_required_field",
148
152
  "statusCode": 422,
149
- }
150
- `);
153
+ },
154
+ }
155
+ `);
151
156
  }));
152
157
  describe('with region', () => {
153
158
  it('creates a domain with region', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -256,11 +261,14 @@ describe('Domains', () => {
256
261
  `);
257
262
  }));
258
263
  it('throws error with wrong region', () => __awaiter(void 0, void 0, void 0, function* () {
259
- fetchMock.mockOnce(JSON.stringify({
260
- statusCode: 422,
261
- name: 'invalid_region',
262
- message: 'Region must be "us-east-1" | "eu-west-1" | "sa-east-1"',
263
- }), {
264
+ const errorResponse = {
265
+ error: {
266
+ statusCode: 422,
267
+ name: 'invalid_region',
268
+ message: 'Region must be "us-east-1" | "eu-west-1" | "sa-east-1"',
269
+ },
270
+ };
271
+ fetchMock.mockOnce(JSON.stringify(errorResponse), {
264
272
  status: 422,
265
273
  headers: {
266
274
  'content-type': 'application/json',
@@ -268,14 +276,17 @@ describe('Domains', () => {
268
276
  },
269
277
  });
270
278
  const resend = new resend_1.Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
271
- yield expect(resend.domains.create({
279
+ const result = resend.domains.create({
272
280
  name: 'resend.com',
273
281
  region: 'remote',
274
- })).resolves.toMatchInlineSnapshot(`
282
+ });
283
+ yield expect(result).resolves.toMatchInlineSnapshot(`
275
284
  {
276
- "message": "Region must be "us-east-1" | "eu-west-1" | "sa-east-1"",
277
- "name": "invalid_region",
278
- "statusCode": 422,
285
+ "error": {
286
+ "message": "Region must be "us-east-1" | "eu-west-1" | "sa-east-1"",
287
+ "name": "invalid_region",
288
+ "statusCode": 422,
289
+ },
279
290
  }
280
291
  `);
281
292
  }));
@@ -333,11 +344,14 @@ describe('Domains', () => {
333
344
  describe('get', () => {
334
345
  describe('when domain not found', () => {
335
346
  it('returns error', () => __awaiter(void 0, void 0, void 0, function* () {
336
- fetchMock.mockOnce(JSON.stringify({
337
- name: 'not_found',
338
- message: 'Domain not found',
339
- statusCode: 404,
340
- }), {
347
+ const errorResponse = {
348
+ error: {
349
+ name: 'not_found',
350
+ message: 'Domain not found',
351
+ statusCode: 404,
352
+ },
353
+ };
354
+ fetchMock.mockOnce(JSON.stringify(errorResponse), {
341
355
  status: 404,
342
356
  headers: {
343
357
  'content-type': 'application/json',
@@ -345,12 +359,14 @@ describe('Domains', () => {
345
359
  },
346
360
  });
347
361
  const resend = new resend_1.Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop');
348
- yield expect(resend.domains.get('1234')).resolves
349
- .toMatchInlineSnapshot(`
362
+ const result = resend.domains.get('1234');
363
+ yield expect(result).resolves.toMatchInlineSnapshot(`
350
364
  {
351
- "message": "Domain not found",
352
- "name": "not_found",
353
- "statusCode": 404,
365
+ "error": {
366
+ "message": "Domain not found",
367
+ "name": "not_found",
368
+ "statusCode": 404,
369
+ },
354
370
  }
355
371
  `);
356
372
  }));
@@ -1,4 +1,5 @@
1
1
  import { PostOptions } from '../../common/interfaces';
2
+ import { ErrorResponse } from '../../interfaces';
2
3
  import { DomainRegion, DomainStatus } from './domain';
3
4
  export interface CreateDomainOptions {
4
5
  name: string;
@@ -28,11 +29,13 @@ export interface DomainDkimRecord {
28
29
  priority?: number;
29
30
  proxy_status?: 'enable' | 'disable';
30
31
  }
31
- export type CreateDomainResponse = {
32
+ interface CreateDomainResponseSuccess {
32
33
  name: string;
33
34
  id: string;
34
35
  status: DomainStatus;
35
36
  created_at: string;
36
37
  records: (DomainSpfRecord | DomainDkimRecord)[];
37
38
  region: DomainRegion;
38
- };
39
+ }
40
+ export type CreateDomainResponse = CreateDomainResponseSuccess | ErrorResponse;
41
+ export {};
@@ -1,5 +1,6 @@
1
+ import { ErrorResponse } from '../../interfaces';
1
2
  import { DomainRecords, DomainRegion, DomainStatus } from './domain';
2
- export type GetDomainResponse = {
3
+ interface GetDomainResponseSuccess {
3
4
  object: 'domain';
4
5
  id: string;
5
6
  name: string;
@@ -7,4 +8,6 @@ export type GetDomainResponse = {
7
8
  region: DomainRegion;
8
9
  status: DomainStatus;
9
10
  records: DomainRecords[];
10
- };
11
+ }
12
+ export type GetDomainResponse = GetDomainResponseSuccess | ErrorResponse;
13
+ export {};
@@ -5,5 +5,5 @@ export declare class Emails {
5
5
  constructor(resend: Resend);
6
6
  send(payload: CreateEmailOptions, options?: CreateEmailRequestOptions): Promise<CreateEmailResponse>;
7
7
  create(payload: CreateEmailOptions, options?: CreateEmailRequestOptions): Promise<CreateEmailResponse>;
8
- get(id: string): Promise<GetEmailResponse>;
8
+ get(id: string): Promise<import("..").ErrorResponse | GetEmailResponse>;
9
9
  }
@@ -68,22 +68,28 @@ describe('Emails', () => {
68
68
  describe('get', () => {
69
69
  describe('when email not found', () => {
70
70
  it('returns error', () => __awaiter(void 0, void 0, void 0, function* () {
71
- fetchMock.mockOnce(JSON.stringify({
72
- name: 'not_found',
73
- message: 'Email not found',
74
- statusCode: 404,
75
- }), {
71
+ const errorResponse = {
72
+ error: {
73
+ name: 'not_found',
74
+ message: 'Email not found',
75
+ statusCode: 404,
76
+ },
77
+ };
78
+ fetchMock.mockOnce(JSON.stringify(errorResponse), {
76
79
  status: 404,
77
80
  headers: {
78
81
  'content-type': 'application/json',
79
82
  Authorization: 'Bearer re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop',
80
83
  },
81
84
  });
82
- yield expect(resend.emails.get('1234')).resolves.toMatchInlineSnapshot(`
85
+ const result = resend.emails.get('1234');
86
+ yield expect(result).resolves.toMatchInlineSnapshot(`
83
87
  {
84
- "message": "Email not found",
85
- "name": "not_found",
86
- "statusCode": 404,
88
+ "error": {
89
+ "message": "Email not found",
90
+ "name": "not_found",
91
+ "statusCode": 404,
92
+ },
87
93
  }
88
94
  `);
89
95
  }));
@@ -1,8 +1,31 @@
1
1
  /// <reference types="node" />
2
2
  import * as React from 'react';
3
3
  import { PostOptions } from '../../common/interfaces';
4
- import { RequireAtLeastOne } from 'type-fest';
5
- interface CreateEmailBaseOptions {
4
+ import { ErrorResponse } from '../../interfaces';
5
+ type RequireAtLeastOne<T> = {
6
+ [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>;
7
+ }[keyof T];
8
+ interface EmailRenderOptions {
9
+ /**
10
+ * The React component used to write the message.
11
+ *
12
+ * @link https://resend.com/api-reference/emails/send-email#body-parameters
13
+ */
14
+ react?: React.ReactElement | React.ReactNode | null;
15
+ /**
16
+ * The HTML version of the message.
17
+ *
18
+ * @link https://resend.com/api-reference/emails/send-email#body-parameters
19
+ */
20
+ html?: string;
21
+ /**
22
+ * The plain text version of the message.
23
+ *
24
+ * @link https://resend.com/api-reference/emails/send-email#body-parameters
25
+ */
26
+ text?: string;
27
+ }
28
+ interface CreateEmailBaseOptions extends EmailRenderOptions {
6
29
  /**
7
30
  * Filename and content of attachments (max 40mb per email)
8
31
  *
@@ -33,24 +56,6 @@ interface CreateEmailBaseOptions {
33
56
  * @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
34
57
  */
35
58
  headers?: Record<string, string>;
36
- /**
37
- * The React component used to write the message.
38
- *
39
- * @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
40
- */
41
- react?: React.ReactElement | React.ReactNode | null;
42
- /**
43
- * The HTML version of the message.
44
- *
45
- * @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
46
- */
47
- html?: string;
48
- /**
49
- * The plain text version of the message.
50
- *
51
- * @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
52
- */
53
- text?: string;
54
59
  /**
55
60
  * Reply-to email address. For multiple addresses, send as an array of strings.
56
61
  *
@@ -76,13 +81,14 @@ interface CreateEmailBaseOptions {
76
81
  */
77
82
  to: string | string[];
78
83
  }
79
- export type CreateEmailOptions = RequireAtLeastOne<CreateEmailBaseOptions, 'react' | 'html' | 'text'>;
84
+ export type CreateEmailOptions = RequireAtLeastOne<EmailRenderOptions> & CreateEmailBaseOptions;
80
85
  export interface CreateEmailRequestOptions extends PostOptions {
81
86
  }
82
- export interface CreateEmailResponse {
87
+ interface CreateEmailSuccessResponse {
83
88
  /** The ID of the newly created email. */
84
89
  id: string;
85
90
  }
91
+ export type CreateEmailResponse = CreateEmailSuccessResponse | ErrorResponse;
86
92
  interface Attachment {
87
93
  /** Content of an attached file. */
88
94
  content?: string | Buffer;
@@ -0,0 +1,8 @@
1
+ import { ErrorResponse, RESEND_ERROR_CODE_KEY, RESEND_ERROR_CODE_NUMBER } from './interfaces';
2
+ export declare class ResendError extends Error {
3
+ readonly statusCode: RESEND_ERROR_CODE_NUMBER | number;
4
+ readonly name: RESEND_ERROR_CODE_KEY;
5
+ constructor(message: string, name: RESEND_ERROR_CODE_KEY, statusCode: RESEND_ERROR_CODE_NUMBER | number);
6
+ static fromResponse(response: ErrorResponse): ResendError;
7
+ toString(): string;
8
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResendError = void 0;
4
+ class ResendError extends Error {
5
+ constructor(message, name, statusCode) {
6
+ super();
7
+ this.message = message;
8
+ this.name = name;
9
+ this.statusCode = statusCode;
10
+ }
11
+ static fromResponse(response) {
12
+ const { error } = response;
13
+ return new ResendError(error.message, error.name, error.statusCode);
14
+ }
15
+ toString() {
16
+ return JSON.stringify({
17
+ message: this.message,
18
+ name: this.name,
19
+ statusCode: this.statusCode,
20
+ }, null, 2);
21
+ }
22
+ }
23
+ exports.ResendError = ResendError;
@@ -0,0 +1,9 @@
1
+ import { ErrorResponse } from './interfaces';
2
+ export declare const isResendErrorResponse: (response: unknown) => response is ErrorResponse;
3
+ /**
4
+ * Consider whether to use this stricter version of the type guard.
5
+ *
6
+ * Right now, it's not used as there is a risk that an API error will not be
7
+ * caught due to the API being ahead of the current types.
8
+ */
9
+ export declare const isResendErrorResponseStrict: (response: unknown) => response is ErrorResponse;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isResendErrorResponseStrict = exports.isResendErrorResponse = void 0;
4
+ const interfaces_1 = require("./interfaces");
5
+ const isResendErrorResponse = (response) => {
6
+ if (typeof response !== 'object' || response === null) {
7
+ return false;
8
+ }
9
+ const { error } = response;
10
+ if (typeof error !== 'object' || error === null) {
11
+ return false;
12
+ }
13
+ const { message, statusCode, name } = error;
14
+ return (typeof message === 'string' &&
15
+ typeof statusCode === 'number' &&
16
+ typeof name === 'string');
17
+ };
18
+ exports.isResendErrorResponse = isResendErrorResponse;
19
+ /**
20
+ * Consider whether to use this stricter version of the type guard.
21
+ *
22
+ * Right now, it's not used as there is a risk that an API error will not be
23
+ * caught due to the API being ahead of the current types.
24
+ */
25
+ const isResendErrorResponseStrict = (response) => {
26
+ if (typeof response !== 'object' || response === null) {
27
+ return false;
28
+ }
29
+ const { error } = response;
30
+ if (typeof error !== 'object' || error === null) {
31
+ return false;
32
+ }
33
+ const { message, statusCode, name } = error;
34
+ return (typeof message === 'string' &&
35
+ typeof statusCode === 'number' &&
36
+ typeof name === 'string' &&
37
+ name in interfaces_1.RESEND_ERROR_CODES_BY_KEY);
38
+ };
39
+ exports.isResendErrorResponseStrict = isResendErrorResponseStrict;
@@ -1,25 +1,30 @@
1
1
  export declare const RESEND_ERROR_CODES_BY_KEY: {
2
- readonly missing_required_fields: 422;
2
+ readonly missing_required_field: 422;
3
+ readonly invalid_access: 422;
4
+ readonly invalid_parameter: 422;
5
+ readonly invalid_region: 422;
3
6
  readonly rate_limit_exceeded: 429;
4
7
  readonly missing_api_key: 401;
5
8
  readonly invalid_api_Key: 403;
6
9
  readonly invalid_from_address: 403;
10
+ readonly validation_error: 403;
7
11
  readonly not_found: 404;
8
12
  readonly method_not_allowed: 405;
13
+ readonly application_error: 500;
9
14
  readonly internal_server_error: 500;
10
15
  };
11
16
  type ValueOf<TObj> = TObj[keyof TObj];
12
17
  export type RESEND_ERROR_CODE_NUMBER = ValueOf<typeof RESEND_ERROR_CODES_BY_KEY>;
13
18
  export type RESEND_ERROR_CODE_KEY = keyof typeof RESEND_ERROR_CODES_BY_KEY;
14
- interface ErrorResponse {
19
+ export interface ErrorResponse {
15
20
  error: {
16
21
  message: string;
17
- status: RESEND_ERROR_CODE_NUMBER;
18
- type: RESEND_ERROR_CODE_KEY;
22
+ statusCode: RESEND_ERROR_CODE_NUMBER | number;
23
+ name: RESEND_ERROR_CODE_KEY;
19
24
  };
20
25
  }
21
- export { ErrorResponse };
22
26
  export type Tag = {
23
27
  name: string;
24
28
  value: string;
25
29
  };
30
+ export {};
@@ -2,12 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RESEND_ERROR_CODES_BY_KEY = void 0;
4
4
  exports.RESEND_ERROR_CODES_BY_KEY = {
5
- missing_required_fields: 422,
5
+ missing_required_field: 422,
6
+ invalid_access: 422,
7
+ invalid_parameter: 422,
8
+ invalid_region: 422,
6
9
  rate_limit_exceeded: 429,
7
10
  missing_api_key: 401,
8
11
  invalid_api_Key: 403,
9
12
  invalid_from_address: 403,
13
+ validation_error: 403,
10
14
  not_found: 404,
11
15
  method_not_allowed: 405,
16
+ application_error: 500,
12
17
  internal_server_error: 500,
13
18
  };
@@ -1,19 +1,22 @@
1
- import { GetOptions, PostOptions, PutOptions } from './common/interfaces';
2
1
  import { ApiKeys } from './api-keys/api-keys';
2
+ import { Batch } from './batch/batch';
3
+ import { GetOptions, PostOptions, PutOptions } from './common/interfaces';
3
4
  import { Domains } from './domains/domains';
4
5
  import { Emails } from './emails/emails';
5
6
  import { CreateEmailOptions, CreateEmailResponse } from './emails/interfaces';
7
+ import { ErrorResponse } from './interfaces';
6
8
  export declare class Resend {
7
9
  readonly key?: string | undefined;
8
10
  private readonly headers;
9
11
  readonly apiKeys: ApiKeys;
10
12
  readonly domains: Domains;
11
13
  readonly emails: Emails;
14
+ readonly batch: Batch;
12
15
  constructor(key?: string | undefined);
13
- fetchRequest(path: string, options?: {}): Promise<any>;
14
- post<T>(path: string, entity?: any, options?: PostOptions): Promise<T>;
15
- get<T>(path: string, options?: GetOptions): Promise<T>;
16
- put<T>(path: string, entity: any, options?: PutOptions): Promise<T>;
17
- delete<T>(path: string, query?: any): Promise<T>;
16
+ fetchRequest<T>(path: string, options?: {}): Promise<T | ErrorResponse>;
17
+ post<T>(path: string, entity?: any, options?: PostOptions): Promise<ErrorResponse | T>;
18
+ get<T>(path: string, options?: GetOptions): Promise<ErrorResponse | T>;
19
+ put<T>(path: string, entity: any, options?: PutOptions): Promise<ErrorResponse | T>;
20
+ delete<T>(path: string, query?: any): Promise<ErrorResponse | T>;
18
21
  sendEmail(data: CreateEmailOptions): Promise<CreateEmailResponse>;
19
22
  }
@@ -10,11 +10,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.Resend = void 0;
13
- const render_1 = require("@react-email/render");
14
13
  const package_json_1 = require("../package.json");
15
14
  const api_keys_1 = require("./api-keys/api-keys");
15
+ const batch_1 = require("./batch/batch");
16
16
  const domains_1 = require("./domains/domains");
17
17
  const emails_1 = require("./emails/emails");
18
+ const guards_1 = require("./guards");
18
19
  const baseUrl = process.env.RESEND_BASE_URL || 'https://api.resend.com';
19
20
  const userAgent = process.env.RESEND_USER_AGENT || `resend-node:${package_json_1.version}`;
20
21
  class Resend {
@@ -23,6 +24,7 @@ class Resend {
23
24
  this.apiKeys = new api_keys_1.ApiKeys(this);
24
25
  this.domains = new domains_1.Domains(this);
25
26
  this.emails = new emails_1.Emails(this);
27
+ this.batch = new batch_1.Batch(this);
26
28
  if (!key) {
27
29
  this.key = process.env.RESEND_API_KEY;
28
30
  if (!this.key) {
@@ -40,7 +42,12 @@ class Resend {
40
42
  const response = yield fetch(`${baseUrl}${path}`, options);
41
43
  if (!response.ok) {
42
44
  const error = yield response.json();
43
- return error;
45
+ if ((0, guards_1.isResendErrorResponse)(error)) {
46
+ return error;
47
+ }
48
+ return {
49
+ error,
50
+ };
44
51
  }
45
52
  return yield response.json();
46
53
  });
@@ -75,24 +82,7 @@ class Resend {
75
82
  }
76
83
  sendEmail(data) {
77
84
  return __awaiter(this, void 0, void 0, function* () {
78
- const path = '/email';
79
- if (data.react) {
80
- data.html = (0, render_1.render)(data.react);
81
- delete data.react;
82
- }
83
- const response = yield this.post(path, {
84
- from: data.from,
85
- to: data.to,
86
- bcc: data.bcc,
87
- cc: data.cc,
88
- reply_to: data.reply_to,
89
- subject: data.subject,
90
- text: data.text,
91
- html: data.html,
92
- attachments: data.attachments,
93
- tags: data.tags,
94
- });
95
- return response;
85
+ return this.emails.create(data);
96
86
  });
97
87
  }
98
88
  }
@@ -216,4 +216,37 @@ describe('Resend', () => {
216
216
  }
217
217
  `);
218
218
  }));
219
+ it('throws an error when an ErrorResponse is returned', () => __awaiter(void 0, void 0, void 0, function* () {
220
+ const payload = {
221
+ from: 'resend.com',
222
+ to: 'bu@resend.com',
223
+ reply_to: ['foo@resend.com', 'bar@resend.com'],
224
+ subject: 'Hello World',
225
+ text: 'Hello world',
226
+ };
227
+ const errorResponse = {
228
+ error: {
229
+ statusCode: 422,
230
+ name: 'invalid_parameter',
231
+ message: 'Invalid `from` field. The email address needs to follow the `email@example.com` or `Name <email@example.com>` format',
232
+ },
233
+ };
234
+ fetchMock.mockOnce(JSON.stringify(errorResponse), {
235
+ status: 422,
236
+ headers: {
237
+ 'content-type': 'application/json',
238
+ Authorization: 'Bearer re_924b3rjh2387fbewf823',
239
+ },
240
+ });
241
+ const result = resend.emails.send(payload);
242
+ yield expect(result).resolves.toMatchInlineSnapshot(`
243
+ {
244
+ "error": {
245
+ "message": "Invalid \`from\` field. The email address needs to follow the \`email@example.com\` or \`Name <email@example.com>\` format",
246
+ "name": "invalid_parameter",
247
+ "statusCode": 422,
248
+ },
249
+ }
250
+ `);
251
+ }));
219
252
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resend",
3
- "version": "1.1.0",
3
+ "version": "2.0.0-canary.0",
4
4
  "description": "Node.js library for the Resend API",
5
5
  "main": "build/src/index.js",
6
6
  "typings": "build/src/index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "engines": {
11
11
  "node": ">=18"
12
12
  },
13
- "packageManager": "yarn@3.6.3",
13
+ "packageManager": "yarn@3.6.4",
14
14
  "scripts": {
15
15
  "build": "tsc -p .",
16
16
  "test": "jest",
@@ -31,23 +31,20 @@
31
31
  },
32
32
  "homepage": "https://github.com/resendlabs/resend-node#readme",
33
33
  "dependencies": {
34
- "@react-email/render": "0.0.7",
35
- "type-fest": "3.13.0"
34
+ "@react-email/render": "0.0.8"
36
35
  },
37
36
  "devDependencies": {
38
- "@types/jest": "29.5.3",
39
- "@types/node": "18.17.5",
40
- "@types/react": "18.2.20",
41
- "@typescript-eslint/eslint-plugin": "6.4.0",
42
- "@typescript-eslint/parser": "6.4.0",
43
- "eslint": "8.47.0",
44
- "fetch-mock": "9.11.0",
45
- "jest": "29.6.2",
37
+ "@types/jest": "29.5.6",
38
+ "@types/node": "18.18.7",
39
+ "@types/react": "18.2.33",
40
+ "@typescript-eslint/eslint-plugin": "6.9.0",
41
+ "@typescript-eslint/parser": "6.9.0",
42
+ "eslint": "8.52.0",
43
+ "jest": "29.7.0",
46
44
  "jest-fetch-mock": "3.0.3",
47
- "prettier": "3.0.2",
48
- "prettier-2": "npm:prettier@2",
49
- "supertest": "6.3.3",
45
+ "prettier": "3.0.3",
50
46
  "ts-jest": "29.1.1",
51
- "typescript": "5.1.6"
47
+ "ts-node": "10.9.1",
48
+ "typescript": "5.2.2"
52
49
  }
53
50
  }