mailisk 2.0.0 → 2.1.1
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/.github/workflows/test.yml +31 -0
- package/README.md +64 -16
- package/dist/index.d.ts +60 -3
- package/dist/index.js +21 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +21 -6
- package/dist/index.mjs.map +1 -1
- package/jest.config.js +13 -0
- package/package.json +10 -2
- package/src/mailisk.interfaces.ts +32 -0
- package/src/mailisk.ts +37 -6
- package/tests/mocks/axios-mocks.ts +49 -0
- package/tests/setup.ts +21 -0
- package/tests/unit/mailisk.test.ts +344 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Build and Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test
|
|
12
|
+
needs: build
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v3
|
|
17
|
+
|
|
18
|
+
- name: Use Node.js 18.x
|
|
19
|
+
uses: actions/setup-node@v3
|
|
20
|
+
with:
|
|
21
|
+
node-version: 18.x
|
|
22
|
+
cache: "npm"
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: npm ci
|
|
26
|
+
|
|
27
|
+
- name: Build
|
|
28
|
+
run: npm run build
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: npm test
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ const { MailiskClient } = require("mailisk");
|
|
|
35
35
|
const mailisk = new MailiskClient({ apiKey: "YOUR_API_KEY" });
|
|
36
36
|
|
|
37
37
|
// send email (using virtual SMTP)
|
|
38
|
-
await
|
|
38
|
+
await mailisk.sendVirtualEmail(namespace, {
|
|
39
39
|
from: "test@example.com",
|
|
40
40
|
to: `john@${namespace}.mailisk.net`,
|
|
41
41
|
subject: "Testing",
|
|
@@ -43,7 +43,7 @@ await client.sendVirtualEmail(namespace, {
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
// receive email
|
|
46
|
-
const result = await
|
|
46
|
+
const result = await mailisk.searchInbox(namespace);
|
|
47
47
|
|
|
48
48
|
console.log(result);
|
|
49
49
|
```
|
|
@@ -54,35 +54,42 @@ This library wraps the REST API endpoints. Find out more in the [API Reference](
|
|
|
54
54
|
|
|
55
55
|
## Client functions
|
|
56
56
|
|
|
57
|
-
### searchInbox(namespace, params?)
|
|
57
|
+
### `searchInbox(namespace, params?)`
|
|
58
|
+
|
|
59
|
+
Use `searchInbox` to fetch messages that arrived in a given namespace, optionally waiting until the first new mail shows up.
|
|
60
|
+
|
|
61
|
+
For the full parameter options see the [endpoint reference](https://docs.mailisk.com/api-reference/search-inbox.html#request-1).
|
|
62
|
+
|
|
63
|
+
Default behaviour:
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
- Waits until at least one new email arrives (override with `wait: false`).
|
|
66
|
+
- Times out after 5 minutes if nothing shows up (adjust via `requestOptions.timeout`).
|
|
67
|
+
- Ignores messages older than 15 minutes to avoid picking up leftovers from previous tests (change via `from_timestamp`).
|
|
60
68
|
|
|
61
|
-
|
|
62
|
-
- The request timeout is adjustable by passing `timeout` in the request options. By default it uses a timeout of 5 minutes.
|
|
63
|
-
- By default `from_timestamp` is set to **current timestamp - 5 seconds**. This ensures that only new emails are returned. Without this, older emails would also be returned, potentially disrupting you if you were waiting for a specific email. This can be overriden by passing the `from_timestamp` parameter (`from_timestmap: 0` will disable filtering by email age).
|
|
69
|
+
#### Quick examples
|
|
64
70
|
|
|
65
71
|
```js
|
|
66
|
-
//
|
|
67
|
-
await mailisk.searchInbox(namespace);
|
|
68
|
-
|
|
72
|
+
// wait up to the default 5 min for *any* new mail
|
|
73
|
+
const { data: emails } = await mailisk.searchInbox(namespace);
|
|
74
|
+
|
|
75
|
+
// custom 60-second timeout
|
|
69
76
|
await mailisk.searchInbox(namespace, {}, { timeout: 1000 * 60 });
|
|
70
|
-
|
|
77
|
+
|
|
78
|
+
// polling pattern — return immediately, even if inbox is empty
|
|
71
79
|
await mailisk.searchInbox(namespace, { wait: false });
|
|
72
80
|
```
|
|
73
81
|
|
|
74
82
|
#### Filter by destination address
|
|
75
83
|
|
|
76
|
-
A common
|
|
84
|
+
A common pattern is to wait for the email your UI just triggered (e.g. password-reset).
|
|
85
|
+
Pass `to_addr_prefix` so you don’t pick up stale messages:
|
|
77
86
|
|
|
78
87
|
```js
|
|
79
88
|
const { data: emails } = await mailisk.searchInbox(namespace, {
|
|
80
|
-
to_addr_prefix:
|
|
89
|
+
to_addr_prefix: `john@${namespace}.mailisk.net`,
|
|
81
90
|
});
|
|
82
91
|
```
|
|
83
92
|
|
|
84
|
-
For more parameter options see the [endpoint reference](https://docs.mailisk.com/api-reference/search-inbox.html#request-1).
|
|
85
|
-
|
|
86
93
|
### sendVirtualEmail(namespace, params)
|
|
87
94
|
|
|
88
95
|
Send an email using [Virtual SMTP](https://docs.mailisk.com/smtp.html). This will fetch the SMTP settings for the selected namespace and send an email. These emails can only be sent to an address that ends in `@{namespace}.mailisk.net`.
|
|
@@ -100,7 +107,7 @@ await mailisk.sendVirtualEmail(namespace, {
|
|
|
100
107
|
|
|
101
108
|
This does not call an API endpoint but rather uses nodemailer to send an email using SMTP.
|
|
102
109
|
|
|
103
|
-
### listNamespaces()
|
|
110
|
+
### `listNamespaces()`
|
|
104
111
|
|
|
105
112
|
List all namespaces associated with the current API Key.
|
|
106
113
|
|
|
@@ -110,3 +117,44 @@ const namespacesResponse = await mailisk.listNamespaces();
|
|
|
110
117
|
// will be ['namespace1', 'namespace2']
|
|
111
118
|
const namespaces = namespacesResponse.map((nr) => nr.namespace);
|
|
112
119
|
```
|
|
120
|
+
|
|
121
|
+
### `getAttachment(attachmentId)`
|
|
122
|
+
|
|
123
|
+
Get information about an attachment.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const attachment = await mailisk.getAttachment(attachmentId);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `downloadAttachment(attachmentId)`
|
|
130
|
+
|
|
131
|
+
Retrieve the raw bytes of a file attached to an email message.
|
|
132
|
+
Typically you call this after `searchInbox` → iterate over `email.attachments[]` → pass the desired `attachment.id`.
|
|
133
|
+
|
|
134
|
+
#### Quick examples
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
import fs from "node:fs";
|
|
138
|
+
import path from "node:path";
|
|
139
|
+
|
|
140
|
+
// assume 'email' was fetched via searchInbox()
|
|
141
|
+
const { id, filename } = email.attachments[0];
|
|
142
|
+
|
|
143
|
+
// download the attachment
|
|
144
|
+
const buffer = await mailisk.downloadAttachment(id);
|
|
145
|
+
|
|
146
|
+
// save to disk (preserve original filename)
|
|
147
|
+
fs.writeFileSync(filename, buffer);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Streaming large files
|
|
151
|
+
|
|
152
|
+
downloadAttachment returns the entire file as a single Buffer.
|
|
153
|
+
If you expect very large attachments and want to avoid holding them fully in memory, use getAttachment(attachmentId).download_url and stream with fetch / axios instead:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
const meta = await mailisk.getAttachment(id);
|
|
157
|
+
const res = await fetch(meta.download_url);
|
|
158
|
+
const fileStream = fs.createWriteStream(filename);
|
|
159
|
+
await new Promise((ok, err) => res.body.pipe(fileStream).on("finish", ok).on("error", err));
|
|
160
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AxiosRequestConfig } from 'axios';
|
|
1
|
+
import { AxiosBasicCredentials, AxiosRequestConfig } from 'axios';
|
|
2
|
+
import { Attachment } from 'nodemailer/lib/mailer';
|
|
2
3
|
|
|
3
4
|
interface EmailAddress {
|
|
4
5
|
/** Email address */
|
|
@@ -6,6 +7,16 @@ interface EmailAddress {
|
|
|
6
7
|
/** Display name, if one is specified */
|
|
7
8
|
name?: string;
|
|
8
9
|
}
|
|
10
|
+
interface EmailAttachment {
|
|
11
|
+
/** Unique identifier for the attachment */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Filename of the attachment */
|
|
14
|
+
filename: string;
|
|
15
|
+
/** Content type of the attachment */
|
|
16
|
+
content_type: string;
|
|
17
|
+
/** Size in bytes of the attachment */
|
|
18
|
+
size: number;
|
|
19
|
+
}
|
|
9
20
|
interface Email {
|
|
10
21
|
/** Namespace scoped ID */
|
|
11
22
|
id: string;
|
|
@@ -31,6 +42,10 @@ interface Email {
|
|
|
31
42
|
expires_timestamp: number;
|
|
32
43
|
/** The spam score as reported by SpamAssassin */
|
|
33
44
|
spam_score?: number;
|
|
45
|
+
/** The headers of the email */
|
|
46
|
+
headers?: Record<string, string>;
|
|
47
|
+
/** The attachments of the email */
|
|
48
|
+
attachments?: EmailAttachment[];
|
|
34
49
|
}
|
|
35
50
|
interface SearchInboxParams {
|
|
36
51
|
/**
|
|
@@ -52,9 +67,21 @@ interface SearchInboxParams {
|
|
|
52
67
|
/**
|
|
53
68
|
* Filter emails by 'to' address. Address must start with this.
|
|
54
69
|
*
|
|
55
|
-
* 'foo' would return 'foobar@namespace.mailisk.net' but not 'barfoo@namespace.mailisk.net'
|
|
70
|
+
* 'foo' would return for 'foobar@namespace.mailisk.net' but not 'barfoo@namespace.mailisk.net'
|
|
56
71
|
*/
|
|
57
72
|
to_addr_prefix?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Filter emails by 'from' address. Address must include this.
|
|
75
|
+
*
|
|
76
|
+
* '@foo' would return for 'a@foo.com', 'b@foo.net'
|
|
77
|
+
*/
|
|
78
|
+
from_addr_includes?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Filter emails by subject. This is case insensitive. Subject must include this.
|
|
81
|
+
*
|
|
82
|
+
* 'password' would return for 'Password reset', 'Reset password notification' but not 'Reset'
|
|
83
|
+
*/
|
|
84
|
+
subject_includes?: string;
|
|
58
85
|
/**
|
|
59
86
|
* Will keep the request going till at least one email would be returned.
|
|
60
87
|
*
|
|
@@ -84,6 +111,16 @@ interface SmtpSettings {
|
|
|
84
111
|
password: string;
|
|
85
112
|
};
|
|
86
113
|
}
|
|
114
|
+
interface GetAttachmentResponse {
|
|
115
|
+
data: {
|
|
116
|
+
id: string;
|
|
117
|
+
filename: string;
|
|
118
|
+
content_type: string;
|
|
119
|
+
size: number;
|
|
120
|
+
expires_at: string | null;
|
|
121
|
+
download_url: string;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
87
124
|
interface ListNamespacesResponse {
|
|
88
125
|
data: [
|
|
89
126
|
{
|
|
@@ -107,12 +144,17 @@ interface SendVirtualEmailParams {
|
|
|
107
144
|
text?: string | undefined;
|
|
108
145
|
/** The HTML version of the message */
|
|
109
146
|
html?: string | undefined;
|
|
147
|
+
/** Custom headers for the email */
|
|
148
|
+
headers?: Record<string, string>;
|
|
149
|
+
/** Attachments to the email */
|
|
150
|
+
attachments?: Attachment[];
|
|
110
151
|
}
|
|
111
152
|
|
|
112
153
|
declare class MailiskClient {
|
|
113
|
-
constructor({ apiKey, baseUrl }: {
|
|
154
|
+
constructor({ apiKey, baseUrl, auth }: {
|
|
114
155
|
apiKey: string;
|
|
115
156
|
baseUrl?: string;
|
|
157
|
+
auth?: AxiosBasicCredentials;
|
|
116
158
|
});
|
|
117
159
|
private readonly axiosInstance;
|
|
118
160
|
/**
|
|
@@ -163,6 +205,21 @@ declare class MailiskClient {
|
|
|
163
205
|
* Get the SMTP settings for a namespace.
|
|
164
206
|
*/
|
|
165
207
|
getSmtpSettings(namespace: string): Promise<SmtpSettings>;
|
|
208
|
+
getAttachment(attachmentId: string): Promise<GetAttachmentResponse>;
|
|
209
|
+
/**
|
|
210
|
+
* Download an attachment from an attachment ID.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* Download an attachment from an email
|
|
214
|
+
* ```typescript
|
|
215
|
+
* const attachment = email.attachments[0];
|
|
216
|
+
* const attachmentBuffer = await client.downloadAttachment(attachment.id);
|
|
217
|
+
*
|
|
218
|
+
* // save to file
|
|
219
|
+
* fs.writeFileSync(attachment.filename, attachmentBuffer);
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
downloadAttachment(attachmentId: string): Promise<Buffer>;
|
|
166
223
|
}
|
|
167
224
|
|
|
168
225
|
export { MailiskClient };
|
package/dist/index.js
CHANGED
|
@@ -35,12 +35,13 @@ module.exports = __toCommonJS(src_exports);
|
|
|
35
35
|
var import_axios = __toESM(require("axios"));
|
|
36
36
|
var import_nodemailer = __toESM(require("nodemailer"));
|
|
37
37
|
var MailiskClient = class {
|
|
38
|
-
constructor({ apiKey, baseUrl }) {
|
|
38
|
+
constructor({ apiKey, baseUrl, auth }) {
|
|
39
39
|
this.axiosInstance = import_axios.default.create({
|
|
40
40
|
headers: {
|
|
41
41
|
"X-Api-Key": apiKey
|
|
42
42
|
},
|
|
43
|
-
baseURL: baseUrl || "https://api.mailisk.com/"
|
|
43
|
+
baseURL: baseUrl || "https://api.mailisk.com/",
|
|
44
|
+
auth
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
axiosInstance;
|
|
@@ -58,30 +59,35 @@ var MailiskClient = class {
|
|
|
58
59
|
pass: smtpSettings.data.password
|
|
59
60
|
}
|
|
60
61
|
});
|
|
61
|
-
const { from, to, subject, text, html } = params;
|
|
62
|
+
const { from, to, subject, text, html, headers, attachments } = params;
|
|
62
63
|
await transport.sendMail({
|
|
63
64
|
from,
|
|
64
65
|
to,
|
|
65
66
|
subject,
|
|
66
67
|
text,
|
|
67
|
-
html
|
|
68
|
+
html,
|
|
69
|
+
headers,
|
|
70
|
+
attachments
|
|
68
71
|
});
|
|
69
72
|
transport.close();
|
|
70
73
|
}
|
|
71
74
|
async searchInbox(namespace, params, config) {
|
|
72
75
|
let _params = { ...params };
|
|
73
76
|
if (params?.from_timestamp === void 0 || params?.from_timestamp === null) {
|
|
74
|
-
_params.from_timestamp = Math.floor(new Date().getTime() / 1e3) -
|
|
77
|
+
_params.from_timestamp = Math.floor(new Date().getTime() / 1e3) - 15 * 60;
|
|
75
78
|
}
|
|
76
79
|
if (params?.wait !== false) {
|
|
77
80
|
_params.wait = true;
|
|
78
81
|
}
|
|
79
82
|
let _config = { ...config };
|
|
83
|
+
if (!config?.maxRedirects) {
|
|
84
|
+
_config.maxRedirects = 99999;
|
|
85
|
+
}
|
|
80
86
|
if (_params.wait && !config?.timeout) {
|
|
81
87
|
_config.timeout = 1e3 * 60 * 5;
|
|
82
88
|
}
|
|
83
89
|
return (await this.axiosInstance.get(`api/emails/${namespace}/inbox`, {
|
|
84
|
-
...
|
|
90
|
+
..._config,
|
|
85
91
|
params: _params
|
|
86
92
|
})).data;
|
|
87
93
|
}
|
|
@@ -89,6 +95,15 @@ var MailiskClient = class {
|
|
|
89
95
|
const result = await this.axiosInstance.get(`api/smtp/${namespace}`);
|
|
90
96
|
return result.data;
|
|
91
97
|
}
|
|
98
|
+
async getAttachment(attachmentId) {
|
|
99
|
+
const result = await this.axiosInstance.get(`api/attachments/${attachmentId}`);
|
|
100
|
+
return result.data;
|
|
101
|
+
}
|
|
102
|
+
async downloadAttachment(attachmentId) {
|
|
103
|
+
const result = await this.getAttachment(attachmentId);
|
|
104
|
+
const response = await import_axios.default.get(result.data.download_url, { responseType: "arraybuffer" });
|
|
105
|
+
return Buffer.from(response.data);
|
|
106
|
+
}
|
|
92
107
|
};
|
|
93
108
|
__name(MailiskClient, "MailiskClient");
|
|
94
109
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/mailisk.ts"],"sourcesContent":["export * from \"./mailisk\";\n","import axios, { AxiosRequestConfig } from \"axios\";\nimport {\n ListNamespacesResponse,\n SearchInboxParams,\n SearchInboxResponse,\n SendVirtualEmailParams,\n SmtpSettings,\n} from \"./mailisk.interfaces\";\nimport nodemailer from \"nodemailer\";\n\nexport class MailiskClient {\n constructor({ apiKey, baseUrl }: { apiKey: string; baseUrl?: string }) {\n this.axiosInstance = axios.create({\n headers: {\n \"X-Api-Key\": apiKey,\n },\n baseURL: baseUrl || \"https://api.mailisk.com/\",\n });\n }\n\n private readonly axiosInstance;\n\n /**\n * List all namespaces that belong to the current account (API key).\n */\n async listNamespaces(): Promise<ListNamespacesResponse> {\n return (await this.axiosInstance.get(\"api/namespaces\")).data;\n }\n\n /**\n * Send an email using the Virtual SMTP.\n *\n * These emails can only be sent to valid Mailisk namespaces, i.e. emails that end in @mynamespace.mailisk.net\n *\n * @example\n * For example, sending a test email:\n * ```typescript\n * client.sendVirtualEmail(namespace, {\n * from: \"test@example.com\",\n * to: `john@${namespace}.mailisk.net`,\n * subject: \"This is a test\",\n * text: \"Testing\",\n * });\n * ```\n */\n async sendVirtualEmail(namespace: string, params: SendVirtualEmailParams): Promise<void> {\n const smtpSettings = await this.getSmtpSettings(namespace);\n\n
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/mailisk.ts"],"sourcesContent":["export * from \"./mailisk\";\n","import axios, { AxiosBasicCredentials, AxiosRequestConfig } from \"axios\";\nimport {\n GetAttachmentResponse,\n ListNamespacesResponse,\n SearchInboxParams,\n SearchInboxResponse,\n SendVirtualEmailParams,\n SmtpSettings,\n} from \"./mailisk.interfaces\";\nimport nodemailer from \"nodemailer\";\n\nexport class MailiskClient {\n constructor({ apiKey, baseUrl, auth }: { apiKey: string; baseUrl?: string; auth?: AxiosBasicCredentials }) {\n this.axiosInstance = axios.create({\n headers: {\n \"X-Api-Key\": apiKey,\n },\n baseURL: baseUrl || \"https://api.mailisk.com/\",\n auth,\n });\n }\n\n private readonly axiosInstance;\n\n /**\n * List all namespaces that belong to the current account (API key).\n */\n async listNamespaces(): Promise<ListNamespacesResponse> {\n return (await this.axiosInstance.get(\"api/namespaces\")).data;\n }\n\n /**\n * Send an email using the Virtual SMTP.\n *\n * These emails can only be sent to valid Mailisk namespaces, i.e. emails that end in @mynamespace.mailisk.net\n *\n * @example\n * For example, sending a test email:\n * ```typescript\n * client.sendVirtualEmail(namespace, {\n * from: \"test@example.com\",\n * to: `john@${namespace}.mailisk.net`,\n * subject: \"This is a test\",\n * text: \"Testing\",\n * });\n * ```\n */\n async sendVirtualEmail(namespace: string, params: SendVirtualEmailParams): Promise<void> {\n const smtpSettings = await this.getSmtpSettings(namespace);\n\n const transport = nodemailer.createTransport({\n host: smtpSettings.data.host,\n port: smtpSettings.data.port,\n secure: false,\n auth: {\n user: smtpSettings.data.username,\n pass: smtpSettings.data.password,\n },\n });\n\n const { from, to, subject, text, html, headers, attachments } = params;\n\n await transport.sendMail({\n from,\n to,\n subject,\n text,\n html,\n headers,\n attachments,\n });\n\n transport.close();\n }\n\n /**\n * Search inbox of a namespace.\n *\n * By default, this calls the api using the `wait` flag. This means the call won't timeout until at least one email is received or 5 minutes pass.\n * It also uses a default `from_timestamp` of **current timestamp - 5 seconds**. This means that older emails will be ignored.\n *\n * Both of these settings can be overriden by passing them in the `params` object.\n *\n * @example\n * Get the latest emails in the namespace\n * ```typescript\n * const { data: emails } = await client.searchInbox(namespace);\n * ```\n *\n * @example\n * Get the latest emails for a specific email address\n * ```typescript\n * const { data: emails } = await client.searchInbox(namespace, {\n * to_addr_prefix: 'john@mynamespace.mailisk.net'\n * });\n * ```\n */\n async searchInbox(\n namespace: string,\n params?: SearchInboxParams,\n config?: AxiosRequestConfig\n ): Promise<SearchInboxResponse> {\n let _params = { ...params };\n\n // default from timestamp, 15 minutes before starting this request\n if (params?.from_timestamp === undefined || params?.from_timestamp === null) {\n _params.from_timestamp = Math.floor(new Date().getTime() / 1000) - 15 * 60;\n }\n\n // by default wait for email\n if (params?.wait !== false) {\n _params.wait = true;\n }\n\n let _config = { ...config };\n\n if (!config?.maxRedirects) {\n _config.maxRedirects = 99999;\n }\n\n // by default, wait 5 minutes for emails before timing out\n if (_params.wait && !config?.timeout) {\n _config.timeout = 1000 * 60 * 5;\n }\n\n return (\n await this.axiosInstance.get(`api/emails/${namespace}/inbox`, {\n ..._config,\n params: _params,\n })\n ).data;\n }\n\n /**\n * Get the SMTP settings for a namespace.\n */\n async getSmtpSettings(namespace: string): Promise<SmtpSettings> {\n const result = await this.axiosInstance.get(`api/smtp/${namespace}`);\n return result.data;\n }\n\n async getAttachment(attachmentId: string): Promise<GetAttachmentResponse> {\n const result = await this.axiosInstance.get(`api/attachments/${attachmentId}`);\n return result.data;\n }\n\n /**\n * Download an attachment from an attachment ID.\n *\n * @example\n * Download an attachment from an email\n * ```typescript\n * const attachment = email.attachments[0];\n * const attachmentBuffer = await client.downloadAttachment(attachment.id);\n *\n * // save to file\n * fs.writeFileSync(attachment.filename, attachmentBuffer);\n * ```\n */\n async downloadAttachment(attachmentId: string): Promise<Buffer> {\n const result = await this.getAttachment(attachmentId);\n\n const response = await axios.get(result.data.download_url, { responseType: \"arraybuffer\" });\n return Buffer.from(response.data);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAiE;AASjE,wBAAuB;AAEhB,IAAM,gBAAN,MAAoB;AAAA,EACzB,YAAY,EAAE,QAAQ,SAAS,KAAK,GAAuE;AACzG,SAAK,gBAAgB,aAAAA,QAAM,OAAO;AAAA,MAChC,SAAS;AAAA,QACP,aAAa;AAAA,MACf;AAAA,MACA,SAAS,WAAW;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEiB;AAAA,EAKjB,MAAM,iBAAkD;AACtD,YAAQ,MAAM,KAAK,cAAc,IAAI,gBAAgB,GAAG;AAAA,EAC1D;AAAA,EAkBA,MAAM,iBAAiB,WAAmB,QAA+C;AACvF,UAAM,eAAe,MAAM,KAAK,gBAAgB,SAAS;AAEzD,UAAM,YAAY,kBAAAC,QAAW,gBAAgB;AAAA,MAC3C,MAAM,aAAa,KAAK;AAAA,MACxB,MAAM,aAAa,KAAK;AAAA,MACxB,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,MAAM,aAAa,KAAK;AAAA,QACxB,MAAM,aAAa,KAAK;AAAA,MAC1B;AAAA,IACF,CAAC;AAED,UAAM,EAAE,MAAM,IAAI,SAAS,MAAM,MAAM,SAAS,YAAY,IAAI;AAEhE,UAAM,UAAU,SAAS;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,cAAU,MAAM;AAAA,EAClB;AAAA,EAwBA,MAAM,YACJ,WACA,QACA,QAC8B;AAC9B,QAAI,UAAU,EAAE,GAAG,OAAO;AAG1B,QAAI,QAAQ,mBAAmB,UAAa,QAAQ,mBAAmB,MAAM;AAC3E,cAAQ,iBAAiB,KAAK,MAAM,IAAI,KAAK,EAAE,QAAQ,IAAI,GAAI,IAAI,KAAK;AAAA,IAC1E;AAGA,QAAI,QAAQ,SAAS,OAAO;AAC1B,cAAQ,OAAO;AAAA,IACjB;AAEA,QAAI,UAAU,EAAE,GAAG,OAAO;AAE1B,QAAI,CAAC,QAAQ,cAAc;AACzB,cAAQ,eAAe;AAAA,IACzB;AAGA,QAAI,QAAQ,QAAQ,CAAC,QAAQ,SAAS;AACpC,cAAQ,UAAU,MAAO,KAAK;AAAA,IAChC;AAEA,YACE,MAAM,KAAK,cAAc,IAAI,cAAc,mBAAmB;AAAA,MAC5D,GAAG;AAAA,MACH,QAAQ;AAAA,IACV,CAAC,GACD;AAAA,EACJ;AAAA,EAKA,MAAM,gBAAgB,WAA0C;AAC9D,UAAM,SAAS,MAAM,KAAK,cAAc,IAAI,YAAY,WAAW;AACnE,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAM,cAAc,cAAsD;AACxE,UAAM,SAAS,MAAM,KAAK,cAAc,IAAI,mBAAmB,cAAc;AAC7E,WAAO,OAAO;AAAA,EAChB;AAAA,EAeA,MAAM,mBAAmB,cAAuC;AAC9D,UAAM,SAAS,MAAM,KAAK,cAAc,YAAY;AAEpD,UAAM,WAAW,MAAM,aAAAD,QAAM,IAAI,OAAO,KAAK,cAAc,EAAE,cAAc,cAAc,CAAC;AAC1F,WAAO,OAAO,KAAK,SAAS,IAAI;AAAA,EAClC;AACF;AA1Ja;","names":["axios","nodemailer"]}
|
package/dist/index.mjs
CHANGED
|
@@ -5,12 +5,13 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
5
5
|
import axios from "axios";
|
|
6
6
|
import nodemailer from "nodemailer";
|
|
7
7
|
var MailiskClient = class {
|
|
8
|
-
constructor({ apiKey, baseUrl }) {
|
|
8
|
+
constructor({ apiKey, baseUrl, auth }) {
|
|
9
9
|
this.axiosInstance = axios.create({
|
|
10
10
|
headers: {
|
|
11
11
|
"X-Api-Key": apiKey
|
|
12
12
|
},
|
|
13
|
-
baseURL: baseUrl || "https://api.mailisk.com/"
|
|
13
|
+
baseURL: baseUrl || "https://api.mailisk.com/",
|
|
14
|
+
auth
|
|
14
15
|
});
|
|
15
16
|
}
|
|
16
17
|
axiosInstance;
|
|
@@ -28,30 +29,35 @@ var MailiskClient = class {
|
|
|
28
29
|
pass: smtpSettings.data.password
|
|
29
30
|
}
|
|
30
31
|
});
|
|
31
|
-
const { from, to, subject, text, html } = params;
|
|
32
|
+
const { from, to, subject, text, html, headers, attachments } = params;
|
|
32
33
|
await transport.sendMail({
|
|
33
34
|
from,
|
|
34
35
|
to,
|
|
35
36
|
subject,
|
|
36
37
|
text,
|
|
37
|
-
html
|
|
38
|
+
html,
|
|
39
|
+
headers,
|
|
40
|
+
attachments
|
|
38
41
|
});
|
|
39
42
|
transport.close();
|
|
40
43
|
}
|
|
41
44
|
async searchInbox(namespace, params, config) {
|
|
42
45
|
let _params = { ...params };
|
|
43
46
|
if (params?.from_timestamp === void 0 || params?.from_timestamp === null) {
|
|
44
|
-
_params.from_timestamp = Math.floor(new Date().getTime() / 1e3) -
|
|
47
|
+
_params.from_timestamp = Math.floor(new Date().getTime() / 1e3) - 15 * 60;
|
|
45
48
|
}
|
|
46
49
|
if (params?.wait !== false) {
|
|
47
50
|
_params.wait = true;
|
|
48
51
|
}
|
|
49
52
|
let _config = { ...config };
|
|
53
|
+
if (!config?.maxRedirects) {
|
|
54
|
+
_config.maxRedirects = 99999;
|
|
55
|
+
}
|
|
50
56
|
if (_params.wait && !config?.timeout) {
|
|
51
57
|
_config.timeout = 1e3 * 60 * 5;
|
|
52
58
|
}
|
|
53
59
|
return (await this.axiosInstance.get(`api/emails/${namespace}/inbox`, {
|
|
54
|
-
...
|
|
60
|
+
..._config,
|
|
55
61
|
params: _params
|
|
56
62
|
})).data;
|
|
57
63
|
}
|
|
@@ -59,6 +65,15 @@ var MailiskClient = class {
|
|
|
59
65
|
const result = await this.axiosInstance.get(`api/smtp/${namespace}`);
|
|
60
66
|
return result.data;
|
|
61
67
|
}
|
|
68
|
+
async getAttachment(attachmentId) {
|
|
69
|
+
const result = await this.axiosInstance.get(`api/attachments/${attachmentId}`);
|
|
70
|
+
return result.data;
|
|
71
|
+
}
|
|
72
|
+
async downloadAttachment(attachmentId) {
|
|
73
|
+
const result = await this.getAttachment(attachmentId);
|
|
74
|
+
const response = await axios.get(result.data.download_url, { responseType: "arraybuffer" });
|
|
75
|
+
return Buffer.from(response.data);
|
|
76
|
+
}
|
|
62
77
|
};
|
|
63
78
|
__name(MailiskClient, "MailiskClient");
|
|
64
79
|
export {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mailisk.ts"],"sourcesContent":["import axios, { AxiosRequestConfig } from \"axios\";\nimport {\n ListNamespacesResponse,\n SearchInboxParams,\n SearchInboxResponse,\n SendVirtualEmailParams,\n SmtpSettings,\n} from \"./mailisk.interfaces\";\nimport nodemailer from \"nodemailer\";\n\nexport class MailiskClient {\n constructor({ apiKey, baseUrl }: { apiKey: string; baseUrl?: string }) {\n this.axiosInstance = axios.create({\n headers: {\n \"X-Api-Key\": apiKey,\n },\n baseURL: baseUrl || \"https://api.mailisk.com/\",\n });\n }\n\n private readonly axiosInstance;\n\n /**\n * List all namespaces that belong to the current account (API key).\n */\n async listNamespaces(): Promise<ListNamespacesResponse> {\n return (await this.axiosInstance.get(\"api/namespaces\")).data;\n }\n\n /**\n * Send an email using the Virtual SMTP.\n *\n * These emails can only be sent to valid Mailisk namespaces, i.e. emails that end in @mynamespace.mailisk.net\n *\n * @example\n * For example, sending a test email:\n * ```typescript\n * client.sendVirtualEmail(namespace, {\n * from: \"test@example.com\",\n * to: `john@${namespace}.mailisk.net`,\n * subject: \"This is a test\",\n * text: \"Testing\",\n * });\n * ```\n */\n async sendVirtualEmail(namespace: string, params: SendVirtualEmailParams): Promise<void> {\n const smtpSettings = await this.getSmtpSettings(namespace);\n\n
|
|
1
|
+
{"version":3,"sources":["../src/mailisk.ts"],"sourcesContent":["import axios, { AxiosBasicCredentials, AxiosRequestConfig } from \"axios\";\nimport {\n GetAttachmentResponse,\n ListNamespacesResponse,\n SearchInboxParams,\n SearchInboxResponse,\n SendVirtualEmailParams,\n SmtpSettings,\n} from \"./mailisk.interfaces\";\nimport nodemailer from \"nodemailer\";\n\nexport class MailiskClient {\n constructor({ apiKey, baseUrl, auth }: { apiKey: string; baseUrl?: string; auth?: AxiosBasicCredentials }) {\n this.axiosInstance = axios.create({\n headers: {\n \"X-Api-Key\": apiKey,\n },\n baseURL: baseUrl || \"https://api.mailisk.com/\",\n auth,\n });\n }\n\n private readonly axiosInstance;\n\n /**\n * List all namespaces that belong to the current account (API key).\n */\n async listNamespaces(): Promise<ListNamespacesResponse> {\n return (await this.axiosInstance.get(\"api/namespaces\")).data;\n }\n\n /**\n * Send an email using the Virtual SMTP.\n *\n * These emails can only be sent to valid Mailisk namespaces, i.e. emails that end in @mynamespace.mailisk.net\n *\n * @example\n * For example, sending a test email:\n * ```typescript\n * client.sendVirtualEmail(namespace, {\n * from: \"test@example.com\",\n * to: `john@${namespace}.mailisk.net`,\n * subject: \"This is a test\",\n * text: \"Testing\",\n * });\n * ```\n */\n async sendVirtualEmail(namespace: string, params: SendVirtualEmailParams): Promise<void> {\n const smtpSettings = await this.getSmtpSettings(namespace);\n\n const transport = nodemailer.createTransport({\n host: smtpSettings.data.host,\n port: smtpSettings.data.port,\n secure: false,\n auth: {\n user: smtpSettings.data.username,\n pass: smtpSettings.data.password,\n },\n });\n\n const { from, to, subject, text, html, headers, attachments } = params;\n\n await transport.sendMail({\n from,\n to,\n subject,\n text,\n html,\n headers,\n attachments,\n });\n\n transport.close();\n }\n\n /**\n * Search inbox of a namespace.\n *\n * By default, this calls the api using the `wait` flag. This means the call won't timeout until at least one email is received or 5 minutes pass.\n * It also uses a default `from_timestamp` of **current timestamp - 5 seconds**. This means that older emails will be ignored.\n *\n * Both of these settings can be overriden by passing them in the `params` object.\n *\n * @example\n * Get the latest emails in the namespace\n * ```typescript\n * const { data: emails } = await client.searchInbox(namespace);\n * ```\n *\n * @example\n * Get the latest emails for a specific email address\n * ```typescript\n * const { data: emails } = await client.searchInbox(namespace, {\n * to_addr_prefix: 'john@mynamespace.mailisk.net'\n * });\n * ```\n */\n async searchInbox(\n namespace: string,\n params?: SearchInboxParams,\n config?: AxiosRequestConfig\n ): Promise<SearchInboxResponse> {\n let _params = { ...params };\n\n // default from timestamp, 15 minutes before starting this request\n if (params?.from_timestamp === undefined || params?.from_timestamp === null) {\n _params.from_timestamp = Math.floor(new Date().getTime() / 1000) - 15 * 60;\n }\n\n // by default wait for email\n if (params?.wait !== false) {\n _params.wait = true;\n }\n\n let _config = { ...config };\n\n if (!config?.maxRedirects) {\n _config.maxRedirects = 99999;\n }\n\n // by default, wait 5 minutes for emails before timing out\n if (_params.wait && !config?.timeout) {\n _config.timeout = 1000 * 60 * 5;\n }\n\n return (\n await this.axiosInstance.get(`api/emails/${namespace}/inbox`, {\n ..._config,\n params: _params,\n })\n ).data;\n }\n\n /**\n * Get the SMTP settings for a namespace.\n */\n async getSmtpSettings(namespace: string): Promise<SmtpSettings> {\n const result = await this.axiosInstance.get(`api/smtp/${namespace}`);\n return result.data;\n }\n\n async getAttachment(attachmentId: string): Promise<GetAttachmentResponse> {\n const result = await this.axiosInstance.get(`api/attachments/${attachmentId}`);\n return result.data;\n }\n\n /**\n * Download an attachment from an attachment ID.\n *\n * @example\n * Download an attachment from an email\n * ```typescript\n * const attachment = email.attachments[0];\n * const attachmentBuffer = await client.downloadAttachment(attachment.id);\n *\n * // save to file\n * fs.writeFileSync(attachment.filename, attachmentBuffer);\n * ```\n */\n async downloadAttachment(attachmentId: string): Promise<Buffer> {\n const result = await this.getAttachment(attachmentId);\n\n const response = await axios.get(result.data.download_url, { responseType: \"arraybuffer\" });\n return Buffer.from(response.data);\n }\n}\n"],"mappings":";;;;AAAA,OAAO,WAA0D;AASjE,OAAO,gBAAgB;AAEhB,IAAM,gBAAN,MAAoB;AAAA,EACzB,YAAY,EAAE,QAAQ,SAAS,KAAK,GAAuE;AACzG,SAAK,gBAAgB,MAAM,OAAO;AAAA,MAChC,SAAS;AAAA,QACP,aAAa;AAAA,MACf;AAAA,MACA,SAAS,WAAW;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEiB;AAAA,EAKjB,MAAM,iBAAkD;AACtD,YAAQ,MAAM,KAAK,cAAc,IAAI,gBAAgB,GAAG;AAAA,EAC1D;AAAA,EAkBA,MAAM,iBAAiB,WAAmB,QAA+C;AACvF,UAAM,eAAe,MAAM,KAAK,gBAAgB,SAAS;AAEzD,UAAM,YAAY,WAAW,gBAAgB;AAAA,MAC3C,MAAM,aAAa,KAAK;AAAA,MACxB,MAAM,aAAa,KAAK;AAAA,MACxB,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ,MAAM,aAAa,KAAK;AAAA,QACxB,MAAM,aAAa,KAAK;AAAA,MAC1B;AAAA,IACF,CAAC;AAED,UAAM,EAAE,MAAM,IAAI,SAAS,MAAM,MAAM,SAAS,YAAY,IAAI;AAEhE,UAAM,UAAU,SAAS;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,cAAU,MAAM;AAAA,EAClB;AAAA,EAwBA,MAAM,YACJ,WACA,QACA,QAC8B;AAC9B,QAAI,UAAU,EAAE,GAAG,OAAO;AAG1B,QAAI,QAAQ,mBAAmB,UAAa,QAAQ,mBAAmB,MAAM;AAC3E,cAAQ,iBAAiB,KAAK,MAAM,IAAI,KAAK,EAAE,QAAQ,IAAI,GAAI,IAAI,KAAK;AAAA,IAC1E;AAGA,QAAI,QAAQ,SAAS,OAAO;AAC1B,cAAQ,OAAO;AAAA,IACjB;AAEA,QAAI,UAAU,EAAE,GAAG,OAAO;AAE1B,QAAI,CAAC,QAAQ,cAAc;AACzB,cAAQ,eAAe;AAAA,IACzB;AAGA,QAAI,QAAQ,QAAQ,CAAC,QAAQ,SAAS;AACpC,cAAQ,UAAU,MAAO,KAAK;AAAA,IAChC;AAEA,YACE,MAAM,KAAK,cAAc,IAAI,cAAc,mBAAmB;AAAA,MAC5D,GAAG;AAAA,MACH,QAAQ;AAAA,IACV,CAAC,GACD;AAAA,EACJ;AAAA,EAKA,MAAM,gBAAgB,WAA0C;AAC9D,UAAM,SAAS,MAAM,KAAK,cAAc,IAAI,YAAY,WAAW;AACnE,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAM,cAAc,cAAsD;AACxE,UAAM,SAAS,MAAM,KAAK,cAAc,IAAI,mBAAmB,cAAc;AAC7E,WAAO,OAAO;AAAA,EAChB;AAAA,EAeA,MAAM,mBAAmB,cAAuC;AAC9D,UAAM,SAAS,MAAM,KAAK,cAAc,YAAY;AAEpD,UAAM,WAAW,MAAM,MAAM,IAAI,OAAO,KAAK,cAAc,EAAE,cAAc,cAAc,CAAC;AAC1F,WAAO,OAAO,KAAK,SAAS,IAAI;AAAA,EAClC;AACF;AA1Ja;","names":[]}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: "ts-jest",
|
|
3
|
+
testEnvironment: "node",
|
|
4
|
+
roots: ["<rootDir>/tests"],
|
|
5
|
+
transform: {
|
|
6
|
+
"^.+\\.tsx?$": "ts-jest",
|
|
7
|
+
},
|
|
8
|
+
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
|
|
9
|
+
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
|
10
|
+
collectCoverage: true,
|
|
11
|
+
collectCoverageFrom: ["src/**/*.ts"],
|
|
12
|
+
setupFilesAfterEnv: ["<rootDir>/tests/setup.ts"],
|
|
13
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailisk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Mailisk library for NodeJS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mailisk",
|
|
@@ -11,12 +11,20 @@
|
|
|
11
11
|
"main": "dist/index.js",
|
|
12
12
|
"types": "dist/index.d.ts",
|
|
13
13
|
"scripts": {
|
|
14
|
-
"build": "tsup"
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"test": "jest",
|
|
16
|
+
"test:watch": "jest --watch",
|
|
17
|
+
"test:coverage": "jest --coverage"
|
|
15
18
|
},
|
|
16
19
|
"author": "",
|
|
17
20
|
"license": "ISC",
|
|
18
21
|
"devDependencies": {
|
|
22
|
+
"@types/jest": "^29.5.14",
|
|
19
23
|
"@types/nodemailer": "^6.4.6",
|
|
24
|
+
"dotenv": "^16.4.7",
|
|
25
|
+
"jest": "^29.7.0",
|
|
26
|
+
"jest-mock-axios": "^4.8.0",
|
|
27
|
+
"ts-jest": "^29.2.6",
|
|
20
28
|
"tsup": "^6.2.3",
|
|
21
29
|
"typescript": "^4.8.4"
|
|
22
30
|
},
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Attachment } from "nodemailer/lib/mailer";
|
|
2
|
+
|
|
1
3
|
export interface EmailAddress {
|
|
2
4
|
/** Email address */
|
|
3
5
|
address: string;
|
|
@@ -5,6 +7,17 @@ export interface EmailAddress {
|
|
|
5
7
|
name?: string;
|
|
6
8
|
}
|
|
7
9
|
|
|
10
|
+
export interface EmailAttachment {
|
|
11
|
+
/** Unique identifier for the attachment */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Filename of the attachment */
|
|
14
|
+
filename: string;
|
|
15
|
+
/** Content type of the attachment */
|
|
16
|
+
content_type: string;
|
|
17
|
+
/** Size in bytes of the attachment */
|
|
18
|
+
size: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
export interface Email {
|
|
9
22
|
/** Namespace scoped ID */
|
|
10
23
|
id: string;
|
|
@@ -30,6 +43,10 @@ export interface Email {
|
|
|
30
43
|
expires_timestamp: number;
|
|
31
44
|
/** The spam score as reported by SpamAssassin */
|
|
32
45
|
spam_score?: number;
|
|
46
|
+
/** The headers of the email */
|
|
47
|
+
headers?: Record<string, string>;
|
|
48
|
+
/** The attachments of the email */
|
|
49
|
+
attachments?: EmailAttachment[];
|
|
33
50
|
}
|
|
34
51
|
|
|
35
52
|
export interface SearchInboxParams {
|
|
@@ -99,6 +116,17 @@ export interface SmtpSettings {
|
|
|
99
116
|
};
|
|
100
117
|
}
|
|
101
118
|
|
|
119
|
+
export interface GetAttachmentResponse {
|
|
120
|
+
data: {
|
|
121
|
+
id: string;
|
|
122
|
+
filename: string;
|
|
123
|
+
content_type: string;
|
|
124
|
+
size: number;
|
|
125
|
+
expires_at: string | null;
|
|
126
|
+
download_url: string;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
102
130
|
export interface ListNamespacesResponse {
|
|
103
131
|
data: [
|
|
104
132
|
{
|
|
@@ -123,4 +151,8 @@ export interface SendVirtualEmailParams {
|
|
|
123
151
|
text?: string | undefined;
|
|
124
152
|
/** The HTML version of the message */
|
|
125
153
|
html?: string | undefined;
|
|
154
|
+
/** Custom headers for the email */
|
|
155
|
+
headers?: Record<string, string>;
|
|
156
|
+
/** Attachments to the email */
|
|
157
|
+
attachments?: Attachment[];
|
|
126
158
|
}
|
package/src/mailisk.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import axios, { AxiosRequestConfig } from "axios";
|
|
1
|
+
import axios, { AxiosBasicCredentials, AxiosRequestConfig } from "axios";
|
|
2
2
|
import {
|
|
3
|
+
GetAttachmentResponse,
|
|
3
4
|
ListNamespacesResponse,
|
|
4
5
|
SearchInboxParams,
|
|
5
6
|
SearchInboxResponse,
|
|
@@ -9,12 +10,13 @@ import {
|
|
|
9
10
|
import nodemailer from "nodemailer";
|
|
10
11
|
|
|
11
12
|
export class MailiskClient {
|
|
12
|
-
constructor({ apiKey, baseUrl }: { apiKey: string; baseUrl?: string }) {
|
|
13
|
+
constructor({ apiKey, baseUrl, auth }: { apiKey: string; baseUrl?: string; auth?: AxiosBasicCredentials }) {
|
|
13
14
|
this.axiosInstance = axios.create({
|
|
14
15
|
headers: {
|
|
15
16
|
"X-Api-Key": apiKey,
|
|
16
17
|
},
|
|
17
18
|
baseURL: baseUrl || "https://api.mailisk.com/",
|
|
19
|
+
auth,
|
|
18
20
|
});
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -46,8 +48,6 @@ export class MailiskClient {
|
|
|
46
48
|
async sendVirtualEmail(namespace: string, params: SendVirtualEmailParams): Promise<void> {
|
|
47
49
|
const smtpSettings = await this.getSmtpSettings(namespace);
|
|
48
50
|
|
|
49
|
-
// TODO: to should match namespace
|
|
50
|
-
|
|
51
51
|
const transport = nodemailer.createTransport({
|
|
52
52
|
host: smtpSettings.data.host,
|
|
53
53
|
port: smtpSettings.data.port,
|
|
@@ -58,7 +58,7 @@ export class MailiskClient {
|
|
|
58
58
|
},
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
const { from, to, subject, text, html } = params;
|
|
61
|
+
const { from, to, subject, text, html, headers, attachments } = params;
|
|
62
62
|
|
|
63
63
|
await transport.sendMail({
|
|
64
64
|
from,
|
|
@@ -66,6 +66,8 @@ export class MailiskClient {
|
|
|
66
66
|
subject,
|
|
67
67
|
text,
|
|
68
68
|
html,
|
|
69
|
+
headers,
|
|
70
|
+
attachments,
|
|
69
71
|
});
|
|
70
72
|
|
|
71
73
|
transport.close();
|
|
@@ -112,6 +114,10 @@ export class MailiskClient {
|
|
|
112
114
|
|
|
113
115
|
let _config = { ...config };
|
|
114
116
|
|
|
117
|
+
if (!config?.maxRedirects) {
|
|
118
|
+
_config.maxRedirects = 99999;
|
|
119
|
+
}
|
|
120
|
+
|
|
115
121
|
// by default, wait 5 minutes for emails before timing out
|
|
116
122
|
if (_params.wait && !config?.timeout) {
|
|
117
123
|
_config.timeout = 1000 * 60 * 5;
|
|
@@ -119,7 +125,7 @@ export class MailiskClient {
|
|
|
119
125
|
|
|
120
126
|
return (
|
|
121
127
|
await this.axiosInstance.get(`api/emails/${namespace}/inbox`, {
|
|
122
|
-
...
|
|
128
|
+
..._config,
|
|
123
129
|
params: _params,
|
|
124
130
|
})
|
|
125
131
|
).data;
|
|
@@ -132,4 +138,29 @@ export class MailiskClient {
|
|
|
132
138
|
const result = await this.axiosInstance.get(`api/smtp/${namespace}`);
|
|
133
139
|
return result.data;
|
|
134
140
|
}
|
|
141
|
+
|
|
142
|
+
async getAttachment(attachmentId: string): Promise<GetAttachmentResponse> {
|
|
143
|
+
const result = await this.axiosInstance.get(`api/attachments/${attachmentId}`);
|
|
144
|
+
return result.data;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Download an attachment from an attachment ID.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* Download an attachment from an email
|
|
152
|
+
* ```typescript
|
|
153
|
+
* const attachment = email.attachments[0];
|
|
154
|
+
* const attachmentBuffer = await client.downloadAttachment(attachment.id);
|
|
155
|
+
*
|
|
156
|
+
* // save to file
|
|
157
|
+
* fs.writeFileSync(attachment.filename, attachmentBuffer);
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
async downloadAttachment(attachmentId: string): Promise<Buffer> {
|
|
161
|
+
const result = await this.getAttachment(attachmentId);
|
|
162
|
+
|
|
163
|
+
const response = await axios.get(result.data.download_url, { responseType: "arraybuffer" });
|
|
164
|
+
return Buffer.from(response.data);
|
|
165
|
+
}
|
|
135
166
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Mock namespace response data
|
|
2
|
+
export const mockNamespacesResponse = {
|
|
3
|
+
namespaces: [
|
|
4
|
+
{
|
|
5
|
+
name: "test-namespace",
|
|
6
|
+
created_at: new Date().toISOString(),
|
|
7
|
+
expires_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
8
|
+
},
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Mock email response data
|
|
13
|
+
export const mockEmailsResponse = {
|
|
14
|
+
emails: [
|
|
15
|
+
{
|
|
16
|
+
id: "email-123",
|
|
17
|
+
from: { address: "sender@example.com", name: "Sender" },
|
|
18
|
+
to: [{ address: "recipient@test-namespace.mailisk.net", name: "Recipient" }],
|
|
19
|
+
subject: "Test Email",
|
|
20
|
+
html: "<p>Test content</p>",
|
|
21
|
+
text: "Test content",
|
|
22
|
+
received_date: new Date().toISOString(),
|
|
23
|
+
received_timestamp: Math.floor(Date.now() / 1000),
|
|
24
|
+
expires_timestamp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
count: 1,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Mock attachment response data
|
|
31
|
+
export const mockAttachmentResponse = {
|
|
32
|
+
id: "attachment-123",
|
|
33
|
+
filename: "test.txt",
|
|
34
|
+
content_type: "text/plain",
|
|
35
|
+
size: 100,
|
|
36
|
+
expires_at: "Fri, 23 Jun 2025 02:29:13 GMT",
|
|
37
|
+
download_url: "https://example.com/attachment-123.txt",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Mock SMTP settings response
|
|
41
|
+
export const mockSmtpSettingsResponse = {
|
|
42
|
+
host: "smtp.mailisk.com",
|
|
43
|
+
port: 25,
|
|
44
|
+
secure: false,
|
|
45
|
+
auth: {
|
|
46
|
+
user: "test-namespace",
|
|
47
|
+
pass: "mock-password",
|
|
48
|
+
},
|
|
49
|
+
};
|
package/tests/setup.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
dotenv.config({ path: path.resolve(process.cwd(), ".env.test") });
|
|
5
|
+
|
|
6
|
+
jest.setTimeout(10000);
|
|
7
|
+
|
|
8
|
+
process.env.TEST_MODE = "true";
|
|
9
|
+
|
|
10
|
+
export const createTestClient = (mockResponses = {}) => {
|
|
11
|
+
const { MailiskClient } = require("../src/mailisk");
|
|
12
|
+
|
|
13
|
+
return new MailiskClient({
|
|
14
|
+
apiKey: "test-api-key",
|
|
15
|
+
baseUrl: "https://test-api.mailisk.com/",
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
jest.resetAllMocks();
|
|
21
|
+
});
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
jest.mock("axios");
|
|
2
|
+
jest.mock("nodemailer");
|
|
3
|
+
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import nodemailer from "nodemailer";
|
|
6
|
+
import { MailiskClient } from "../../src/mailisk";
|
|
7
|
+
import {
|
|
8
|
+
mockNamespacesResponse,
|
|
9
|
+
mockEmailsResponse,
|
|
10
|
+
mockSmtpSettingsResponse,
|
|
11
|
+
mockAttachmentResponse,
|
|
12
|
+
} from "../mocks/axios-mocks";
|
|
13
|
+
|
|
14
|
+
const setupMockAxios = () => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
|
|
17
|
+
const mockGet = jest.fn();
|
|
18
|
+
const mockPost = jest.fn();
|
|
19
|
+
const mockPut = jest.fn();
|
|
20
|
+
const mockDelete = jest.fn();
|
|
21
|
+
|
|
22
|
+
const mockInstance = {
|
|
23
|
+
get: mockGet,
|
|
24
|
+
post: mockPost,
|
|
25
|
+
put: mockPut,
|
|
26
|
+
delete: mockDelete,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
(axios.create as jest.Mock).mockReturnValue(mockInstance);
|
|
30
|
+
(axios.get as jest.Mock).mockImplementation(jest.fn());
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
mockInstance,
|
|
34
|
+
mockGet,
|
|
35
|
+
mockPost,
|
|
36
|
+
mockPut,
|
|
37
|
+
mockDelete,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
describe("MailiskClient", () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
jest.clearAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("constructor", () => {
|
|
47
|
+
it("should initialize with default baseURL when not provided", () => {
|
|
48
|
+
setupMockAxios();
|
|
49
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
50
|
+
expect(axios.create).toHaveBeenCalledWith({
|
|
51
|
+
headers: {
|
|
52
|
+
"X-Api-Key": "test-key",
|
|
53
|
+
},
|
|
54
|
+
baseURL: "https://api.mailisk.com/",
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should initialize with custom baseURL when provided", () => {
|
|
59
|
+
setupMockAxios();
|
|
60
|
+
const client = new MailiskClient({
|
|
61
|
+
apiKey: "test-key",
|
|
62
|
+
baseUrl: "https://custom-api.mailisk.com/",
|
|
63
|
+
});
|
|
64
|
+
expect(axios.create).toHaveBeenCalledWith({
|
|
65
|
+
headers: {
|
|
66
|
+
"X-Api-Key": "test-key",
|
|
67
|
+
},
|
|
68
|
+
baseURL: "https://custom-api.mailisk.com/",
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("listNamespaces", () => {
|
|
74
|
+
it("should fetch and return namespaces", async () => {
|
|
75
|
+
const { mockGet } = setupMockAxios();
|
|
76
|
+
mockGet.mockResolvedValueOnce({ data: mockNamespacesResponse });
|
|
77
|
+
|
|
78
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
79
|
+
const result = await client.listNamespaces();
|
|
80
|
+
|
|
81
|
+
expect(mockGet).toHaveBeenCalledWith("api/namespaces");
|
|
82
|
+
expect(result).toEqual(mockNamespacesResponse);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should handle errors correctly", async () => {
|
|
86
|
+
const { mockGet } = setupMockAxios();
|
|
87
|
+
const error = new Error("API Error");
|
|
88
|
+
mockGet.mockRejectedValueOnce(error);
|
|
89
|
+
|
|
90
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
91
|
+
|
|
92
|
+
await expect(client.listNamespaces()).rejects.toThrow("API Error");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("getSmtpSettings", () => {
|
|
97
|
+
it("should fetch and return SMTP settings for a namespace", async () => {
|
|
98
|
+
const { mockGet } = setupMockAxios();
|
|
99
|
+
mockGet.mockResolvedValueOnce({ data: mockSmtpSettingsResponse });
|
|
100
|
+
|
|
101
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
102
|
+
const result = await client.getSmtpSettings("test-namespace");
|
|
103
|
+
|
|
104
|
+
expect(mockGet).toHaveBeenCalledWith("api/smtp/test-namespace");
|
|
105
|
+
expect(result).toEqual(mockSmtpSettingsResponse);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should handle errors correctly", async () => {
|
|
109
|
+
const { mockGet } = setupMockAxios();
|
|
110
|
+
const error = new Error("SMTP Settings Error");
|
|
111
|
+
mockGet.mockRejectedValueOnce(error);
|
|
112
|
+
|
|
113
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
114
|
+
|
|
115
|
+
await expect(client.getSmtpSettings("test-namespace")).rejects.toThrow("SMTP Settings Error");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("getAttachment", () => {
|
|
120
|
+
it("should fetch and return attachment data", async () => {
|
|
121
|
+
const { mockGet } = setupMockAxios();
|
|
122
|
+
mockGet.mockResolvedValueOnce({ data: mockAttachmentResponse });
|
|
123
|
+
|
|
124
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
125
|
+
const result = await client.getAttachment("attachment-123");
|
|
126
|
+
|
|
127
|
+
expect(mockGet).toHaveBeenCalledWith("api/attachments/attachment-123");
|
|
128
|
+
expect(result).toEqual(mockAttachmentResponse);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("downloadAttachment", () => {
|
|
133
|
+
it("should download and return attachment data", async () => {
|
|
134
|
+
const getAttachmentSpy = jest.spyOn(MailiskClient.prototype, "getAttachment");
|
|
135
|
+
getAttachmentSpy.mockResolvedValueOnce({ data: mockAttachmentResponse });
|
|
136
|
+
|
|
137
|
+
const mockBuffer = Buffer.from("test content");
|
|
138
|
+
(axios.get as jest.Mock).mockResolvedValueOnce({ data: mockBuffer });
|
|
139
|
+
|
|
140
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
141
|
+
const result = await client.downloadAttachment("attachment-123");
|
|
142
|
+
|
|
143
|
+
expect(axios.get).toHaveBeenCalledWith(mockAttachmentResponse.download_url, { responseType: "arraybuffer" });
|
|
144
|
+
expect(result).toEqual(mockBuffer);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("searchInbox", () => {
|
|
149
|
+
it("should fetch and return emails with default parameters", async () => {
|
|
150
|
+
const { mockGet } = setupMockAxios();
|
|
151
|
+
mockGet.mockResolvedValueOnce({ data: mockEmailsResponse });
|
|
152
|
+
|
|
153
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
154
|
+
const result = await client.searchInbox("test-namespace");
|
|
155
|
+
|
|
156
|
+
expect(mockGet).toHaveBeenCalledWith("api/emails/test-namespace/inbox", {
|
|
157
|
+
maxRedirects: 99999,
|
|
158
|
+
timeout: 1000 * 60 * 5,
|
|
159
|
+
params: {
|
|
160
|
+
from_timestamp: expect.any(Number),
|
|
161
|
+
wait: true,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
expect(result).toEqual(mockEmailsResponse);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should use custom parameters if provided", async () => {
|
|
168
|
+
const { mockGet } = setupMockAxios();
|
|
169
|
+
mockGet.mockResolvedValueOnce({ data: mockEmailsResponse });
|
|
170
|
+
|
|
171
|
+
const customParams = {
|
|
172
|
+
limit: 10,
|
|
173
|
+
offset: 5,
|
|
174
|
+
from_timestamp: 1234567890,
|
|
175
|
+
to_timestamp: 1234567899,
|
|
176
|
+
to_addr_prefix: "john",
|
|
177
|
+
from_addr_includes: "@example.com",
|
|
178
|
+
subject_includes: "test",
|
|
179
|
+
wait: false,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
183
|
+
const result = await client.searchInbox("test-namespace", customParams);
|
|
184
|
+
|
|
185
|
+
expect(mockGet).toHaveBeenCalledWith("api/emails/test-namespace/inbox", {
|
|
186
|
+
maxRedirects: 99999,
|
|
187
|
+
params: customParams,
|
|
188
|
+
});
|
|
189
|
+
expect(result).toEqual(mockEmailsResponse);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should use custom axios config if provided", async () => {
|
|
193
|
+
const { mockGet } = setupMockAxios();
|
|
194
|
+
mockGet.mockResolvedValueOnce({ data: mockEmailsResponse });
|
|
195
|
+
|
|
196
|
+
const customParams = {
|
|
197
|
+
wait: true,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const customConfig = {
|
|
201
|
+
timeout: 10000,
|
|
202
|
+
maxRedirects: 5,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
206
|
+
const result = await client.searchInbox("test-namespace", customParams, customConfig);
|
|
207
|
+
|
|
208
|
+
expect(mockGet).toHaveBeenCalledWith("api/emails/test-namespace/inbox", {
|
|
209
|
+
...customConfig,
|
|
210
|
+
params: {
|
|
211
|
+
from_timestamp: expect.any(Number),
|
|
212
|
+
wait: true,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
expect(result).toEqual(mockEmailsResponse);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should handle errors correctly", async () => {
|
|
219
|
+
const { mockGet } = setupMockAxios();
|
|
220
|
+
const error = new Error("Search Inbox Error");
|
|
221
|
+
mockGet.mockRejectedValueOnce(error);
|
|
222
|
+
|
|
223
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
224
|
+
|
|
225
|
+
await expect(client.searchInbox("test-namespace")).rejects.toThrow("Search Inbox Error");
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("sendVirtualEmail", () => {
|
|
230
|
+
// Setup for nodemailer mock
|
|
231
|
+
const mockSendMail = jest.fn();
|
|
232
|
+
const mockClose = jest.fn();
|
|
233
|
+
const mockTransport = {
|
|
234
|
+
sendMail: mockSendMail,
|
|
235
|
+
close: mockClose,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
beforeEach(() => {
|
|
239
|
+
(nodemailer.createTransport as jest.Mock).mockReturnValue(mockTransport);
|
|
240
|
+
mockSendMail.mockReset().mockResolvedValue({});
|
|
241
|
+
mockClose.mockReset();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should send an email using nodemailer", async () => {
|
|
245
|
+
const { mockGet } = setupMockAxios();
|
|
246
|
+
mockGet.mockResolvedValueOnce({
|
|
247
|
+
data: {
|
|
248
|
+
data: {
|
|
249
|
+
host: "smtp.mailisk.com",
|
|
250
|
+
port: 25,
|
|
251
|
+
username: "test-namespace",
|
|
252
|
+
password: "mock-password",
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const emailParams = {
|
|
258
|
+
from: "sender@example.com",
|
|
259
|
+
to: "recipient@test-namespace.mailisk.net",
|
|
260
|
+
subject: "Test Subject",
|
|
261
|
+
text: "Test Content",
|
|
262
|
+
html: "<p>Test HTML Content</p>",
|
|
263
|
+
headers: { "X-Custom-Header": "Custom Value" },
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
267
|
+
await client.sendVirtualEmail("test-namespace", emailParams);
|
|
268
|
+
|
|
269
|
+
expect(mockGet).toHaveBeenCalledWith("api/smtp/test-namespace");
|
|
270
|
+
|
|
271
|
+
expect(nodemailer.createTransport).toHaveBeenCalledWith({
|
|
272
|
+
host: "smtp.mailisk.com",
|
|
273
|
+
port: 25,
|
|
274
|
+
secure: false,
|
|
275
|
+
auth: {
|
|
276
|
+
user: "test-namespace",
|
|
277
|
+
pass: "mock-password",
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
expect(mockSendMail).toHaveBeenCalledWith({
|
|
282
|
+
from: "sender@example.com",
|
|
283
|
+
to: "recipient@test-namespace.mailisk.net",
|
|
284
|
+
subject: "Test Subject",
|
|
285
|
+
text: "Test Content",
|
|
286
|
+
html: "<p>Test HTML Content</p>",
|
|
287
|
+
headers: { "X-Custom-Header": "Custom Value" },
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(mockClose).toHaveBeenCalled();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should handle errors from SMTP settings fetch", async () => {
|
|
294
|
+
const { mockGet } = setupMockAxios();
|
|
295
|
+
const error = new Error("SMTP Settings Error");
|
|
296
|
+
mockGet.mockRejectedValueOnce(error);
|
|
297
|
+
|
|
298
|
+
const emailParams = {
|
|
299
|
+
from: "sender@example.com",
|
|
300
|
+
to: "recipient@test-namespace.mailisk.net",
|
|
301
|
+
subject: "Test Subject",
|
|
302
|
+
text: "Test Content",
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
306
|
+
|
|
307
|
+
await expect(client.sendVirtualEmail("test-namespace", emailParams)).rejects.toThrow("SMTP Settings Error");
|
|
308
|
+
|
|
309
|
+
expect(nodemailer.createTransport).not.toHaveBeenCalled();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should handle errors from nodemailer sendMail", async () => {
|
|
313
|
+
const { mockGet } = setupMockAxios();
|
|
314
|
+
mockGet.mockResolvedValueOnce({
|
|
315
|
+
data: {
|
|
316
|
+
data: {
|
|
317
|
+
host: "smtp.mailisk.com",
|
|
318
|
+
port: 25,
|
|
319
|
+
username: "test-namespace",
|
|
320
|
+
password: "mock-password",
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const sendMailError = new Error("Failed to send email");
|
|
326
|
+
mockSendMail.mockRejectedValueOnce(sendMailError);
|
|
327
|
+
|
|
328
|
+
const emailParams = {
|
|
329
|
+
from: "sender@example.com",
|
|
330
|
+
to: "recipient@test-namespace.mailisk.net",
|
|
331
|
+
subject: "Test Subject",
|
|
332
|
+
text: "Test Content",
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const client = new MailiskClient({ apiKey: "test-key" });
|
|
336
|
+
|
|
337
|
+
await expect(client.sendVirtualEmail("test-namespace", emailParams)).rejects.toThrow("Failed to send email");
|
|
338
|
+
|
|
339
|
+
// In the actual implementation, if sendMail throws an error, close won't be called
|
|
340
|
+
// We just verify that sendMail was called
|
|
341
|
+
expect(mockSendMail).toHaveBeenCalled();
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
});
|