Skip to content

Commit 85a25aa

Browse files
committed
first commit
0 parents  commit 85a25aa

27 files changed

+3870
-0
lines changed

.eslintrc.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"env": {
3+
"es6": true
4+
},
5+
"extends": [
6+
"eslint:recommended",
7+
"plugin:@typescript-eslint/eslint-recommended",
8+
"plugin:@typescript-eslint/recommended"
9+
],
10+
"globals": {
11+
"Atomics": "readonly",
12+
"SharedArrayBuffer": "readonly"
13+
},
14+
"parser": "@typescript-eslint/parser",
15+
"parserOptions": {
16+
"ecmaVersion": 11,
17+
"sourceType": "module"
18+
},
19+
"plugins": ["@typescript-eslint"],
20+
"rules": {
21+
"@typescript-eslint/ban-types": "off",
22+
"@typescript-eslint/no-explicit-any": "off",
23+
"no-empty": "off"
24+
}
25+
}

.github/workflows/tests.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3+
4+
name: Tests
5+
6+
on:
7+
push:
8+
branches: [main]
9+
pull_request:
10+
branches: [main]
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
16+
strategy:
17+
matrix:
18+
node-version: [18.x]
19+
20+
steps:
21+
- uses: actions/checkout@v2
22+
- name: Use Node.js ${{ matrix.node-version }}
23+
uses: actions/setup-node@v1
24+
with:
25+
node-version: ${{ matrix.node-version }}
26+
- run: yarn install
27+
- run: yarn test

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
node_modules
2+
yarn-error.log
3+
npm-error.log
4+
coverage
5+
vite*js
6+
7+
lib

.npmignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
src
2+
__tests__
3+
jest.config.js
4+
yarn-error.log
5+
yarn.lock
6+
.prettierrc
7+
tsconfig.json
8+
logo.png
9+
.github
10+
coverage
11+
vite*js
12+
vitest.config.ts

.prettierrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"tabWidth": 2,
3+
"singleQuote": true,
4+
"semi": true,
5+
"arrowParens": "avoid"
6+
}

LICENSE

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Copyright (c) 2023, Mat Sz
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted (subject to the limitations in the disclaimer
6+
below) provided that the following conditions are met:
7+
8+
* Redistributions of source code must retain the above copyright notice,
9+
this list of conditions and the following disclaimer.
10+
11+
* Redistributions in binary form must reproduce the above copyright
12+
notice, this list of conditions and the following disclaimer in the
13+
documentation and/or other materials provided with the distribution.
14+
15+
* Neither the name of the copyright holder nor the names of its
16+
contributors may be used to endorse or promote products derived from this
17+
software without specific prior written permission.
18+
19+
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
20+
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
21+
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23+
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
24+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27+
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
28+
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30+
POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<h1 align="center">
2+
@typemail/smtp
3+
</h1>
4+
5+
<p align="center">
6+
<img alt="workflow" src="https://img.shields.io/github/actions/workflow/status/typemail/smtp/tests.yml?branch=main">
7+
<a href="https://npmjs.com/package/@typemail/smtp">
8+
<img alt="npm" src="https://img.shields.io/npm/v/@typemail/smtp">
9+
<img alt="npm" src="https://img.shields.io/npm/dw/@typemail/smtp">
10+
<img alt="NPM" src="https://img.shields.io/npm/l/@typemail/smtp">
11+
</a>
12+
</p>
13+
14+
@typemail/smtp is an SMTP library for node.js.
15+
16+
## Server
17+
18+
```js
19+
import { SMTPServer } from '@typemail/smtp/server';
20+
21+
const server = new SMTPServer();
22+
23+
server.on('connection', conn => {
24+
conn.on('message', message => console.log(message));
25+
});
26+
```
27+
28+
`message` will be of the type _SMTPMessage_:
29+
30+
```ts
31+
export interface SMTPMessage {
32+
recipients: string[];
33+
sender: string;
34+
message: string;
35+
}
36+
```
37+
38+
The `message` is a raw message that needs to be parsed. [letterparser](https://github.com/mat-sz/letterparser) can be used to parse and extract data from the raw messages.
39+
40+
### Options
41+
42+
The constructor for `SMTPServer` accepts an options object.
43+
44+
| Property | Default value | Description |
45+
| -------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------ |
46+
| `ip` | `0.0.0.0` | IP address to bind to. |
47+
| `port` | `25` | Port to bind to. (Ports under 1024 usually require superuser privileges.) |
48+
| `hostname` | `localhost` | Hostname advertised by the SMTP server. |
49+
| `size` | `1000000` | Maximum message size (in bytes). |
50+
| `tls` | `undefined` | [createSecureContext options](https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options) for STARTTLS support. |
51+
| `tlsPost` | `465` | Port for secure only communication, only enabled if `tls` is configured properly. |
52+
| `authenticate` | `undefined` | Authentication function. See [Authentication](#Authentication) for more details. |
53+
| `authMethods` | `undefined` | Array of authentication method strings. See [Authentication](#Authentication) for more details. |
54+
55+
### Events
56+
57+
#### `listening`
58+
59+
Emitted when all configured servers are listening.
60+
61+
#### `message`
62+
63+
Emitted when a message is succesfully received.
64+
65+
#### `error`
66+
67+
Emitted when an error occurs.
68+
69+
#### `rejected`
70+
71+
Emitted when a message is rejected. For now, this only happens when the message exceeds the maximum size.
72+
73+
### Authentication
74+
75+
To enable authentication, a function of following type must be passed with the options object:
76+
77+
```ts
78+
authenticate?: (
79+
connection: SMTPServerConnection,
80+
method: string,
81+
payload?: { username: string; password: string; identity?: string; } | string[]
82+
) => boolean | Promise<boolean>;
83+
```
84+
85+
Custom methods can be supported in the following way:
86+
87+
```js
88+
import { SMTPServer } from '@typemail/smtp/server';
89+
90+
const server = new SMTPServer({
91+
supportedAuthenticationMethods: ['XOAUTH2', 'NTLM'],
92+
authenticate: async (connection, method, payload) => {
93+
switch (method) {
94+
case 'XOAUTH2':
95+
if (Array.isArray(payload) && typeof payload[0] === 'string') {
96+
// Validate payload and return the result.
97+
return true;
98+
}
99+
break;
100+
case 'NTLM':
101+
const negotiateResponse = await connection.challenge(
102+
'ntlm supported',
103+
true
104+
);
105+
// Test response.
106+
const authenticateResponse = await connection.challenge('anything');
107+
// Test response.
108+
return true;
109+
break;
110+
}
111+
112+
return false;
113+
},
114+
});
115+
116+
server.on('connection', conn => {
117+
conn.on('message', message => console.log(message));
118+
});
119+
```

__tests__/cert/server.cert

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFazCCA1OgAwIBAgIUQ5Hu/ZrmW2u34z01//6VEqcokn4wDQYJKoZIhvcNAQEL
3+
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
4+
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMzA5MjgxNjQ1NDhaFw0zNDEy
5+
MTUxNjQ1NDhaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
6+
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
7+
AQUAA4ICDwAwggIKAoICAQCvDGqpZqFpW7HbIA6lZvyrwWxwttmwoz12qxskE1tk
8+
oVH6YZWJ3O7gMTqaCydcElsRrrgDq1xZpfhMaQ6WPK4iqu0Wtp3c1zUbXmIbQuzX
9+
V9QdKpLArH/B92tTVdB+UtJDwjRItxt4CVRpuvOATAB1Z1hSnhF2mYwnSMPLe7yq
10+
mO7fh02uaKyLCoMNnDpw6SCrF4TwFH4U2tLseQWnADOIL3Aw8HMKbYdy47+SbD+c
11+
sczbAoMTGX2zsPlphsncU4vl6txynr70ah2mmmNP95HiCixp07c3RTXopieeeNqs
12+
zWn50aoXQTBNfna2hA+d6VpYuVWZxm8y5KVhIr3os0J4mEQ3Cp6fA+pUFnr41/cF
13+
ZUn9Xijrd+yIj+lo1yA/0rXGONRdjWt9q4hjIMnTw5BuiHZCTrYEMUjUbRY9w3gY
14+
cMeJpF7dZKMTkF5cj96I0EUJ5slxqWdsq+YXZKLVmcwC54xbs5xU71o/HLJebSIW
15+
HQgfpxGigjQw5WGPB0EzwR40smnbvpVMSBYWTlBaE1DxEB0Zzxo2jMXl7dLPMp7W
16+
AsrVRFhh/V/pn4Qq/WaTJKuc/lQ0mZIjiqt7aO1gp48Nh0RM+oSLk6Tor8MCgBcM
17+
95KBfPKOMOgw8pOtVhRmDoODP3AxjUQWmt2StSvih3RsbEks4HUSylkYcH4mUBSF
18+
YQIDAQABo1MwUTAdBgNVHQ4EFgQU+5lf/ovvQskPBSnSLiO0F7L1r/YwHwYDVR0j
19+
BBgwFoAU+5lf/ovvQskPBSnSLiO0F7L1r/YwDwYDVR0TAQH/BAUwAwEB/zANBgkq
20+
hkiG9w0BAQsFAAOCAgEAXCrjZADEMJGuUUpM2funat+wFeS/U7zD7kSyNMWDsQgy
21+
GwzAm5sWncT/qfCZryzOS8K/Lqi10sMx9RZhGJquFjwtEhu45cY94fW7FIpkrEce
22+
frq34YGDrwlxJZW1/SK+pIsuGuB+apj8gbijh4nzw0wojs4EBEzo+Xi3zXN81U2L
23+
gN/XYypDt3YSUxI1MCrrWGiErDK7Ki3VAg+RwCJJGBjnQAbKrxIiSU2Wjgatb3NH
24+
MG9HgXgvDQAvxwF7a2k2eNrwOOjLgJaItYjeUygQ4KnxVYe8h5E3fx5guLQf9M1f
25+
HqlTVRR6EFCdhDTk/zLBuocMHa4mLF/bPhlK4mFVt743ga/Wv5IZtvTnuPRVbfch
26+
486JMzmbQYYk+O/ORidK72Qq8vza/CnpBkqhiv7vF9Lnx4Ybt71eHl05QuqxcsUR
27+
7BWnH7rnI6A/VyxQYJOPHi9JBWRcXSBn+L/A8glxhz2VWPxAV6AxGmtUbQ2QCM7q
28+
BBRorYijvQGufGTeaCXiYT8GtnwRtIj3zPbrMG7jbcNHPBF7uV3PIruoyNWWzQav
29+
FqFvsQHg0Ae9NlUn9P2lISwP/qEStYqzevTvX+oTBIsepKdIS+50WqYeiUzUwNsK
30+
yOpIA68GNplavExqyISECFqkOoRpDwJTKCgIWJ5H3rw+dyDA6IOPkRX/kZTCf1w=
31+
-----END CERTIFICATE-----

__tests__/cert/server.key

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
-----BEGIN PRIVATE KEY-----
2+
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
51+
PGggbVpwhCRGZk36lamfUBzEbQ5CvA==
52+
-----END PRIVATE KEY-----

__tests__/client/index.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { sendmail, SMTPServer, SMTPClient } from '../../src/index.js';
2+
3+
const HOST = '127.0.0.1';
4+
const PORT = 5525;
5+
let server: SMTPServer;
6+
7+
describe('client', () => {
8+
// TODO: TLS tests
9+
// TODO: Error handling tests
10+
// TODO: Auth tests
11+
// TODO: Capability tests
12+
// TODO: Greeting tests
13+
14+
beforeAll(() => {
15+
server = new SMTPServer({
16+
hostname: 'localhost',
17+
});
18+
server.listen(PORT, HOST);
19+
});
20+
21+
describe('SMTPClient', () => {
22+
it('sends emails', () => {
23+
const message = {
24+
sender: 'a@localhost',
25+
recipients: ['b@localhost'],
26+
message: 'Test',
27+
};
28+
29+
return new Promise(resolve => {
30+
server.once('connection', connection => {
31+
connection.on('message', msg => {
32+
expect(msg).toMatchObject(message);
33+
resolve(true);
34+
});
35+
});
36+
37+
const client = new SMTPClient({ hostname: HOST, port: PORT });
38+
client.on('ready', () => {
39+
client.mail(message);
40+
});
41+
});
42+
});
43+
});
44+
45+
describe('sendmail', () => {
46+
it('sends emails', () => {
47+
const message = {
48+
sender: 'a@localhost',
49+
recipients: ['b@localhost'],
50+
message: 'Test',
51+
};
52+
53+
return new Promise(resolve => {
54+
server.once('connection', connection => {
55+
connection.on('message', msg => {
56+
expect(msg).toMatchObject(message);
57+
resolve(true);
58+
});
59+
});
60+
61+
sendmail(message, { smtp: { hostname: HOST, port: PORT } });
62+
});
63+
});
64+
});
65+
66+
afterAll(() => {
67+
server.close();
68+
});
69+
});

0 commit comments

Comments
 (0)