├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .prettierrc.json ├── .yarnclean ├── CHANGELOG.md ├── LICENSE ├── README.md ├── ava.config.js ├── email.js ├── email.js.map ├── email.ts ├── package.json ├── rollup.config.ts ├── smtp ├── address.ts ├── client.ts ├── connection.ts ├── date.ts ├── error.ts ├── message.ts ├── mime.ts └── response.ts ├── test ├── address.ts ├── attachments │ ├── postfix-2.8.7.tar.gz │ ├── smtp.gif │ ├── smtp.html │ ├── smtp.pdf │ ├── smtp.txt │ └── smtp2.html ├── auth.ts ├── client.ts ├── connection.ts ├── date.ts ├── message.ts └── mime.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 2 7 | tab_width = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.yml] 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": [ 4 | "@typescript-eslint" 5 | ], 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:prettier/recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/no-explicit-any": [ 13 | "error", 14 | { 15 | "ignoreRestArgs": true 16 | } 17 | ], 18 | "curly": [ 19 | "error", 20 | "all" 21 | ], 22 | "linebreak-style": [ 23 | "error", 24 | "unix" 25 | ], 26 | "valid-jsdoc": "error" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ts text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | 3 | jobs: 4 | test: 5 | name: lint 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | node: [^12, ^14, ^16, ^18] 11 | os: [ubuntu-latest, windows-latest, macos-latest] 12 | 13 | steps: 14 | - name: checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: install 23 | run: yarn install 24 | 25 | - name: lint 26 | run: yarn lint 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | 3 | jobs: 4 | test: 5 | name: test 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | node: [^12, ^14, ^16, ^18] 11 | os: [ubuntu-latest, windows-latest] 12 | 13 | steps: 14 | - name: checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: install 23 | run: yarn install 24 | 25 | - name: test 26 | run: yarn test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | 4 | *.ini 5 | *.log 6 | *.swp 7 | *.swo 8 | *~ 9 | 10 | .DS_Store 11 | package-lock.json 12 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "useTabs": true 5 | } 6 | -------------------------------------------------------------------------------- /.yarnclean: -------------------------------------------------------------------------------- 1 | *~ 2 | *.apache 3 | *.apache2 4 | *.bnf 5 | *.browser.* 6 | *.bsd 7 | *.coffee 8 | *.conf 9 | *.conf.* 10 | *.config.* 11 | *.css 12 | *.docs 13 | *.flow* 14 | *.gif 15 | *.gz 16 | *.html 17 | *.ico 18 | *.iml 19 | *.ini 20 | *.jpeg 21 | *.jpg 22 | *.lcov 23 | *.lock 24 | *.log 25 | *.ls 26 | *.map 27 | *.markdown 28 | *.md 29 | *.mit 30 | *.patch 31 | *.png 32 | *.sh 33 | *.swf 34 | *.txt 35 | *.yml 36 | *-browser.* 37 | *_browser.* 38 | *_example.* 39 | *install* 40 | *publish* 41 | .*config 42 | .*ignore 43 | .*rc 44 | .*rc.* 45 | .git* 46 | .*code 47 | _ts3.4 48 | authors 49 | bench 50 | benchmark 51 | benchmarks 52 | bower.json 53 | browser.* 54 | changelog 55 | component.json 56 | demo 57 | docs 58 | example 59 | examples 60 | fixtures 61 | fp 62 | gruntfile.* 63 | gulpfile.* 64 | jsdoc* 65 | licence* 66 | license* 67 | makefile 68 | man 69 | scripts 70 | spec 71 | test 72 | test-* 73 | !test-*.d.ts 74 | tests 75 | tsserverlibrary.* 76 | typescriptservices.* 77 | webpack* 78 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [4.0.2] - 2023-05-12 8 | ### Fixed 9 | - redact passwords in error messages [#339](https://github.com/eleith/emailjs/issues/339) 10 | 11 | ## [4.0.0] - 2022-04-20 12 | ### Added 13 | - support `isolatedModules` and `preserveValueImports` compilation scenarios [#305](https://github.com/eleith/emailjs/pull/305) 14 | 15 | ### Fixed 16 | - support `typescript@3.8.3` [#307](https://github.com/eleith/emailjs/issues/307) 17 | - the types change in `v3.8.0` for `Client#send` & `Client#sendAsync` unintentionally raised the minimum `typescript` requirement. fixing this involved weakening the types for those functions, which may require modifying your code. this change will be reverted for `v4.0.0`. 18 | 19 | ## [3.8.0] - 2022-03-17 20 | ### Added 21 | - support `typescript@4.6` 22 | - type allow `Client#send` & `Client#sendAsync` to accept message headers instead of a `Message` 23 | - no behavior change: this was previously allowed, but the types didn't acknowledge it 24 | 25 | ## [3.7.0] - 2021-11-19 26 | ### Added 27 | - support `typescript@4.5` 28 | 29 | ## [3.6.0] - 2021-09-03 30 | ### Added 31 | - support `tsc` compilation without `--esModuleInterop` or `--allowSyntheticDefaultImports` [#296](https://github.com/eleith/emailjs/pull/296) 32 | - `Message#readAsync` API [#297](https://github.com/eleith/emailjs/pull/297) 33 | - `Message#checkValidity` API [#298](https://github.com/eleith/emailjs/pull/298) 34 | 35 | ### Deprecated 36 | - `Message#valid` API [#298](https://github.com/eleith/emailjs/pull/298) 37 | 38 | ## [3.5.0] - 2021-06-28 39 | ### Added 40 | - support `tsc --noPropertyAccessFromIndexSignature` [#290](https://github.com/eleith/emailjs/pull/290) 41 | 42 | ### Fixed 43 | - use `engines` field in `package.json` to signal node version support 44 | 45 | ## [3.4.0] - 2020-12-01 46 | ### Added 47 | - `SMTPClient#sendAsync` API [#267](https://github.com/eleith/emailjs/issues/267) 48 | - `isRFC2822Date` API 49 | 50 | ### Changed 51 | - use `WeakSet` instead of `WeakMap` for greylist tracking 52 | 53 | ### Fixed 54 | - use camelCase style for internal function names 55 | - use correct types in jsdoc comments 56 | 57 | ## [3.3.0] - 2020-08-08 58 | ### Added 59 | - greylist support [#202](https://github.com/eleith/emailjs/issues/202) 60 | 61 | ### Fixed 62 | - check socket is writable before sending [#205](https://github.com/eleith/emailjs/issues/205) 63 | 64 | ## [3.2.1] - 2020-06-27 65 | ### Fixed 66 | - use correct type for `MessageAttachment.stream` [#261](https://github.com/eleith/emailjs/issues/261) 67 | - add missing types in mime functions [#262](https://github.com/eleith/emailjs/pull/262) 68 | 69 | ## [3.2.0] - 2020-06-19 70 | ### Added 71 | - `addressparser` API (forked from dropped dependency) [#259](https://github.com/eleith/emailjs/issues/259) 72 | - `mimeEncode`/`mimeWordEncode` APIs (forked from dropped dependency) [#247](https://github.com/eleith/emailjs/issues/247) 73 | 74 | ### Changed 75 | - drop dependency on `addressparser` [#259](https://github.com/eleith/emailjs/issues/259) 76 | - drop dependency on `emailjs-mime-codec` [#247](https://github.com/eleith/emailjs/issues/247) 77 | 78 | ### Fixed 79 | - make `MessageAttachment` interface usable [#254](https://github.com/eleith/emailjs/issues/254) 80 | - mend regression in address type validation [#252](https://github.com/eleith/emailjs/pull/252) 81 | 82 | ## [3.1.0] - 2020-06-19 [YANKED] 83 | 84 | ## [3.0.0] - 2020-05-28 85 | ### Added 86 | - convert source to strict typescript, listed under the `types` field in `package.json` 87 | - support "dual-package" ESM + CJS via [conditional exports](https://nodejs.org/docs/latest-v14.x/api/esm.html#esm_conditional_exports) & `rollup`-generated bundles 88 | - `SMTPClient#creatMessageStack` API [#229](https://github.com/eleith/emailjs/issues/229) 89 | - `SMTPError` API 90 | 91 | ### Changed 92 | - simplify public API [#249](https://github.com/eleith/emailjs/issues/249) 93 | - rename `Client` -> `SMTPClient` [#249](https://github.com/eleith/emailjs/issues/249) 94 | - rename `SMTPResponse` -> `SMTPResponseMonitor` [#249](https://github.com/eleith/emailjs/issues/249) 95 | 96 | ### Removed 97 | - `Message#attach_alternative` API 98 | - `makeSMTPError` API 99 | 100 | ### Fixed 101 | - filter duplicate message recipients [#242](https://github.com/eleith/emailjs/issues/242) 102 | - error when passing `password` without `user` [#199](https://github.com/eleith/emailjs/issues/199) 103 | - trim `host` before connecting [#136](https://github.com/eleith/emailjs/issues/136) 104 | 105 | ## [2.2.0] - 2018-07-06 106 | ### Added 107 | - expose rfc2822 date module 108 | - annotate code with typescript-compatible jsdoc tags 109 | 110 | ### Changed 111 | - drop dependency on `moment` 112 | - drop dependency on `starttls` 113 | 114 | ### Fixed 115 | - ensure timeout is set to default value [#225](https://github.com/eleith/emailjs/issues/225) 116 | 117 | ## [2.1.0] - 2018-06-09 118 | ### Added 119 | - expose error module 120 | 121 | ### Changed 122 | - handle errors with `fs.closeSync` instead of `fs.close` 123 | - refactor to ES2015+ constructs 124 | - lint & format with eslint + prettier 125 | - drop optional dependency on `bufferjs` 126 | 127 | ### Fixed 128 | - remove `new Buffer` calls 129 | 130 | ## [2.0.1] - 2018-02-11 131 | ### Added 132 | - a new changelog 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) <2011> 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # emailjs [![Test Status](https://github.com/eleith/emailjs/workflows/.github/workflows/test.yml/badge.svg)](https://github.com/eleith/emailjs/actions?query=workflow%3A.github%2Fworkflows%2Ftest.yml) [![Lint Status](https://github.com/eleith/emailjs/workflows/.github/workflows/lint.yml/badge.svg)](https://github.com/eleith/emailjs/actions?query=workflow%3A.github%2Fworkflows%2Flint.yml) 2 | 3 | send emails, html and attachments (files, streams and strings) from node.js to any smtp server 4 | 5 | ## INSTALLING 6 | 7 | npm install emailjs 8 | 9 | ## FEATURES 10 | 11 | - works with SSL and TLS smtp servers 12 | - supports smtp authentication ('PLAIN', 'LOGIN', 'CRAM-MD5', 'XOAUTH2') 13 | - emails are queued and the queue is sent asynchronously 14 | - supports sending html emails and emails with multiple attachments (MIME) 15 | - attachments can be added as strings, streams or file paths 16 | - supports utf-8 headers and body 17 | - built-in type declarations 18 | - automatically handles [greylisting](http://projects.puremagic.com/greylisting/whitepaper.html) 19 | 20 | ## REQUIRES 21 | 22 | - auth access to an SMTP Server 23 | - if your service (ex: gmail) uses two-step authentication, use an application specific password 24 | 25 | ## EXAMPLE USAGE - text only emails 26 | 27 | ```js 28 | import { SMTPClient } from 'emailjs'; 29 | 30 | const client = new SMTPClient({ 31 | user: 'user', 32 | password: 'password', 33 | host: 'smtp.your-email.com', 34 | ssl: true, 35 | }); 36 | 37 | // send the message and get a callback with an error or details of the message that was sent 38 | client.send( 39 | { 40 | text: 'i hope this works', 41 | from: 'you ', 42 | to: 'someone , another ', 43 | cc: 'else ', 44 | subject: 'testing emailjs', 45 | }, 46 | (err, message) => { 47 | console.log(err || message); 48 | } 49 | ); 50 | ``` 51 | 52 | ## EXAMPLE USAGE - using async/await 53 | 54 | ```js 55 | // assuming top-level await for brevity 56 | import { SMTPClient } from 'emailjs'; 57 | 58 | const client = new SMTPClient({ 59 | user: 'user', 60 | password: 'password', 61 | host: 'smtp.your-email.com', 62 | ssl: true, 63 | }); 64 | 65 | try { 66 | const message = await client.sendAsync({ 67 | text: 'i hope this works', 68 | from: 'you ', 69 | to: 'someone , another ', 70 | cc: 'else ', 71 | subject: 'testing emailjs', 72 | }); 73 | console.log(message); 74 | } catch (err) { 75 | console.error(err); 76 | } 77 | ``` 78 | 79 | ## EXAMPLE USAGE - html emails and attachments 80 | 81 | ```js 82 | import { SMTPClient } from 'emailjs'; 83 | 84 | const client = new SMTPClient({ 85 | user: 'user', 86 | password: 'password', 87 | host: 'smtp.your-email.com', 88 | ssl: true, 89 | }); 90 | 91 | const message = { 92 | text: 'i hope this works', 93 | from: 'you ', 94 | to: 'someone , another ', 95 | cc: 'else ', 96 | subject: 'testing emailjs', 97 | attachment: [ 98 | { data: 'i hope this works!', alternative: true }, 99 | { path: 'path/to/file.zip', type: 'application/zip', name: 'renamed.zip' }, 100 | ], 101 | }; 102 | 103 | // send the message and get a callback with an error or details of the message that was sent 104 | client.send(message, function (err, message) { 105 | console.log(err || message); 106 | }); 107 | 108 | // you can continue to send more messages with successive calls to 'client.send', 109 | // they will be queued on the same smtp connection 110 | 111 | // or instead of using the built-in client you can create an instance of 'smtp.SMTPConnection' 112 | ``` 113 | 114 | ## EXAMPLE USAGE - sending through outlook 115 | 116 | ```js 117 | import { SMTPClient, Message } from 'emailjs'; 118 | 119 | const client = new SMTPClient({ 120 | user: 'user', 121 | password: 'password', 122 | host: 'smtp-mail.outlook.com', 123 | tls: { 124 | ciphers: 'SSLv3', 125 | }, 126 | }); 127 | 128 | const message = new Message({ 129 | text: 'i hope this works', 130 | from: 'you ', 131 | to: 'someone , another ', 132 | cc: 'else ', 133 | subject: 'testing emailjs', 134 | attachment: [ 135 | { data: 'i hope this works!', alternative: true }, 136 | { path: 'path/to/file.zip', type: 'application/zip', name: 'renamed.zip' }, 137 | ], 138 | }); 139 | 140 | // send the message and get a callback with an error or details of the message that was sent 141 | client.send(message, (err, message) => { 142 | console.log(err || message); 143 | }); 144 | ``` 145 | 146 | ## EXAMPLE USAGE - attaching and embedding an image 147 | 148 | ```js 149 | import { SMTPClient, Message } from 'emailjs'; 150 | 151 | const client = new SMTPClient({ 152 | user: 'user', 153 | password: 'password', 154 | host: 'smtp-mail.outlook.com', 155 | tls: { 156 | ciphers: 'SSLv3', 157 | }, 158 | }); 159 | 160 | const message = new Message({ 161 | text: 'i hope this works', 162 | from: 'you ', 163 | to: 'someone , another ', 164 | cc: 'else ', 165 | subject: 'testing emailjs', 166 | attachment: [ 167 | { 168 | data: 169 | 'i hope this works! here is an image: ', 170 | }, 171 | { path: 'path/to/file.zip', type: 'application/zip', name: 'renamed.zip' }, 172 | { 173 | path: 'path/to/image.jpg', 174 | type: 'image/jpg', 175 | headers: { 'Content-ID': '' }, 176 | }, 177 | ], 178 | }); 179 | 180 | // send the message and get a callback with an error or details of the message that was sent 181 | client.send(message, (err, message) => { 182 | console.log(err || message); 183 | }); 184 | ``` 185 | 186 | # API 187 | 188 | ## new SMTPClient(options) 189 | 190 | ```js 191 | // options is an object with the following recognized schema: 192 | const options = { 193 | user, // username for logging into smtp 194 | password, // password for logging into smtp 195 | host, // smtp host (defaults to 'localhost') 196 | port, // smtp port (defaults to 25 for unencrypted, 465 for `ssl`, and 587 for `tls`) 197 | ssl, // boolean or object (if true or object, ssl connection will be made) 198 | tls, // boolean or object (if true or object, starttls will be initiated) 199 | timeout, // max number of milliseconds to wait for smtp responses (defaults to 5000) 200 | domain, // domain to greet smtp with (defaults to os.hostname) 201 | authentication, // array of preferred authentication methods ('PLAIN', 'LOGIN', 'CRAM-MD5', 'XOAUTH2') 202 | logger, // override the built-in logger (useful for e.g. Azure Function Apps, where console.log doesn't work) 203 | }; 204 | // ssl/tls objects are an abbreviated form of [`tls.connect`](https://nodejs.org/dist/latest-v14.x/docs/api/tls.html#tls_tls_connect_options_callback)'s options 205 | // the missing items are: `port`, `host`, `path`, `socket`, `timeout` and `secureContext` 206 | // NOTE: `host` is trimmed before being used to establish a connection; 207 | // however, the original untrimmed value will still be visible in configuration. 208 | ``` 209 | 210 | ## SMTPClient#send(message, callback) 211 | 212 | ```js 213 | // message can be a smtp.Message (as returned by email.message.create) 214 | // or an object identical to the first argument accepted by email.message.create 215 | 216 | // callback will be executed with (err, message) 217 | // either when message is sent or an error has occurred 218 | ``` 219 | 220 | ## new Message(headers) 221 | 222 | ```js 223 | // headers is an object with the following recognized schema: 224 | const headers = { 225 | from, // sender of the format (address or name
or "name"
) 226 | to, // recipients (same format as above), multiple recipients are separated by a comma 227 | cc, // carbon copied recipients (same format as above) 228 | bcc, // blind carbon copied recipients (same format as above) 229 | text, // text of the email 230 | subject, // string subject of the email 231 | attachment, // one attachment or array of attachments 232 | }; 233 | // the `from` field is required. 234 | // at least one `to`, `cc`, or `bcc` header is also required. 235 | // you can also add whatever other headers you want. 236 | ``` 237 | 238 | ## Message#attach(options) 239 | 240 | Can be called multiple times, each adding a new attachment. 241 | 242 | ```js 243 | // options is an object with the following recognized schema: 244 | const options = { 245 | // one of these fields is required 246 | path, // string to where the file is located 247 | data, // string of the data you want to attach 248 | stream, // binary stream that will provide attachment data (make sure it is in the paused state) 249 | // better performance for binary streams is achieved if buffer.length % (76*6) == 0 250 | // current max size of buffer must be no larger than Message.BUFFERSIZE 251 | 252 | // optionally these fields are also accepted 253 | type, // string of the file mime type 254 | name, // name to give the file as perceived by the recipient 255 | charset, // charset to encode attatchment in 256 | method, // method to send attachment as (used by calendar invites) 257 | alternative, // if true, will be attached inline as an alternative (also defaults type='text/html') 258 | inline, // if true, will be attached inline 259 | encoded, // set this to true if the data is already base64 encoded, (avoid this if possible) 260 | headers, // object containing header=>value pairs for inclusion in this attachment's header 261 | related, // an array of attachments that you want to be related to the parent attachment 262 | }; 263 | ``` 264 | 265 | ## Message#checkValidity() 266 | 267 | Synchronously validate that a Message is properly formed. 268 | 269 | ```js 270 | const message = new Message(options); 271 | const { isValid, validationError } = message.checkValidity(); 272 | if (isValid) { 273 | // ... 274 | } else { 275 | // first error encountered 276 | console.error(validationError); 277 | } 278 | ``` 279 | 280 | ## new SMTPConnection(options={}) 281 | 282 | ```js 283 | // options is an object with the following recognized schema: 284 | const options = { 285 | user, // username for logging into smtp 286 | password, // password for logging into smtp 287 | host, // smtp host (defaults to 'localhost') 288 | port, // smtp port (defaults to 25 for unencrypted, 465 for `ssl`, and 587 for `tls`) 289 | ssl, // boolean or object (if true or object, ssl connection will be made) 290 | tls, // boolean or object (if true or object, starttls will be initiated) 291 | timeout, // max number of milliseconds to wait for smtp responses (defaults to 5000) 292 | domain, // domain to greet smtp with (defaults to os.hostname) 293 | authentication, // array of preferred authentication methods ('PLAIN', 'LOGIN', 'CRAM-MD5', 'XOAUTH2') 294 | logger, // override the built-in logger (useful for e.g. Azure Function Apps, where console.log doesn't work) 295 | }; 296 | // ssl/tls objects are an abbreviated form of [`tls.connect`](https://nodejs.org/dist/latest-v14.x/docs/api/tls.html#tls_tls_connect_options_callback)'s options 297 | // the missing items are: `port`, `host`, `path`, `socket`, `timeout` and `secureContext` 298 | // NOTE: `host` is trimmed before being used to establish a connection; 299 | // however, the original untrimmed value will still be visible in configuration. 300 | ``` 301 | 302 | To target a Message Transfer Agent (MTA), omit all options. 303 | 304 | ## SMTPConnection#authentication 305 | 306 | associative array of currently supported SMTP authentication mechanisms 307 | 308 | ## Authors 309 | 310 | eleith 311 | zackschuster 312 | 313 | ## Testing 314 | 315 | npm install -d 316 | npm test 317 | 318 | ## Contributions 319 | 320 | issues and pull requests are welcome 321 | -------------------------------------------------------------------------------- /ava.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extensions: { 3 | ts: 'module', 4 | }, 5 | environmentVariables: { 6 | NODE_TLS_REJECT_UNAUTHORIZED: '0', 7 | }, 8 | files: ['test/*.ts'], 9 | nodeArguments: ['--loader=ts-node/esm'], 10 | // makes tests far slower 11 | workerThreads: false, 12 | }; 13 | -------------------------------------------------------------------------------- /email.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"email.js","sources":["smtp/address.ts","smtp/date.ts","smtp/mime.ts","smtp/message.ts","smtp/error.ts","smtp/response.ts","smtp/connection.ts","smtp/client.ts"],"sourcesContent":[null,null,null,null,null,null,null,null],"names":["CRLF","readFile","closeFileSync","closeFile","openFile","close"],"mappings":"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"} -------------------------------------------------------------------------------- /email.ts: -------------------------------------------------------------------------------- 1 | export * from './smtp/address.js'; 2 | export * from './smtp/client.js'; 3 | export * from './smtp/connection.js'; 4 | export * from './smtp/date.js'; 5 | export * from './smtp/error.js'; 6 | export * from './smtp/message.js'; 7 | export * from './smtp/mime.js'; 8 | export * from './smtp/response.js'; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emailjs", 3 | "description": "send text/html emails and attachments (files, streams and strings) from node.js to any smtp server", 4 | "version": "4.0.3", 5 | "author": "eleith", 6 | "contributors": [ 7 | "izuzak", 8 | "Hiverness", 9 | "mscdex", 10 | "jimmybergman", 11 | "zackschuster" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "http://github.com/eleith/emailjs.git" 16 | }, 17 | "type": "module", 18 | "devDependencies": { 19 | "@ledge/configs": "23.3.23322", 20 | "@rollup/plugin-typescript": "8.3.2", 21 | "@types/mailparser": "3.4.0", 22 | "@types/node": "12.12.6", 23 | "@types/smtp-server": "3.5.7", 24 | "@typescript-eslint/eslint-plugin": "5.21.0", 25 | "@typescript-eslint/parser": "5.21.0", 26 | "ava": "4.2.0", 27 | "eslint": "8.14.0", 28 | "eslint-config-prettier": "8.5.0", 29 | "eslint-plugin-prettier": "4.0.0", 30 | "mailparser": "3.5.0", 31 | "prettier": "2.6.2", 32 | "rollup": "2.70.2", 33 | "smtp-server": "3.11.0", 34 | "ts-node": "10.9.1", 35 | "tslib": "2.4.0", 36 | "typescript": "4.3.5" 37 | }, 38 | "peerDependencies": { 39 | "typescript": ">=4.3.5" 40 | }, 41 | "peerDependenciesMeta": { 42 | "typescript": { 43 | "optional": true 44 | } 45 | }, 46 | "resolutions": { 47 | "nodemailer": "6.7.4" 48 | }, 49 | "engines": { 50 | "node": ">=12" 51 | }, 52 | "files": [ 53 | "email.js", 54 | "email.ts", 55 | "smtp" 56 | ], 57 | "types": "./email.ts", 58 | "exports": { 59 | "default": "./email.js" 60 | }, 61 | "scripts": { 62 | "build": "rollup -c rollup.config.ts", 63 | "lint": "eslint *.ts \"+(smtp|test)/*.ts\"", 64 | "pretest": "yarn build", 65 | "test": "ava" 66 | }, 67 | "license": "MIT" 68 | } 69 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | import { builtinModules } from 'module'; 2 | import typescript from '@rollup/plugin-typescript'; 3 | 4 | export default { 5 | input: 'email.ts', 6 | output: { 7 | file: 'email.js', 8 | format: 'es', 9 | sourcemap: true, 10 | }, 11 | external: builtinModules, 12 | plugins: [ 13 | typescript({ removeComments: false, include: ['email.ts', 'smtp/*'] }), 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /smtp/address.ts: -------------------------------------------------------------------------------- 1 | interface AddressToken { 2 | type: 'operator' | 'text'; 3 | value: string; 4 | } 5 | 6 | export interface AddressObject { 7 | address?: string; 8 | name?: string; 9 | group?: AddressObject[]; 10 | } 11 | 12 | /* 13 | * Operator tokens and which tokens are expected to end the sequence 14 | */ 15 | const OPERATORS = new Map([ 16 | ['"', '"'], 17 | ['(', ')'], 18 | ['<', '>'], 19 | [',', ''], 20 | // Groups are ended by semicolons 21 | [':', ';'], 22 | // Semicolons are not a legal delimiter per the RFC2822 grammar other 23 | // than for terminating a group, but they are also not valid for any 24 | // other use in this context. Given that some mail clients have 25 | // historically allowed the semicolon as a delimiter equivalent to the 26 | // comma in their UI, it makes sense to treat them the same as a comma 27 | // when used outside of a group. 28 | [';', ''], 29 | ]); 30 | 31 | /** 32 | * Tokenizes the original input string 33 | * 34 | * @param {string | string[] | undefined} address string(s) to tokenize 35 | * @return {AddressToken[]} An array of operator|text tokens 36 | */ 37 | function tokenizeAddress(address: string | string[] = '') { 38 | const tokens: AddressToken[] = []; 39 | let token: AddressToken | undefined = undefined; 40 | let operator: string | undefined = undefined; 41 | 42 | for (const character of address.toString()) { 43 | if ((operator?.length ?? 0) > 0 && character === operator) { 44 | tokens.push({ type: 'operator', value: character }); 45 | token = undefined; 46 | operator = undefined; 47 | } else if ((operator?.length ?? 0) === 0 && OPERATORS.has(character)) { 48 | tokens.push({ type: 'operator', value: character }); 49 | token = undefined; 50 | operator = OPERATORS.get(character); 51 | } else { 52 | if (token == null) { 53 | token = { type: 'text', value: character }; 54 | tokens.push(token); 55 | } else { 56 | token.value += character; 57 | } 58 | } 59 | } 60 | 61 | return tokens 62 | .map((x) => { 63 | x.value = x.value.trim(); 64 | return x; 65 | }) 66 | .filter((x) => x.value.length > 0); 67 | } 68 | 69 | /** 70 | * Converts tokens for a single address into an address object 71 | * 72 | * @param {AddressToken[]} tokens Tokens object 73 | * @return {AddressObject[]} addresses object array 74 | */ 75 | function convertAddressTokens(tokens: AddressToken[]) { 76 | const addressObjects: AddressObject[] = []; 77 | const groups: string[] = []; 78 | let addresses: string[] = []; 79 | let comments: string[] = []; 80 | let texts: string[] = []; 81 | 82 | let state = 'text'; 83 | let isGroup = false; 84 | function handleToken(token: AddressToken) { 85 | if (token.type === 'operator') { 86 | switch (token.value) { 87 | case '<': 88 | state = 'address'; 89 | break; 90 | case '(': 91 | state = 'comment'; 92 | break; 93 | case ':': 94 | state = 'group'; 95 | isGroup = true; 96 | break; 97 | default: 98 | state = 'text'; 99 | break; 100 | } 101 | } else if (token.value.length > 0) { 102 | switch (state) { 103 | case 'address': 104 | addresses.push(token.value); 105 | break; 106 | case 'comment': 107 | comments.push(token.value); 108 | break; 109 | case 'group': 110 | groups.push(token.value); 111 | break; 112 | default: 113 | texts.push(token.value); 114 | break; 115 | } 116 | } 117 | } 118 | 119 | // Filter out , (comments) and regular text 120 | for (const token of tokens) { 121 | handleToken(token); 122 | } 123 | 124 | // If there is no text but a comment, replace the two 125 | if (texts.length === 0 && comments.length > 0) { 126 | texts = [...comments]; 127 | comments = []; 128 | } 129 | 130 | // http://tools.ietf.org/html/rfc2822#appendix-A.1.3 131 | if (isGroup) { 132 | addressObjects.push({ 133 | name: texts.length === 0 ? undefined : texts.join(' '), 134 | group: groups.length > 0 ? addressparser(groups.join(',')) : [], 135 | }); 136 | } else { 137 | // If no address was found, try to detect one from regular text 138 | if (addresses.length === 0 && texts.length > 0) { 139 | for (let i = texts.length - 1; i >= 0; i--) { 140 | if (texts[i].match(/^[^@\s]+@[^@\s]+$/)) { 141 | addresses = texts.splice(i, 1); 142 | break; 143 | } 144 | } 145 | 146 | // still no address 147 | if (addresses.length === 0) { 148 | for (let i = texts.length - 1; i >= 0; i--) { 149 | texts[i] = texts[i] 150 | .replace(/\s*\b[^@\s]+@[^@\s]+\b\s*/, (address: string) => { 151 | if (addresses.length === 0) { 152 | addresses = [address.trim()]; 153 | return ' '; 154 | } else { 155 | return address; 156 | } 157 | }) 158 | .trim(); 159 | 160 | if (addresses.length > 0) { 161 | break; 162 | } 163 | } 164 | } 165 | } 166 | 167 | // If there's still is no text but a comment exixts, replace the two 168 | if (texts.length === 0 && comments.length > 0) { 169 | texts = [...comments]; 170 | comments = []; 171 | } 172 | 173 | // Keep only the first address occurence, push others to regular text 174 | if (addresses.length > 1) { 175 | texts = [...texts, ...addresses.splice(1)]; 176 | } 177 | 178 | if (addresses.length === 0 && isGroup) { 179 | return []; 180 | } else { 181 | // Join values with spaces 182 | let address = addresses.join(' '); 183 | let name = texts.length === 0 ? address : texts.join(' '); 184 | 185 | if (address === name) { 186 | if (address.match(/@/)) { 187 | name = ''; 188 | } else { 189 | address = ''; 190 | } 191 | } 192 | 193 | addressObjects.push({ address, name }); 194 | } 195 | } 196 | 197 | return addressObjects; 198 | } 199 | 200 | /** 201 | * Parses structured e-mail addresses from an address field 202 | * 203 | * Example: 204 | * 205 | * "Name " 206 | * 207 | * will be converted to 208 | * 209 | * [{name: "Name", address: "address@domain"}] 210 | * 211 | * @param {string | string[] | undefined} address Address field 212 | * @return {AddressObject[]} An array of address objects 213 | */ 214 | export function addressparser(address?: string | string[]) { 215 | const addresses: AddressObject[] = []; 216 | let tokens: AddressToken[] = []; 217 | 218 | for (const token of tokenizeAddress(address)) { 219 | if ( 220 | token.type === 'operator' && 221 | (token.value === ',' || token.value === ';') 222 | ) { 223 | if (tokens.length > 0) { 224 | addresses.push(...convertAddressTokens(tokens)); 225 | } 226 | tokens = []; 227 | } else { 228 | tokens.push(token); 229 | } 230 | } 231 | 232 | if (tokens.length > 0) { 233 | addresses.push(...convertAddressTokens(tokens)); 234 | } 235 | 236 | return addresses; 237 | } 238 | -------------------------------------------------------------------------------- /smtp/client.ts: -------------------------------------------------------------------------------- 1 | import { addressparser } from './address.js'; 2 | import type { MessageAttachment, MessageHeaders } from './message.js'; 3 | import { Message } from './message.js'; 4 | import type { SMTPConnectionOptions } from './connection.js'; 5 | import { SMTPConnection, SMTPState } from './connection.js'; 6 | 7 | export type MessageCallback = < 8 | U extends Error | null, 9 | V extends U extends Error ? T : Message 10 | >( 11 | err: U, 12 | msg: V 13 | ) => void; 14 | 15 | export interface MessageStack { 16 | callback: MessageCallback; 17 | message: Message; 18 | attachment: MessageAttachment; 19 | text: string; 20 | returnPath: string; 21 | from: string; 22 | to: ReturnType; 23 | cc: string[]; 24 | bcc: string[]; 25 | } 26 | 27 | export class SMTPClient { 28 | public readonly smtp: SMTPConnection; 29 | public readonly queue: MessageStack[] = []; 30 | 31 | protected sending = false; 32 | protected ready = false; 33 | protected timer: NodeJS.Timeout | null = null; 34 | 35 | /** 36 | * Create a standard SMTP client backed by a self-managed SMTP connection. 37 | * 38 | * NOTE: `host` is trimmed before being used to establish a connection; however, the original untrimmed value will still be visible in configuration. 39 | * 40 | * @param {SMTPConnectionOptions} server smtp options 41 | */ 42 | constructor(server: Partial) { 43 | this.smtp = new SMTPConnection(server); 44 | } 45 | 46 | /** 47 | * @public 48 | * @template {Message | MessageHeaders} T 49 | * @param {T} msg the message to send 50 | * @param {MessageCallback} callback receiver for the error (if any) as well as the passed-in message / headers 51 | * @returns {void} 52 | */ 53 | public send( 54 | msg: T, 55 | callback: MessageCallback 56 | ): void { 57 | const message = 58 | msg instanceof Message 59 | ? msg 60 | : this._canMakeMessage(msg) 61 | ? new Message(msg) 62 | : null; 63 | 64 | if (message == null) { 65 | callback(new Error('message is not a valid Message instance'), msg); 66 | return; 67 | } 68 | 69 | const { isValid, validationError } = message.checkValidity(); 70 | 71 | if (isValid) { 72 | const stack = this.createMessageStack(message, callback); 73 | if (stack.to.length === 0) { 74 | return callback(new Error('No recipients found in message'), msg); 75 | } 76 | this.queue.push(stack); 77 | this._poll(); 78 | } else { 79 | callback(new Error(validationError), msg); 80 | } 81 | } 82 | 83 | /** 84 | * @public 85 | * @template {Message | MessageHeaders} T 86 | * @param {T} msg the message to send 87 | * @returns {Promise} a promise that resolves to the passed-in message / headers 88 | */ 89 | public sendAsync(msg: T) { 90 | return new Promise((resolve, reject) => { 91 | this.send(msg, (err, message) => { 92 | if (err != null) { 93 | reject(err); 94 | } else { 95 | // unfortunately, the conditional type doesn't reach here 96 | // fortunately, we only return a `Message` when err is null, so this is safe 97 | resolve(message as Message); 98 | } 99 | }); 100 | }); 101 | } 102 | 103 | /** 104 | * @public 105 | * @description Converts a message to the raw object used by the internal stack. 106 | * @param {Message} message message to convert 107 | * @param {MessageCallback} callback errback 108 | * @returns {MessageStack} raw message object 109 | */ 110 | public createMessageStack( 111 | message: Message, 112 | callback: MessageCallback = function () { 113 | /* ø */ 114 | } 115 | ) { 116 | const [{ address: from }] = addressparser(message.header.from); 117 | const stack = { 118 | message, 119 | to: [] as ReturnType, 120 | from, 121 | callback: callback.bind(this), 122 | } as MessageStack; 123 | 124 | const { 125 | header: { to, cc, bcc, 'return-path': returnPath }, 126 | } = message; 127 | 128 | if ((typeof to === 'string' || Array.isArray(to)) && to.length > 0) { 129 | stack.to = addressparser(to); 130 | } 131 | 132 | if ((typeof cc === 'string' || Array.isArray(cc)) && cc.length > 0) { 133 | stack.to = stack.to.concat( 134 | addressparser(cc).filter( 135 | (x) => stack.to.some((y) => y.address === x.address) === false 136 | ) 137 | ); 138 | } 139 | 140 | if ((typeof bcc === 'string' || Array.isArray(bcc)) && bcc.length > 0) { 141 | stack.to = stack.to.concat( 142 | addressparser(bcc).filter( 143 | (x) => stack.to.some((y) => y.address === x.address) === false 144 | ) 145 | ); 146 | } 147 | 148 | if (typeof returnPath === 'string' && returnPath.length > 0) { 149 | const parsedReturnPath = addressparser(returnPath); 150 | if (parsedReturnPath.length > 0) { 151 | const [{ address: returnPathAddress }] = parsedReturnPath; 152 | stack.returnPath = returnPathAddress as string; 153 | } 154 | } 155 | 156 | return stack; 157 | } 158 | 159 | /** 160 | * @protected 161 | * @returns {void} 162 | */ 163 | protected _poll() { 164 | if (this.timer != null) { 165 | clearTimeout(this.timer); 166 | } 167 | 168 | if (this.queue.length) { 169 | if (this.smtp.state() == SMTPState.NOTCONNECTED) { 170 | this._connect(this.queue[0]); 171 | } else if ( 172 | this.smtp.state() == SMTPState.CONNECTED && 173 | !this.sending && 174 | this.ready 175 | ) { 176 | this._sendmail(this.queue.shift() as MessageStack); 177 | } 178 | } 179 | // wait around 1 seconds in case something does come in, 180 | // otherwise close out SMTP connection if still open 181 | else if (this.smtp.state() == SMTPState.CONNECTED) { 182 | this.timer = setTimeout(() => this.smtp.quit(), 1000); 183 | } 184 | } 185 | 186 | /** 187 | * @protected 188 | * @param {MessageStack} stack stack 189 | * @returns {void} 190 | */ 191 | protected _connect(stack: MessageStack) { 192 | /** 193 | * @param {Error} err callback error 194 | * @returns {void} 195 | */ 196 | const connect = (err: Error) => { 197 | if (!err) { 198 | const begin = (err: Error) => { 199 | if (!err) { 200 | this.ready = true; 201 | this._poll(); 202 | } else { 203 | stack.callback(err, stack.message); 204 | 205 | // clear out the queue so all callbacks can be called with the same error message 206 | this.queue.shift(); 207 | this._poll(); 208 | } 209 | }; 210 | 211 | if (!this.smtp.authorized()) { 212 | this.smtp.login(begin); 213 | } else { 214 | this.smtp.ehlo_or_helo_if_needed(begin); 215 | } 216 | } else { 217 | stack.callback(err, stack.message); 218 | 219 | // clear out the queue so all callbacks can be called with the same error message 220 | this.queue.shift(); 221 | this._poll(); 222 | } 223 | }; 224 | 225 | this.ready = false; 226 | this.smtp.connect(connect); 227 | } 228 | 229 | /** 230 | * @protected 231 | * @param {MessageStack} msg message stack 232 | * @returns {boolean} can make message 233 | */ 234 | protected _canMakeMessage(msg: MessageHeaders) { 235 | return ( 236 | msg.from && 237 | (msg.to || msg.cc || msg.bcc) && 238 | (msg.text !== undefined || this._containsInlinedHtml(msg.attachment)) 239 | ); 240 | } 241 | 242 | /** 243 | * @protected 244 | * @param {MessageAttachment | MessageAttachment[]} attachment attachment 245 | * @returns {boolean} whether the attachment contains inlined html 246 | */ 247 | protected _containsInlinedHtml( 248 | attachment?: MessageAttachment | MessageAttachment[] 249 | ) { 250 | if (Array.isArray(attachment)) { 251 | return attachment.some((att) => { 252 | return this._isAttachmentInlinedHtml(att); 253 | }); 254 | } else { 255 | return this._isAttachmentInlinedHtml(attachment); 256 | } 257 | } 258 | 259 | /** 260 | * @protected 261 | * @param {MessageAttachment} attachment attachment 262 | * @returns {boolean} whether the attachment is inlined html 263 | */ 264 | protected _isAttachmentInlinedHtml(attachment?: MessageAttachment) { 265 | return ( 266 | attachment && 267 | (attachment.data || attachment.path) && 268 | attachment.alternative === true 269 | ); 270 | } 271 | 272 | /** 273 | * @protected 274 | * @param {MessageStack} stack stack 275 | * @param {function(MessageStack): void} next next 276 | * @returns {function(Error): void} callback 277 | */ 278 | protected _sendsmtp(stack: MessageStack, next: (msg: MessageStack) => void) { 279 | /** 280 | * @param {Error} [err] error 281 | * @returns {void} 282 | */ 283 | return (err: Error) => { 284 | if (!err && next) { 285 | next.apply(this, [stack]); 286 | } else { 287 | // if we snag on SMTP commands, call done, passing the error 288 | // but first reset SMTP state so queue can continue polling 289 | this.smtp.rset(() => this._senddone(err, stack)); 290 | } 291 | }; 292 | } 293 | 294 | /** 295 | * @protected 296 | * @param {MessageStack} stack stack 297 | * @returns {void} 298 | */ 299 | protected _sendmail(stack: MessageStack) { 300 | const from = stack.returnPath || stack.from; 301 | this.sending = true; 302 | this.smtp.mail(this._sendsmtp(stack, this._sendrcpt), '<' + from + '>'); 303 | } 304 | 305 | /** 306 | * @protected 307 | * @param {MessageStack} stack stack 308 | * @returns {void} 309 | */ 310 | protected _sendrcpt(stack: MessageStack) { 311 | if (stack.to == null || typeof stack.to === 'string') { 312 | throw new TypeError('stack.to must be array'); 313 | } 314 | 315 | const to = stack.to.shift()?.address; 316 | this.smtp.rcpt( 317 | this._sendsmtp(stack, stack.to.length ? this._sendrcpt : this._senddata), 318 | `<${to}>` 319 | ); 320 | } 321 | 322 | /** 323 | * @protected 324 | * @param {MessageStack} stack stack 325 | * @returns {void} 326 | */ 327 | protected _senddata(stack: MessageStack) { 328 | this.smtp.data(this._sendsmtp(stack, this._sendmessage)); 329 | } 330 | 331 | /** 332 | * @protected 333 | * @param {MessageStack} stack stack 334 | * @returns {void} 335 | */ 336 | protected _sendmessage(stack: MessageStack) { 337 | const stream = stack.message.stream(); 338 | 339 | stream.on('data', (data) => this.smtp.message(data)); 340 | stream.on('end', () => { 341 | this.smtp.data_end( 342 | this._sendsmtp(stack, () => this._senddone(null, stack)) 343 | ); 344 | }); 345 | 346 | // there is no way to cancel a message while in the DATA portion, 347 | // so we have to close the socket to prevent a bad email from going out 348 | stream.on('error', (err) => { 349 | this.smtp.close(); 350 | this._senddone(err, stack); 351 | }); 352 | } 353 | 354 | /** 355 | * @protected 356 | * @param {Error} err err 357 | * @param {MessageStack} stack stack 358 | * @returns {void} 359 | */ 360 | protected _senddone(err: Error | null, stack: MessageStack) { 361 | this.sending = false; 362 | stack.callback(err, stack.message); 363 | this._poll(); 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /smtp/connection.ts: -------------------------------------------------------------------------------- 1 | import { createHmac } from 'crypto'; 2 | import { EventEmitter } from 'events'; 3 | import { Socket } from 'net'; 4 | import { hostname } from 'os'; 5 | import { connect, createSecureContext, TLSSocket } from 'tls'; 6 | import type { ConnectionOptions } from 'tls'; 7 | 8 | import { SMTPError, SMTPErrorStates } from './error.js'; 9 | import { SMTPResponseMonitor } from './response.js'; 10 | 11 | /** 12 | * @readonly 13 | * @enum 14 | */ 15 | export const AUTH_METHODS = { 16 | PLAIN: 'PLAIN', 17 | 'CRAM-MD5': 'CRAM-MD5', 18 | LOGIN: 'LOGIN', 19 | XOAUTH2: 'XOAUTH2', 20 | } as const; 21 | 22 | /** 23 | * @readonly 24 | * @enum 25 | */ 26 | export const SMTPState = { 27 | NOTCONNECTED: 0, 28 | CONNECTING: 1, 29 | CONNECTED: 2, 30 | } as const; 31 | 32 | export const DEFAULT_TIMEOUT = 5000 as const; 33 | 34 | const SMTP_PORT = 25 as const; 35 | const SMTP_SSL_PORT = 465 as const; 36 | const SMTP_TLS_PORT = 587 as const; 37 | const CRLF = '\r\n' as const; 38 | const GREYLIST_DELAY = 300 as const; 39 | 40 | let DEBUG: 0 | 1 = 0; 41 | 42 | /** 43 | * @param {...any[]} args the message(s) to log 44 | * @returns {void} 45 | */ 46 | const log = (...args: any[]) => { 47 | if (DEBUG === 1) { 48 | args.forEach((d) => 49 | console.log( 50 | typeof d === 'object' 51 | ? d instanceof Error 52 | ? d.message 53 | : JSON.stringify(d) 54 | : d 55 | ) 56 | ); 57 | } 58 | }; 59 | 60 | /** 61 | * @param {function(...any[]): void} callback the function to call 62 | * @param {...any[]} args the arguments to apply to the function 63 | * @returns {void} 64 | */ 65 | const caller = (callback?: (...rest: any[]) => void, ...args: any[]) => { 66 | if (typeof callback === 'function') { 67 | callback(...args); 68 | } 69 | }; 70 | 71 | export type SMTPSocketOptions = Omit< 72 | ConnectionOptions, 73 | 'port' | 'host' | 'path' | 'socket' | 'timeout' | 'secureContext' 74 | >; 75 | 76 | export interface SMTPConnectionOptions { 77 | timeout: number | null; 78 | user: string; 79 | password: string; 80 | domain: string; 81 | host: string; 82 | port: number; 83 | ssl: boolean | SMTPSocketOptions; 84 | tls: boolean | SMTPSocketOptions; 85 | authentication: (keyof typeof AUTH_METHODS)[]; 86 | logger: (...args: any[]) => void; 87 | } 88 | 89 | export interface ConnectOptions { 90 | ssl?: boolean; 91 | } 92 | 93 | export class SMTPConnection extends EventEmitter { 94 | public readonly user: () => string; 95 | public readonly password: () => string; 96 | public readonly timeout: number = DEFAULT_TIMEOUT; 97 | 98 | protected readonly log = log; 99 | protected readonly authentication: (keyof typeof AUTH_METHODS)[] = [ 100 | AUTH_METHODS['CRAM-MD5'], 101 | AUTH_METHODS.LOGIN, 102 | AUTH_METHODS.PLAIN, 103 | AUTH_METHODS.XOAUTH2, 104 | ]; 105 | 106 | protected _state: 0 | 1 | 2 = SMTPState.NOTCONNECTED; 107 | protected _secure = false; 108 | protected loggedin = false; 109 | 110 | protected sock: Socket | TLSSocket | null = null; 111 | protected features: { [index: string]: string | boolean } | null = null; 112 | protected monitor: SMTPResponseMonitor | null = null; 113 | protected domain = hostname(); 114 | protected host = 'localhost'; 115 | protected ssl: boolean | SMTPSocketOptions = false; 116 | protected tls: boolean | SMTPSocketOptions = false; 117 | protected port: number; 118 | 119 | private greylistResponseTracker = new WeakSet<(...rest: any[]) => void>(); 120 | 121 | /** 122 | * SMTP class written using python's (2.7) smtplib.py as a base. 123 | * 124 | * To target a Message Transfer Agent (MTA), omit all options. 125 | * 126 | * NOTE: `host` is trimmed before being used to establish a connection; however, the original untrimmed value will still be visible in configuration. 127 | */ 128 | constructor({ 129 | timeout, 130 | host, 131 | user, 132 | password, 133 | domain, 134 | port, 135 | ssl, 136 | tls, 137 | logger, 138 | authentication, 139 | }: Partial = {}) { 140 | super(); 141 | 142 | if (Array.isArray(authentication)) { 143 | this.authentication = authentication; 144 | } 145 | 146 | if (typeof timeout === 'number') { 147 | this.timeout = timeout; 148 | } 149 | 150 | if (typeof domain === 'string') { 151 | this.domain = domain; 152 | } 153 | 154 | if (typeof host === 'string') { 155 | this.host = host; 156 | } 157 | 158 | if ( 159 | ssl != null && 160 | (typeof ssl === 'boolean' || 161 | (typeof ssl === 'object' && Array.isArray(ssl) === false)) 162 | ) { 163 | this.ssl = ssl; 164 | } 165 | 166 | if ( 167 | tls != null && 168 | (typeof tls === 'boolean' || 169 | (typeof tls === 'object' && Array.isArray(tls) === false)) 170 | ) { 171 | this.tls = tls; 172 | } 173 | 174 | this.port = port || (ssl ? SMTP_SSL_PORT : tls ? SMTP_TLS_PORT : SMTP_PORT); 175 | this.loggedin = user && password ? false : true; 176 | 177 | if (!user && (password?.length ?? 0) > 0) { 178 | throw new Error('`password` cannot be set without `user`'); 179 | } 180 | 181 | // keep these strings hidden when quicky debugging/logging 182 | this.user = () => user as string; 183 | this.password = () => password as string; 184 | 185 | if (typeof logger === 'function') { 186 | this.log = logger; 187 | } 188 | } 189 | 190 | /** 191 | * @public 192 | * @param {0 | 1} level - 193 | * @returns {void} 194 | */ 195 | public debug(level: 0 | 1) { 196 | DEBUG = level; 197 | } 198 | 199 | /** 200 | * @public 201 | * @returns {SMTPState} the current state 202 | */ 203 | public state() { 204 | return this._state; 205 | } 206 | 207 | /** 208 | * @public 209 | * @returns {boolean} whether or not the instance is authorized 210 | */ 211 | public authorized() { 212 | return this.loggedin; 213 | } 214 | 215 | /** 216 | * Establish an SMTP connection. 217 | * 218 | * NOTE: `host` is trimmed before being used to establish a connection; however, the original untrimmed value will still be visible in configuration. 219 | * 220 | * @public 221 | * @param {function(...any[]): void} callback function to call after response 222 | * @param {number} [port] the port to use for the connection 223 | * @param {string} [host] the hostname to use for the connection 224 | * @param {ConnectOptions} [options={}] the options 225 | * @returns {void} 226 | */ 227 | public connect( 228 | callback: (...rest: any[]) => void, 229 | port: number = this.port, 230 | host: string = this.host, 231 | options: ConnectOptions = {} 232 | ) { 233 | this.port = port; 234 | this.host = host; 235 | this.ssl = options.ssl || this.ssl; 236 | 237 | if (this._state !== SMTPState.NOTCONNECTED) { 238 | this.quit(() => this.connect(callback, port, host, options)); 239 | } 240 | 241 | /** 242 | * @returns {void} 243 | */ 244 | const connected = () => { 245 | this.log(`connected: ${this.host}:${this.port}`); 246 | 247 | if (this.ssl && !this.tls) { 248 | // if key/ca/cert was passed in, check if connection is authorized 249 | if ( 250 | typeof this.ssl !== 'boolean' && 251 | this.sock instanceof TLSSocket && 252 | !this.sock.authorized 253 | ) { 254 | this.close(true); 255 | caller( 256 | callback, 257 | SMTPError.create( 258 | 'could not establish an ssl connection', 259 | SMTPErrorStates.CONNECTIONAUTH 260 | ) 261 | ); 262 | } else { 263 | this._secure = true; 264 | } 265 | } 266 | }; 267 | 268 | /** 269 | * @param {Error} err err 270 | * @returns {void} 271 | */ 272 | const connectedErrBack = (err?: Error) => { 273 | if (!err) { 274 | connected(); 275 | } else { 276 | this.close(true); 277 | this.log(err); 278 | caller( 279 | callback, 280 | SMTPError.create( 281 | 'could not connect', 282 | SMTPErrorStates.COULDNOTCONNECT, 283 | err 284 | ) 285 | ); 286 | } 287 | }; 288 | 289 | const response = ( 290 | err: Error | null | undefined, 291 | msg: { code: string | number; data: string } 292 | ) => { 293 | if (err) { 294 | if (this._state === SMTPState.NOTCONNECTED && !this.sock) { 295 | return; 296 | } 297 | this.close(true); 298 | caller(callback, err); 299 | } else if (msg.code == '220') { 300 | this.log(msg.data); 301 | 302 | // might happen first, so no need to wait on connected() 303 | this._state = SMTPState.CONNECTED; 304 | caller(callback, null, msg.data); 305 | } else { 306 | this.log(`response (data): ${msg.data}`); 307 | this.quit(() => { 308 | caller( 309 | callback, 310 | SMTPError.create( 311 | 'bad response on connection', 312 | SMTPErrorStates.BADRESPONSE, 313 | err, 314 | msg.data 315 | ) 316 | ); 317 | }); 318 | } 319 | }; 320 | 321 | this._state = SMTPState.CONNECTING; 322 | this.log(`connecting: ${this.host}:${this.port}`); 323 | 324 | if (this.ssl) { 325 | this.sock = connect( 326 | this.port, 327 | this.host.trim(), 328 | typeof this.ssl === 'object' ? this.ssl : {}, 329 | connected 330 | ); 331 | } else { 332 | this.sock = new Socket(); 333 | this.sock.connect(this.port, this.host.trim(), connectedErrBack); 334 | } 335 | 336 | this.monitor = new SMTPResponseMonitor(this.sock, this.timeout, () => 337 | this.close(true) 338 | ); 339 | this.sock.once('response', response); 340 | this.sock.once('error', response); // the socket could reset or throw, so let's handle it and let the user know 341 | } 342 | 343 | /** 344 | * @public 345 | * @param {string} str the string to send 346 | * @param {function(...any[]): void} callback function to call after response 347 | * @returns {void} 348 | */ 349 | public send(str: string, callback: (...args: any[]) => void) { 350 | if (this.sock != null && this._state === SMTPState.CONNECTED) { 351 | this.log(str); 352 | 353 | this.sock.once('response', (err, msg) => { 354 | if (err) { 355 | caller(callback, err); 356 | } else { 357 | this.log(msg.data); 358 | caller(callback, null, msg); 359 | } 360 | }); 361 | if (this.sock.writable) { 362 | this.sock.write(str); 363 | } 364 | } else { 365 | this.close(true); 366 | caller( 367 | callback, 368 | SMTPError.create( 369 | 'no connection has been established', 370 | SMTPErrorStates.NOCONNECTION 371 | ) 372 | ); 373 | } 374 | } 375 | 376 | /** 377 | * @public 378 | * @param {string} cmd command to issue 379 | * @param {function(...any[]): void} callback function to call after response 380 | * @param {(number[] | number)} [codes=[250]] array codes 381 | * @returns {void} 382 | */ 383 | public command( 384 | cmd: string, 385 | callback: (...rest: any[]) => void, 386 | codes: number[] | number = [250] 387 | ) { 388 | const codesArray = Array.isArray(codes) 389 | ? codes 390 | : typeof codes === 'number' 391 | ? [codes] 392 | : [250]; 393 | 394 | const response = ( 395 | err: Error | null | undefined, 396 | msg: { code: string | number; data: string; message: string } 397 | ) => { 398 | if (err) { 399 | caller(callback, err); 400 | } else { 401 | const code = Number(msg.code); 402 | if (codesArray.indexOf(code) !== -1) { 403 | caller(callback, err, msg.data, msg.message); 404 | } else if ( 405 | (code === 450 || code === 451) && 406 | msg.message.toLowerCase().includes('greylist') && 407 | this.greylistResponseTracker.has(response) === false 408 | ) { 409 | this.greylistResponseTracker.add(response); 410 | setTimeout(() => { 411 | this.send(cmd + CRLF, response); 412 | }, GREYLIST_DELAY); 413 | } else { 414 | const suffix = msg.message ? `: ${msg.message}` : ''; 415 | const errorMessage = `bad response on command '${ 416 | cmd.split(' ')[0] 417 | }'${suffix}`; 418 | caller( 419 | callback, 420 | SMTPError.create( 421 | errorMessage, 422 | SMTPErrorStates.BADRESPONSE, 423 | null, 424 | msg.data 425 | ) 426 | ); 427 | } 428 | } 429 | }; 430 | 431 | this.greylistResponseTracker.delete(response); 432 | this.send(cmd + CRLF, response); 433 | } 434 | 435 | /** 436 | * @public 437 | * @description SMTP 'helo' command. 438 | * 439 | * Hostname to send for self command defaults to the FQDN of the local 440 | * host. 441 | * 442 | * As this command was deprecated by rfc2821, it should only be used for compatibility with non-compliant servers. 443 | * @see https://tools.ietf.org/html/rfc2821#appendix-F.3 444 | * 445 | * @param {function(...any[]): void} callback function to call after response 446 | * @param {string} domain the domain to associate with the 'helo' request 447 | * @returns {void} 448 | */ 449 | public helo(callback: (...rest: any[]) => void, domain?: string) { 450 | this.command(`helo ${domain || this.domain}`, (err, data) => { 451 | if (err) { 452 | caller(callback, err); 453 | } else { 454 | this.parse_smtp_features(data); 455 | caller(callback, err, data); 456 | } 457 | }); 458 | } 459 | 460 | /** 461 | * @public 462 | * @param {function(...any[]): void} callback function to call after response 463 | * @returns {void} 464 | */ 465 | public starttls(callback: (...rest: any[]) => void) { 466 | const response = (err: Error, msg: { data: unknown }) => { 467 | if (this.sock == null) { 468 | throw new Error('null socket'); 469 | } 470 | 471 | if (err) { 472 | err.message += ' while establishing a starttls session'; 473 | caller(callback, err); 474 | } else { 475 | const secureContext = createSecureContext( 476 | typeof this.tls === 'object' ? this.tls : {} 477 | ); 478 | const secureSocket = new TLSSocket(this.sock, { secureContext }); 479 | 480 | secureSocket.on('error', (err: Error) => { 481 | this.close(true); 482 | caller(callback, err); 483 | }); 484 | 485 | this._secure = true; 486 | this.sock = secureSocket; 487 | 488 | new SMTPResponseMonitor(this.sock, this.timeout, () => 489 | this.close(true) 490 | ); 491 | caller(callback, msg.data); 492 | } 493 | }; 494 | 495 | this.command('starttls', response, [220]); 496 | } 497 | 498 | /** 499 | * @public 500 | * @param {string} data the string to parse for features 501 | * @returns {void} 502 | */ 503 | public parse_smtp_features(data: string) { 504 | // According to RFC1869 some (badly written) 505 | // MTA's will disconnect on an ehlo. Toss an exception if 506 | // that happens -ddm 507 | 508 | data.split('\n').forEach((ext) => { 509 | const parse = ext.match(/^(?:\d+[-=]?)\s*?([^\s]+)(?:\s+(.*)\s*?)?$/); 510 | 511 | // To be able to communicate with as many SMTP servers as possible, 512 | // we have to take the old-style auth advertisement into account, 513 | // because: 514 | // 1) Else our SMTP feature parser gets confused. 515 | // 2) There are some servers that only advertise the auth methods we 516 | // support using the old style. 517 | 518 | if (parse != null && this.features != null) { 519 | // RFC 1869 requires a space between ehlo keyword and parameters. 520 | // It's actually stricter, in that only spaces are allowed between 521 | // parameters, but were not going to check for that here. Note 522 | // that the space isn't present if there are no parameters. 523 | this.features[parse[1].toLowerCase()] = parse[2] || true; 524 | } 525 | }); 526 | } 527 | 528 | /** 529 | * @public 530 | * @param {function(...any[]): void} callback function to call after response 531 | * @param {string} domain the domain to associate with the 'ehlo' request 532 | * @returns {void} 533 | */ 534 | public ehlo(callback: (...rest: any[]) => void, domain?: string) { 535 | this.features = {}; 536 | this.command(`ehlo ${domain || this.domain}`, (err, data) => { 537 | if (err) { 538 | caller(callback, err); 539 | } else { 540 | this.parse_smtp_features(data); 541 | 542 | if (this.tls && !this._secure) { 543 | this.starttls(() => this.ehlo(callback, domain)); 544 | } else { 545 | caller(callback, err, data); 546 | } 547 | } 548 | }); 549 | } 550 | 551 | /** 552 | * @public 553 | * @param {string} opt the features keyname to check 554 | * @returns {boolean} whether the extension exists 555 | */ 556 | public has_extn(opt: string) { 557 | return (this.features ?? {})[opt.toLowerCase()] === undefined; 558 | } 559 | 560 | /** 561 | * @public 562 | * @description SMTP 'help' command, returns text from the server 563 | * @param {function(...any[]): void} callback function to call after response 564 | * @param {string} domain the domain to associate with the 'help' request 565 | * @returns {void} 566 | */ 567 | public help(callback: (...rest: any[]) => void, domain: string) { 568 | this.command(domain ? `help ${domain}` : 'help', callback, [211, 214]); 569 | } 570 | 571 | /** 572 | * @public 573 | * @param {function(...any[]): void} callback function to call after response 574 | * @returns {void} 575 | */ 576 | public rset(callback: (...rest: any[]) => void) { 577 | this.command('rset', callback); 578 | } 579 | 580 | /** 581 | * @public 582 | * @param {function(...any[]): void} callback function to call after response 583 | * @returns {void} 584 | */ 585 | public noop(callback: (...rest: any[]) => void) { 586 | this.send('noop', callback); 587 | } 588 | 589 | /** 590 | * @public 591 | * @param {function(...any[]): void} callback function to call after response 592 | * @param {string} from the sender 593 | * @returns {void} 594 | */ 595 | public mail(callback: (...rest: any[]) => void, from: string) { 596 | this.command(`mail FROM:${from}`, callback); 597 | } 598 | 599 | /** 600 | * @public 601 | * @param {function(...any[]): void} callback function to call after response 602 | * @param {string} to the receiver 603 | * @returns {void} 604 | */ 605 | public rcpt(callback: (...rest: any[]) => void, to: string) { 606 | this.command(`RCPT TO:${to}`, callback, [250, 251]); 607 | } 608 | 609 | /** 610 | * @public 611 | * @param {function(...any[]): void} callback function to call after response 612 | * @returns {void} 613 | */ 614 | public data(callback: (...rest: any[]) => void) { 615 | this.command('data', callback, [354]); 616 | } 617 | 618 | /** 619 | * @public 620 | * @param {function(...any[]): void} callback function to call after response 621 | * @returns {void} 622 | */ 623 | public data_end(callback: (...rest: any[]) => void) { 624 | this.command(`${CRLF}.`, callback); 625 | } 626 | 627 | /** 628 | * @public 629 | * @param {string} data the message to send 630 | * @returns {void} 631 | */ 632 | public message(data: string) { 633 | this.log(data); 634 | this.sock?.write(data) ?? this.log('no socket to write to'); 635 | } 636 | 637 | /** 638 | * @public 639 | * @description SMTP 'verify' command -- checks for address validity. 640 | * @param {string} address the address to validate 641 | * @param {function(...any[]): void} callback function to call after response 642 | * @returns {void} 643 | */ 644 | public verify(address: string, callback: (...rest: any[]) => void) { 645 | this.command(`vrfy ${address}`, callback, [250, 251, 252]); 646 | } 647 | 648 | /** 649 | * @public 650 | * @description SMTP 'expn' command -- expands a mailing list. 651 | * @param {string} address the mailing list to expand 652 | * @param {function(...any[]): void} callback function to call after response 653 | * @returns {void} 654 | */ 655 | public expn(address: string, callback: (...rest: any[]) => void) { 656 | this.command(`expn ${address}`, callback); 657 | } 658 | 659 | /** 660 | * @public 661 | * @description Calls this.ehlo() and, if an error occurs, this.helo(). 662 | * 663 | * If there has been no previous EHLO or HELO command self session, self 664 | * method tries ESMTP EHLO first. 665 | * 666 | * @param {function(...any[]): void} callback function to call after response 667 | * @param {string} [domain] the domain to associate with the command 668 | * @returns {void} 669 | */ 670 | public ehlo_or_helo_if_needed( 671 | callback: (...rest: any[]) => void, 672 | domain?: string 673 | ) { 674 | // is this code callable...? 675 | if (!this.features) { 676 | const response = (err: Error, data: unknown) => 677 | caller(callback, err, data); 678 | this.ehlo((err, data) => { 679 | if (err) { 680 | this.helo(response, domain); 681 | } else { 682 | caller(callback, err, data); 683 | } 684 | }, domain); 685 | } 686 | } 687 | 688 | /** 689 | * @public 690 | * 691 | * Log in on an SMTP server that requires authentication. 692 | * 693 | * If there has been no previous EHLO or HELO command self session, self 694 | * method tries ESMTP EHLO first. 695 | * 696 | * This method will return normally if the authentication was successful. 697 | * 698 | * @param {function(...any[]): void} callback function to call after response 699 | * @param {string} [user] the username to authenticate with 700 | * @param {string} [password] the password for the authentication 701 | * @param {{ method: string, domain: string }} [options] login options 702 | * @returns {void} 703 | */ 704 | public login( 705 | callback: (...rest: any[]) => void, 706 | user?: string, 707 | password?: string, 708 | options: { method?: string; domain?: string } = {} 709 | ) { 710 | const login = { 711 | user: user ? () => user : this.user, 712 | password: password ? () => password : this.password, 713 | method: options?.method?.toUpperCase() ?? '', 714 | }; 715 | 716 | const domain = options?.domain || this.domain; 717 | 718 | const initiate = (err: Error | null | undefined, data: unknown) => { 719 | if (err) { 720 | caller(callback, err); 721 | return; 722 | } 723 | 724 | let method: keyof typeof AUTH_METHODS | null = null; 725 | 726 | /** 727 | * @param {string} challenge challenge 728 | * @returns {string} base64 cram hash 729 | */ 730 | const encodeCramMd5 = (challenge: string) => { 731 | const hmac = createHmac('md5', login.password()); 732 | hmac.update(Buffer.from(challenge, 'base64').toString('ascii')); 733 | return Buffer.from(`${login.user()} ${hmac.digest('hex')}`).toString( 734 | 'base64' 735 | ); 736 | }; 737 | 738 | /** 739 | * @returns {string} base64 login/password 740 | */ 741 | const encodePlain = () => 742 | Buffer.from(`\u0000${login.user()}\u0000${login.password()}`).toString( 743 | 'base64' 744 | ); 745 | 746 | /** 747 | * @see https://developers.google.com/gmail/xoauth2_protocol 748 | * @returns {string} base64 xoauth2 auth token 749 | */ 750 | const encodeXoauth2 = () => 751 | Buffer.from( 752 | `user=${login.user()}\u0001auth=Bearer ${login.password()}\u0001\u0001` 753 | ).toString('base64'); 754 | 755 | // List of authentication methods we support: from preferred to 756 | // less preferred methods. 757 | if (!method) { 758 | const preferred = this.authentication; 759 | let auth = ''; 760 | 761 | if (typeof this.features?.['auth'] === 'string') { 762 | auth = this.features['auth']; 763 | } 764 | 765 | for (let i = 0; i < preferred.length; i++) { 766 | if (auth.includes(preferred[i])) { 767 | method = preferred[i]; 768 | break; 769 | } 770 | } 771 | } 772 | 773 | /** 774 | * handle bad responses from command differently 775 | * @param {Error} err err 776 | * @param {unknown} data data 777 | * @returns {void} 778 | */ 779 | const failed = (err: Error, data: unknown) => { 780 | this.loggedin = false; 781 | this.close(); // if auth is bad, close the connection, it won't get better by itself 782 | 783 | err.message = err.message.replace(this.password(), 'REDACTED'); 784 | 785 | caller( 786 | callback, 787 | SMTPError.create( 788 | 'authorization.failed', 789 | SMTPErrorStates.AUTHFAILED, 790 | err, 791 | data 792 | ) 793 | ); 794 | }; 795 | 796 | /** 797 | * @param {Error} err err 798 | * @param {unknown} data data 799 | * @returns {void} 800 | */ 801 | const response = (err: Error | null | undefined, data: unknown) => { 802 | if (err) { 803 | failed(err, data); 804 | } else { 805 | this.loggedin = true; 806 | caller(callback, err, data); 807 | } 808 | }; 809 | 810 | /** 811 | * @param {Error} err err 812 | * @param {unknown} data data 813 | * @param {string} msg msg 814 | * @returns {void} 815 | */ 816 | const attempt = ( 817 | err: Error | null | undefined, 818 | data: unknown, 819 | msg: string 820 | ) => { 821 | if (err) { 822 | failed(err, data); 823 | } else { 824 | if (method === AUTH_METHODS['CRAM-MD5']) { 825 | this.command(encodeCramMd5(msg), response, [235, 503]); 826 | } else if (method === AUTH_METHODS.LOGIN) { 827 | this.command( 828 | Buffer.from(login.password()).toString('base64'), 829 | response, 830 | [235, 503] 831 | ); 832 | } 833 | } 834 | }; 835 | 836 | /** 837 | * @param {Error} err err 838 | * @param {unknown} data data 839 | * @param {string} msg msg 840 | * @returns {void} 841 | */ 842 | const attemptUser = (err: Error, data: unknown) => { 843 | if (err) { 844 | failed(err, data); 845 | } else { 846 | if (method === AUTH_METHODS.LOGIN) { 847 | this.command( 848 | Buffer.from(login.user()).toString('base64'), 849 | attempt, 850 | [334] 851 | ); 852 | } 853 | } 854 | }; 855 | 856 | switch (method) { 857 | case AUTH_METHODS['CRAM-MD5']: 858 | this.command(`AUTH ${AUTH_METHODS['CRAM-MD5']}`, attempt, [334]); 859 | break; 860 | case AUTH_METHODS.LOGIN: 861 | this.command(`AUTH ${AUTH_METHODS.LOGIN}`, attemptUser, [334]); 862 | break; 863 | case AUTH_METHODS.PLAIN: 864 | this.command( 865 | `AUTH ${AUTH_METHODS.PLAIN} ${encodePlain()}`, 866 | response, 867 | [235, 503] 868 | ); 869 | break; 870 | case AUTH_METHODS.XOAUTH2: 871 | this.command( 872 | `AUTH ${AUTH_METHODS.XOAUTH2} ${encodeXoauth2()}`, 873 | response, 874 | [235, 503] 875 | ); 876 | break; 877 | default: 878 | caller( 879 | callback, 880 | SMTPError.create( 881 | 'no form of authorization supported', 882 | SMTPErrorStates.AUTHNOTSUPPORTED, 883 | null, 884 | data 885 | ) 886 | ); 887 | break; 888 | } 889 | }; 890 | 891 | this.ehlo_or_helo_if_needed(initiate, domain); 892 | } 893 | 894 | /** 895 | * @public 896 | * @param {boolean} [force=false] whether or not to force destroy the connection 897 | * @returns {void} 898 | */ 899 | public close(force = false) { 900 | if (this.sock) { 901 | if (force) { 902 | this.log('smtp connection destroyed!'); 903 | this.sock.destroy(); 904 | } else { 905 | this.log('smtp connection closed.'); 906 | this.sock.end(); 907 | } 908 | } 909 | 910 | if (this.monitor) { 911 | this.monitor.stop(); 912 | this.monitor = null; 913 | } 914 | 915 | this._state = SMTPState.NOTCONNECTED; 916 | this._secure = false; 917 | this.sock = null; 918 | this.features = null; 919 | this.loggedin = !(this.user() && this.password()); 920 | } 921 | 922 | /** 923 | * @public 924 | * @param {function(...any[]): void} [callback] function to call after response 925 | * @returns {void} 926 | */ 927 | public quit(callback?: (...rest: any[]) => void) { 928 | this.command( 929 | 'quit', 930 | (err, data) => { 931 | caller(callback, err, data); 932 | this.close(); 933 | }, 934 | [221, 250] 935 | ); 936 | } 937 | } 938 | -------------------------------------------------------------------------------- /smtp/date.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Date} [date] an optional date to convert to RFC2822 format 3 | * @param {boolean} [useUtc] whether to parse the date as UTC (default: false) 4 | * @returns {string} the converted date 5 | */ 6 | export function getRFC2822Date(date = new Date(), useUtc = false) { 7 | if (useUtc) { 8 | return getRFC2822DateUTC(date); 9 | } 10 | 11 | const dates = date 12 | .toString() 13 | .replace('GMT', '') 14 | .replace(/\s\(.*\)$/, '') 15 | .split(' '); 16 | 17 | dates[0] = dates[0] + ','; 18 | 19 | const day = dates[1]; 20 | dates[1] = dates[2]; 21 | dates[2] = day; 22 | 23 | return dates.join(' '); 24 | } 25 | 26 | /** 27 | * @param {Date} [date] an optional date to convert to RFC2822 format (UTC) 28 | * @returns {string} the converted date 29 | */ 30 | export function getRFC2822DateUTC(date = new Date()) { 31 | const dates = date.toUTCString().split(' '); 32 | dates.pop(); // remove timezone 33 | dates.push('+0000'); 34 | return dates.join(' '); 35 | } 36 | 37 | /** 38 | * RFC 2822 regex 39 | * @see https://tools.ietf.org/html/rfc2822#section-3.3 40 | * @see https://github.com/moment/moment/blob/a831fc7e2694281ce31e4f090bbcf90a690f0277/src/lib/create/from-string.js#L101 41 | */ 42 | const rfc2822re = 43 | /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/; 44 | 45 | /** 46 | * @param {string} [date] a string to check for conformance to the [rfc2822](https://tools.ietf.org/html/rfc2822#section-3.3) standard 47 | * @returns {boolean} the result of the conformance check 48 | */ 49 | export function isRFC2822Date(date: string) { 50 | return rfc2822re.test(date); 51 | } 52 | -------------------------------------------------------------------------------- /smtp/error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @readonly 3 | * @enum 4 | */ 5 | export const SMTPErrorStates = { 6 | COULDNOTCONNECT: 1, 7 | BADRESPONSE: 2, 8 | AUTHFAILED: 3, 9 | TIMEDOUT: 4, 10 | ERROR: 5, 11 | NOCONNECTION: 6, 12 | AUTHNOTSUPPORTED: 7, 13 | CONNECTIONCLOSED: 8, 14 | CONNECTIONENDED: 9, 15 | CONNECTIONAUTH: 10, 16 | } as const; 17 | 18 | export class SMTPError extends Error { 19 | public code: number | null = null; 20 | public smtp: unknown = null; 21 | public previous: Error | null = null; 22 | 23 | /** 24 | * @protected 25 | * @param {string} message error message 26 | */ 27 | protected constructor(message: string) { 28 | super(message); 29 | } 30 | 31 | /** 32 | * 33 | * @param {string} message error message 34 | * @param {number} code smtp error state 35 | * @param {Error | null} error previous error 36 | * @param {unknown} smtp arbitrary data 37 | * @returns {SMTPError} error 38 | */ 39 | public static create( 40 | message: string, 41 | code: number, 42 | error?: Error | null, 43 | smtp?: unknown 44 | ) { 45 | const msg = error?.message ? `${message} (${error.message})` : message; 46 | const err = new SMTPError(msg); 47 | 48 | err.code = code; 49 | err.smtp = smtp; 50 | 51 | if (error) { 52 | err.previous = error; 53 | } 54 | 55 | return err; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /smtp/message.ts: -------------------------------------------------------------------------------- 1 | import type { PathLike } from 'fs'; 2 | import { 3 | existsSync, 4 | open as openFile, 5 | close as closeFile, 6 | closeSync as closeFileSync, 7 | read as readFile, 8 | } from 'fs'; 9 | import { hostname } from 'os'; 10 | import { Stream } from 'stream'; 11 | import type { Readable } from 'stream'; 12 | 13 | import { addressparser } from './address.js'; 14 | import { getRFC2822Date } from './date.js'; 15 | import { mimeWordEncode } from './mime.js'; 16 | 17 | const CRLF = '\r\n' as const; 18 | 19 | /** 20 | * MIME standard wants 76 char chunks when sending out. 21 | */ 22 | export const MIMECHUNK = 76 as const; 23 | 24 | /** 25 | * meets both base64 and mime divisibility 26 | */ 27 | export const MIME64CHUNK = (MIMECHUNK * 6) as 456; 28 | 29 | /** 30 | * size of the message stream buffer 31 | */ 32 | export const BUFFERSIZE = (MIMECHUNK * 24 * 7) as 12768; 33 | 34 | export interface MessageAttachmentHeaders { 35 | [index: string]: string | undefined; 36 | 'content-type'?: string; 37 | 'content-transfer-encoding'?: BufferEncoding | '7bit' | '8bit'; 38 | 'content-disposition'?: string; 39 | } 40 | 41 | export interface MessageAttachment { 42 | [index: string]: 43 | | string 44 | | boolean 45 | | MessageAttachment 46 | | MessageAttachment[] 47 | | MessageAttachmentHeaders 48 | | Readable 49 | | PathLike 50 | | undefined; 51 | name?: string; 52 | headers?: MessageAttachmentHeaders; 53 | inline?: boolean; 54 | alternative?: MessageAttachment | boolean; 55 | related?: MessageAttachment[]; 56 | data?: string; 57 | encoded?: boolean; 58 | stream?: Readable; 59 | path?: PathLike; 60 | type?: string; 61 | charset?: string; 62 | method?: string; 63 | } 64 | 65 | export interface MessageHeaders { 66 | [index: string]: 67 | | boolean 68 | | string 69 | | string[] 70 | | null 71 | | undefined 72 | | MessageAttachment 73 | | MessageAttachment[]; 74 | 'content-type'?: string; 75 | 'message-id'?: string; 76 | 'return-path'?: string | null; 77 | date?: string; 78 | from: string | string[]; 79 | to: string | string[]; 80 | cc?: string | string[]; 81 | bcc?: string | string[]; 82 | subject: string; 83 | text: string | null; 84 | attachment?: MessageAttachment | MessageAttachment[]; 85 | } 86 | 87 | let counter = 0; 88 | 89 | function generateBoundary() { 90 | let text = ''; 91 | const possible = 92 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'()+_,-./:=?"; 93 | 94 | for (let i = 0; i < 69; i++) { 95 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 96 | } 97 | 98 | return text; 99 | } 100 | 101 | function convertPersonToAddress(person: string | string[]) { 102 | return addressparser(person) 103 | .map(({ name, address }) => { 104 | return name 105 | ? `${mimeWordEncode(name).replace(/,/g, '=2C')} <${address}>` 106 | : address; 107 | }) 108 | .join(', '); 109 | } 110 | 111 | function convertDashDelimitedTextToSnakeCase(text: string) { 112 | return text 113 | .toLowerCase() 114 | .replace(/^(.)|-(.)/g, (match) => match.toUpperCase()); 115 | } 116 | 117 | export class Message { 118 | public readonly attachments: MessageAttachment[] = []; 119 | public readonly header: Partial = { 120 | 'message-id': `<${new Date().getTime()}.${counter++}.${ 121 | process.pid 122 | }@${hostname()}>`, 123 | date: getRFC2822Date(), 124 | }; 125 | public readonly content: string = 'text/plain; charset=utf-8'; 126 | public readonly text?: string; 127 | public alternative: MessageAttachment | null = null; 128 | 129 | /** 130 | * Construct an rfc2822-compliant message object. 131 | * 132 | * Special notes: 133 | * - The `from` field is required. 134 | * - At least one `to`, `cc`, or `bcc` header is also required. 135 | * - You can also add whatever other headers you want. 136 | * 137 | * @see https://tools.ietf.org/html/rfc2822 138 | * @param {Partial} headers Message headers 139 | */ 140 | constructor(headers: Partial) { 141 | for (const header in headers) { 142 | // allow user to override default content-type to override charset or send a single non-text message 143 | if (/^content-type$/i.test(header)) { 144 | this.content = headers[header] as string; 145 | } else if (header === 'text') { 146 | this.text = headers[header] as string; 147 | } else if ( 148 | header === 'attachment' && 149 | typeof headers[header] === 'object' 150 | ) { 151 | const attachment = headers[header]; 152 | if (Array.isArray(attachment)) { 153 | for (let i = 0; i < attachment.length; i++) { 154 | this.attach(attachment[i]); 155 | } 156 | } else if (attachment != null) { 157 | this.attach(attachment); 158 | } 159 | } else if (header === 'subject') { 160 | this.header.subject = mimeWordEncode(headers.subject as string); 161 | } else if (/^(cc|bcc|to|from)/i.test(header)) { 162 | this.header[header.toLowerCase()] = convertPersonToAddress( 163 | headers[header] as string | string[] 164 | ); 165 | } else { 166 | // allow any headers the user wants to set?? 167 | this.header[header.toLowerCase()] = headers[header]; 168 | } 169 | } 170 | } 171 | 172 | /** 173 | * Attach a file to the message. 174 | * 175 | * Can be called multiple times, each adding a new attachment. 176 | * 177 | * @public 178 | * @param {MessageAttachment} options attachment options 179 | * @returns {Message} the current instance for chaining 180 | */ 181 | public attach(options: MessageAttachment) { 182 | // sender can specify an attachment as an alternative 183 | if (options.alternative) { 184 | this.alternative = options; 185 | this.alternative.charset = options.charset || 'utf-8'; 186 | this.alternative.type = options.type || 'text/html'; 187 | this.alternative.inline = true; 188 | } else { 189 | this.attachments.push(options); 190 | } 191 | 192 | return this; 193 | } 194 | 195 | /** 196 | * @public 197 | * @returns {{ isValid: boolean, validationError: (string | undefined) }} an object specifying whether this message is validly formatted, and the first validation error if it is not. 198 | */ 199 | public checkValidity() { 200 | if ( 201 | typeof this.header.from !== 'string' && 202 | Array.isArray(this.header.from) === false 203 | ) { 204 | return { 205 | isValid: false, 206 | validationError: 'Message must have a `from` header', 207 | }; 208 | } 209 | 210 | if ( 211 | typeof this.header.to !== 'string' && 212 | Array.isArray(this.header.to) === false && 213 | typeof this.header.cc !== 'string' && 214 | Array.isArray(this.header.cc) === false && 215 | typeof this.header.bcc !== 'string' && 216 | Array.isArray(this.header.bcc) === false 217 | ) { 218 | return { 219 | isValid: false, 220 | validationError: 221 | 'Message must have at least one `to`, `cc`, or `bcc` header', 222 | }; 223 | } 224 | 225 | if (this.attachments.length > 0) { 226 | const failed: string[] = []; 227 | 228 | this.attachments.forEach((attachment) => { 229 | if (attachment.path) { 230 | if (existsSync(attachment.path) === false) { 231 | failed.push(`${attachment.path} does not exist`); 232 | } 233 | } else if (attachment.stream) { 234 | if (!attachment.stream.readable) { 235 | failed.push('attachment stream is not readable'); 236 | } 237 | } else if (!attachment.data) { 238 | failed.push('attachment has no data associated with it'); 239 | } 240 | }); 241 | return { 242 | isValid: failed.length === 0, 243 | validationError: failed.join(', '), 244 | }; 245 | } 246 | 247 | return { isValid: true, validationError: undefined }; 248 | } 249 | 250 | /** 251 | * @public 252 | * @deprecated does not conform to the `errback` style followed by the rest of the library, and will be removed in the next major version. use `checkValidity` instead. 253 | * @param {function(isValid: boolean, invalidReason: (string | undefined)): void} callback . 254 | * @returns {void} 255 | */ 256 | public valid(callback: (isValid: boolean, invalidReason?: string) => void) { 257 | const { isValid, validationError } = this.checkValidity(); 258 | callback(isValid, validationError); 259 | } 260 | 261 | /** 262 | * @public 263 | * @returns {MessageStream} a stream of the current message 264 | */ 265 | public stream() { 266 | return new MessageStream(this); 267 | } 268 | 269 | /** 270 | * @public 271 | * @param {function(Error, string): void} callback the function to call with the error and buffer 272 | * @returns {void} 273 | */ 274 | public read(callback: (err: Error, buffer: string) => void) { 275 | let buffer = ''; 276 | const str = this.stream(); 277 | str.on('data', (data) => (buffer += data)); 278 | str.on('end', (err) => callback(err, buffer)); 279 | str.on('error', (err) => callback(err, buffer)); 280 | } 281 | 282 | public readAsync() { 283 | return new Promise((resolve, reject) => { 284 | this.read((err, buffer) => { 285 | if (err != null) { 286 | reject(err); 287 | } else { 288 | resolve(buffer); 289 | } 290 | }); 291 | }); 292 | } 293 | } 294 | 295 | class MessageStream extends Stream { 296 | readable = true; 297 | paused = false; 298 | buffer: Buffer | null = Buffer.alloc(MIMECHUNK * 24 * 7); 299 | bufferIndex = 0; 300 | 301 | /** 302 | * @param {Message} message the message to stream 303 | */ 304 | constructor(private message: Message) { 305 | super(); 306 | 307 | /** 308 | * @param {string} [data] the data to output 309 | * @param {Function} [callback] the function 310 | * @param {any[]} [args] array of arguments to pass to the callback 311 | * @returns {void} 312 | */ 313 | const output = (data: string) => { 314 | // can we buffer the data? 315 | if (this.buffer != null) { 316 | const bytes = Buffer.byteLength(data); 317 | 318 | if (bytes + this.bufferIndex < this.buffer.length) { 319 | this.buffer.write(data, this.bufferIndex); 320 | this.bufferIndex += bytes; 321 | } 322 | // we can't buffer the data, so ship it out! 323 | else if (bytes > this.buffer.length) { 324 | if (this.bufferIndex) { 325 | this.emit( 326 | 'data', 327 | this.buffer.toString('utf-8', 0, this.bufferIndex) 328 | ); 329 | this.bufferIndex = 0; 330 | } 331 | 332 | const loops = Math.ceil(data.length / this.buffer.length); 333 | let loop = 0; 334 | while (loop < loops) { 335 | this.emit( 336 | 'data', 337 | data.substring( 338 | this.buffer.length * loop, 339 | this.buffer.length * (loop + 1) 340 | ) 341 | ); 342 | loop++; 343 | } 344 | } // we need to clean out the buffer, it is getting full 345 | else { 346 | if (!this.paused) { 347 | this.emit( 348 | 'data', 349 | this.buffer.toString('utf-8', 0, this.bufferIndex) 350 | ); 351 | this.buffer.write(data, 0); 352 | this.bufferIndex = bytes; 353 | } else { 354 | // we can't empty out the buffer, so let's wait till we resume before adding to it 355 | this.once('resume', () => output(data)); 356 | } 357 | } 358 | } 359 | }; 360 | 361 | /** 362 | * @param {MessageAttachment} [attachment] the attachment whose headers you would like to output 363 | * @returns {void} 364 | */ 365 | const outputAttachmentHeaders = (attachment: MessageAttachment) => { 366 | let data: string[] = []; 367 | const headers: Partial = { 368 | 'content-type': 369 | attachment.type + 370 | (attachment.charset ? `; charset=${attachment.charset}` : '') + 371 | (attachment.method ? `; method=${attachment.method}` : ''), 372 | 'content-transfer-encoding': 'base64', 373 | 'content-disposition': attachment.inline 374 | ? 'inline' 375 | : `attachment; filename="${mimeWordEncode( 376 | attachment.name as string 377 | )}"`, 378 | }; 379 | 380 | // allow sender to override default headers 381 | if (attachment.headers != null) { 382 | for (const header in attachment.headers) { 383 | headers[header.toLowerCase()] = attachment.headers[header]; 384 | } 385 | } 386 | 387 | for (const header in headers) { 388 | data = data.concat([ 389 | convertDashDelimitedTextToSnakeCase(header), 390 | ': ', 391 | headers[header] as string, 392 | CRLF, 393 | ]); 394 | } 395 | 396 | output(data.concat([CRLF]).join('')); 397 | }; 398 | 399 | /** 400 | * @param {string} data the data to output as base64 401 | * @param {function(): void} [callback] the function to call after output is finished 402 | * @returns {void} 403 | */ 404 | const outputBase64 = (data: string, callback?: () => void) => { 405 | const loops = Math.ceil(data.length / MIMECHUNK); 406 | let loop = 0; 407 | while (loop < loops) { 408 | output(data.substring(MIMECHUNK * loop, MIMECHUNK * (loop + 1)) + CRLF); 409 | loop++; 410 | } 411 | if (callback) { 412 | callback(); 413 | } 414 | }; 415 | 416 | const outputFile = ( 417 | attachment: MessageAttachment, 418 | next: (err: NodeJS.ErrnoException | null) => void 419 | ) => { 420 | const chunk = MIME64CHUNK * 16; 421 | const buffer = Buffer.alloc(chunk); 422 | 423 | const inputEncoding = 424 | attachment?.headers?.['content-transfer-encoding'] || 'base64'; 425 | const encoding = 426 | inputEncoding === '7bit' 427 | ? 'ascii' 428 | : inputEncoding === '8bit' 429 | ? 'binary' 430 | : inputEncoding; 431 | 432 | /** 433 | * @param {Error} err the error to emit 434 | * @param {number} fd the file descriptor 435 | * @returns {void} 436 | */ 437 | const opened = (err: NodeJS.ErrnoException | null, fd: number) => { 438 | if (err) { 439 | this.emit('error', err); 440 | return; 441 | } 442 | const readBytes = ( 443 | err: NodeJS.ErrnoException | null, 444 | bytes: number 445 | ) => { 446 | if (err || this.readable === false) { 447 | this.emit( 448 | 'error', 449 | err || new Error('message stream was interrupted somehow!') 450 | ); 451 | return; 452 | } 453 | // guaranteed to be encoded without padding unless it is our last read 454 | outputBase64(buffer.toString(encoding, 0, bytes), () => { 455 | if (bytes == chunk) { 456 | // we read a full chunk, there might be more 457 | readFile(fd, buffer, 0, chunk, null, readBytes); 458 | } // that was the last chunk, we are done reading the file 459 | else { 460 | this.removeListener('error', closeFileSync); 461 | closeFile(fd, next); 462 | } 463 | }); 464 | }; 465 | readFile(fd, buffer, 0, chunk, null, readBytes); 466 | this.once('error', closeFileSync); 467 | }; 468 | 469 | openFile(attachment.path as PathLike, 'r', opened); 470 | }; 471 | 472 | /** 473 | * @param {MessageAttachment} attachment the metadata to use as headers 474 | * @param {function(): void} callback the function to call after output is finished 475 | * @returns {void} 476 | */ 477 | const outputStream = ( 478 | attachment: MessageAttachment, 479 | callback: () => void 480 | ) => { 481 | const { stream } = attachment; 482 | if (stream?.readable) { 483 | let previous = Buffer.alloc(0); 484 | 485 | stream.resume(); 486 | 487 | stream.on('end', () => { 488 | outputBase64(previous.toString('base64'), callback); 489 | this.removeListener('pause', stream.pause); 490 | this.removeListener('resume', stream.resume); 491 | this.removeListener('error', stream.resume); 492 | }); 493 | 494 | stream.on('data', (buff) => { 495 | // do we have bytes from a previous stream data event? 496 | let buffer = Buffer.isBuffer(buff) ? buff : Buffer.from(buff); 497 | 498 | if (previous.byteLength > 0) { 499 | buffer = Buffer.concat([previous, buffer]); 500 | } 501 | 502 | const padded = buffer.length % MIME64CHUNK; 503 | previous = Buffer.alloc(padded); 504 | 505 | // encode as much of the buffer to base64 without empty bytes 506 | if (padded > 0) { 507 | // copy dangling bytes into previous buffer 508 | buffer.copy(previous, 0, buffer.length - padded); 509 | } 510 | outputBase64(buffer.toString('base64', 0, buffer.length - padded)); 511 | }); 512 | 513 | this.on('pause', stream.pause); 514 | this.on('resume', stream.resume); 515 | this.on('error', stream.resume); 516 | } else { 517 | this.emit('error', { message: 'stream not readable' }); 518 | } 519 | }; 520 | 521 | const outputAttachment = ( 522 | attachment: MessageAttachment, 523 | callback: () => void 524 | ) => { 525 | const build = attachment.path 526 | ? outputFile 527 | : attachment.stream 528 | ? outputStream 529 | : outputData; 530 | outputAttachmentHeaders(attachment); 531 | build(attachment, callback); 532 | }; 533 | 534 | /** 535 | * @param {string} boundary the boundary text between outputs 536 | * @param {MessageAttachment[]} list the list of potential messages to output 537 | * @param {number} index the index of the list item to output 538 | * @param {function(): void} callback the function to call if index is greater than upper bound 539 | * @returns {void} 540 | */ 541 | const outputMessage = ( 542 | boundary: string, 543 | list: MessageAttachment[], 544 | index: number, 545 | callback: () => void 546 | ) => { 547 | if (index < list.length) { 548 | output(`--${boundary}${CRLF}`); 549 | if (list[index].related) { 550 | outputRelated(list[index], () => 551 | outputMessage(boundary, list, index + 1, callback) 552 | ); 553 | } else { 554 | outputAttachment(list[index], () => 555 | outputMessage(boundary, list, index + 1, callback) 556 | ); 557 | } 558 | } else { 559 | output(`${CRLF}--${boundary}--${CRLF}${CRLF}`); 560 | callback(); 561 | } 562 | }; 563 | 564 | const outputMixed = () => { 565 | const boundary = generateBoundary(); 566 | output( 567 | `Content-Type: multipart/mixed; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}` 568 | ); 569 | 570 | if (this.message.alternative == null) { 571 | outputText(this.message); 572 | outputMessage(boundary, this.message.attachments, 0, close); 573 | } else { 574 | outputAlternative( 575 | // typescript bug; should narrow to { alternative: MessageAttachment } 576 | this.message as Parameters[0], 577 | () => outputMessage(boundary, this.message.attachments, 0, close) 578 | ); 579 | } 580 | }; 581 | 582 | /** 583 | * @param {MessageAttachment} attachment the metadata to use as headers 584 | * @param {function(): void} callback the function to call after output is finished 585 | * @returns {void} 586 | */ 587 | const outputData = ( 588 | attachment: MessageAttachment, 589 | callback: () => void 590 | ) => { 591 | outputBase64( 592 | attachment.encoded 593 | ? attachment.data ?? '' 594 | : Buffer.from(attachment.data ?? '').toString('base64'), 595 | callback 596 | ); 597 | }; 598 | 599 | /** 600 | * @param {Message} message the message to output 601 | * @returns {void} 602 | */ 603 | const outputText = (message: Message) => { 604 | let data: string[] = []; 605 | 606 | data = data.concat([ 607 | 'Content-Type:', 608 | message.content, 609 | CRLF, 610 | 'Content-Transfer-Encoding: 7bit', 611 | CRLF, 612 | ]); 613 | data = data.concat(['Content-Disposition: inline', CRLF, CRLF]); 614 | data = data.concat([message.text || '', CRLF, CRLF]); 615 | 616 | output(data.join('')); 617 | }; 618 | 619 | /** 620 | * @param {MessageAttachment} message the message to output 621 | * @param {function(): void} callback the function to call after output is finished 622 | * @returns {void} 623 | */ 624 | const outputRelated = ( 625 | message: MessageAttachment, 626 | callback: () => void 627 | ) => { 628 | const boundary = generateBoundary(); 629 | output( 630 | `Content-Type: multipart/related; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}` 631 | ); 632 | outputAttachment(message, () => { 633 | outputMessage(boundary, message.related ?? [], 0, () => { 634 | output(`${CRLF}--${boundary}--${CRLF}${CRLF}`); 635 | callback(); 636 | }); 637 | }); 638 | }; 639 | 640 | /** 641 | * @param {Message} message the message to output 642 | * @param {function(): void} callback the function to call after output is finished 643 | * @returns {void} 644 | */ 645 | const outputAlternative = ( 646 | message: Message & { alternative: MessageAttachment }, 647 | callback: () => void 648 | ) => { 649 | const boundary = generateBoundary(); 650 | output( 651 | `Content-Type: multipart/alternative; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}` 652 | ); 653 | outputText(message); 654 | output(`--${boundary}${CRLF}`); 655 | 656 | /** 657 | * @returns {void} 658 | */ 659 | const finish = () => { 660 | output([CRLF, '--', boundary, '--', CRLF, CRLF].join('')); 661 | callback(); 662 | }; 663 | 664 | if (message.alternative.related) { 665 | outputRelated(message.alternative, finish); 666 | } else { 667 | outputAttachment(message.alternative, finish); 668 | } 669 | }; 670 | 671 | const close = (err?: Error) => { 672 | if (err) { 673 | this.emit('error', err); 674 | } else { 675 | this.emit( 676 | 'data', 677 | this.buffer?.toString('utf-8', 0, this.bufferIndex) ?? '' 678 | ); 679 | this.emit('end'); 680 | } 681 | this.buffer = null; 682 | this.bufferIndex = 0; 683 | this.readable = false; 684 | this.removeAllListeners('resume'); 685 | this.removeAllListeners('pause'); 686 | this.removeAllListeners('error'); 687 | this.removeAllListeners('data'); 688 | this.removeAllListeners('end'); 689 | }; 690 | 691 | /** 692 | * @returns {void} 693 | */ 694 | const outputHeaderData = () => { 695 | if (this.message.attachments.length || this.message.alternative) { 696 | output(`MIME-Version: 1.0${CRLF}`); 697 | outputMixed(); 698 | } // you only have a text message! 699 | else { 700 | outputText(this.message); 701 | close(); 702 | } 703 | }; 704 | 705 | /** 706 | * @returns {void} 707 | */ 708 | const outputHeader = () => { 709 | let data: string[] = []; 710 | 711 | for (const header in this.message.header) { 712 | // do not output BCC in the headers (regex) nor custom Object.prototype functions... 713 | if ( 714 | !/bcc/i.test(header) && 715 | Object.prototype.hasOwnProperty.call(this.message.header, header) 716 | ) { 717 | data = data.concat([ 718 | convertDashDelimitedTextToSnakeCase(header), 719 | ': ', 720 | this.message.header[header] as string, 721 | CRLF, 722 | ]); 723 | } 724 | } 725 | 726 | output(data.join('')); 727 | outputHeaderData(); 728 | }; 729 | 730 | this.once('destroy', close); 731 | process.nextTick(outputHeader); 732 | } 733 | 734 | /** 735 | * @public 736 | * pause the stream 737 | * @returns {void} 738 | */ 739 | public pause() { 740 | this.paused = true; 741 | this.emit('pause'); 742 | } 743 | 744 | /** 745 | * @public 746 | * resume the stream 747 | * @returns {void} 748 | */ 749 | public resume() { 750 | this.paused = false; 751 | this.emit('resume'); 752 | } 753 | 754 | /** 755 | * @public 756 | * destroy the stream 757 | * @returns {void} 758 | */ 759 | public destroy() { 760 | this.emit( 761 | 'destroy', 762 | this.bufferIndex > 0 ? { message: 'message stream destroyed' } : null 763 | ); 764 | } 765 | 766 | /** 767 | * @public 768 | * destroy the stream at first opportunity 769 | * @returns {void} 770 | */ 771 | public destroySoon() { 772 | this.emit('destroy'); 773 | } 774 | } 775 | -------------------------------------------------------------------------------- /smtp/mime.ts: -------------------------------------------------------------------------------- 1 | // adapted from https://github.com/emailjs/emailjs-mime-codec/blob/6909c706b9f09bc0e5c3faf48f723cca53e5b352/src/mimecodec.js 2 | import { TextDecoder, TextEncoder } from 'util'; 3 | 4 | const encoder = new TextEncoder(); 5 | 6 | /** 7 | * @see https://tools.ietf.org/html/rfc2045#section-6.7 8 | */ 9 | const RANGES = [ 10 | [0x09], // 11 | [0x0a], // 12 | [0x0d], // 13 | [0x20, 0x3c], // !"#$%&'()*+,-./0123456789:; 14 | [0x3e, 0x7e], // >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} 15 | ]; 16 | const LOOKUP = 17 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); 18 | const MAX_CHUNK_LENGTH = 16383; // must be multiple of 3 19 | const MAX_MIME_WORD_LENGTH = 52; 20 | const MAX_B64_MIME_WORD_BYTE_LENGTH = 39; 21 | 22 | function tripletToBase64(num: number) { 23 | return ( 24 | LOOKUP[(num >> 18) & 0x3f] + 25 | LOOKUP[(num >> 12) & 0x3f] + 26 | LOOKUP[(num >> 6) & 0x3f] + 27 | LOOKUP[num & 0x3f] 28 | ); 29 | } 30 | 31 | function encodeChunk(uint8: Uint8Array, start: number, end: number) { 32 | let output = ''; 33 | for (let i = start; i < end; i += 3) { 34 | output += tripletToBase64( 35 | (uint8[i] << 16) + (uint8[i + 1] << 8) + uint8[i + 2] 36 | ); 37 | } 38 | return output; 39 | } 40 | 41 | function encodeBase64(data: Uint8Array) { 42 | const len = data.length; 43 | const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes 44 | let output = ''; 45 | 46 | // go through the array every three bytes, we'll deal with trailing stuff later 47 | for (let i = 0, len2 = len - extraBytes; i < len2; i += MAX_CHUNK_LENGTH) { 48 | output += encodeChunk( 49 | data, 50 | i, 51 | i + MAX_CHUNK_LENGTH > len2 ? len2 : i + MAX_CHUNK_LENGTH 52 | ); 53 | } 54 | 55 | // pad the end with zeros, but make sure to not forget the extra bytes 56 | if (extraBytes === 1) { 57 | const tmp = data[len - 1]; 58 | output += LOOKUP[tmp >> 2]; 59 | output += LOOKUP[(tmp << 4) & 0x3f]; 60 | output += '=='; 61 | } else if (extraBytes === 2) { 62 | const tmp = (data[len - 2] << 8) + data[len - 1]; 63 | output += LOOKUP[tmp >> 10]; 64 | output += LOOKUP[(tmp >> 4) & 0x3f]; 65 | output += LOOKUP[(tmp << 2) & 0x3f]; 66 | output += '='; 67 | } 68 | 69 | return output; 70 | } 71 | 72 | /** 73 | * Splits a mime encoded string. Needed for dividing mime words into smaller chunks 74 | * 75 | * @param {string} str Mime encoded string to be split up 76 | * @param {number} maxlen Maximum length of characters for one part (minimum 12) 77 | * @return {string[]} lines 78 | */ 79 | function splitMimeEncodedString(str: string, maxlen = 12) { 80 | const minWordLength = 12; // require at least 12 symbols to fit possible 4 octet UTF-8 sequences 81 | const maxWordLength = Math.max(maxlen, minWordLength); 82 | const lines: string[] = []; 83 | 84 | while (str.length) { 85 | let curLine = str.substr(0, maxWordLength); 86 | 87 | const match = curLine.match(/=[0-9A-F]?$/i); // skip incomplete escaped char 88 | if (match) { 89 | curLine = curLine.substr(0, match.index); 90 | } 91 | 92 | let done = false; 93 | while (!done) { 94 | let chr; 95 | done = true; 96 | const match = str.substr(curLine.length).match(/^=([0-9A-F]{2})/i); // check if not middle of a unicode char sequence 97 | if (match) { 98 | chr = parseInt(match[1], 16); 99 | // invalid sequence, move one char back anc recheck 100 | if (chr < 0xc2 && chr > 0x7f) { 101 | curLine = curLine.substr(0, curLine.length - 3); 102 | done = false; 103 | } 104 | } 105 | } 106 | 107 | if (curLine.length) { 108 | lines.push(curLine); 109 | } 110 | str = str.substr(curLine.length); 111 | } 112 | 113 | return lines; 114 | } 115 | 116 | /** 117 | * 118 | * @param {number} nr number 119 | * @returns {boolean} if number is in range 120 | */ 121 | function checkRanges(nr: number) { 122 | return RANGES.reduce( 123 | (val, range) => 124 | val || 125 | (range.length === 1 && nr === range[0]) || 126 | (range.length === 2 && nr >= range[0] && nr <= range[1]), 127 | false 128 | ); 129 | } 130 | 131 | /** 132 | * Encodes all non printable and non ascii bytes to =XX form, where XX is the 133 | * byte value in hex. This function does not convert linebreaks etc. it 134 | * only escapes character sequences 135 | * 136 | * NOTE: Encoding support depends on util.TextDecoder, which is severely limited 137 | * prior to Node.js 13. 138 | * 139 | * @see https://nodejs.org/api/util.html#util_whatwg_supported_encodings 140 | * @see https://github.com/nodejs/node/issues/19214 141 | * 142 | * @param {string|Uint8Array} data Either a string or an Uint8Array 143 | * @param {string} encoding WHATWG supported encoding 144 | * @return {string} Mime encoded string 145 | */ 146 | export function mimeEncode(data: string | Uint8Array = '', encoding = 'utf-8') { 147 | const decoder = new TextDecoder(encoding); 148 | const buffer = 149 | typeof data === 'string' 150 | ? encoder.encode(data) 151 | : encoder.encode(decoder.decode(data)); 152 | 153 | return buffer.reduce( 154 | (aggregate, ord, index) => 155 | checkRanges(ord) && 156 | !( 157 | (ord === 0x20 || ord === 0x09) && 158 | (index === buffer.length - 1 || 159 | buffer[index + 1] === 0x0a || 160 | buffer[index + 1] === 0x0d) 161 | ) 162 | ? // if the char is in allowed range, then keep as is, unless it is a ws in the end of a line 163 | aggregate + String.fromCharCode(ord) 164 | : `${aggregate}=${ord < 0x10 ? '0' : ''}${ord 165 | .toString(16) 166 | .toUpperCase()}`, 167 | '' 168 | ); 169 | } 170 | 171 | /** 172 | * Encodes a string or an Uint8Array to an UTF-8 MIME Word 173 | * 174 | * NOTE: Encoding support depends on util.TextDecoder, which is severely limited 175 | * prior to Node.js 13. 176 | * 177 | * @see https://tools.ietf.org/html/rfc2047 178 | * @see https://nodejs.org/api/util.html#util_whatwg_supported_encodings 179 | * @see https://github.com/nodejs/node/issues/19214 180 | * 181 | * @param {string|Uint8Array} data String to be encoded 182 | * @param {'Q' | 'B'} mimeWordEncoding='Q' Encoding for the mime word, either Q or B 183 | * @param {string} encoding WHATWG supported encoding 184 | * @return {string} Single or several mime words joined together 185 | */ 186 | export function mimeWordEncode( 187 | data: string | Uint8Array, 188 | mimeWordEncoding: 'Q' | 'B' = 'Q', 189 | encoding = 'utf-8' 190 | ) { 191 | let parts: string[] = []; 192 | const decoder = new TextDecoder(encoding); 193 | const str = typeof data === 'string' ? data : decoder.decode(data); 194 | 195 | if (mimeWordEncoding === 'Q') { 196 | const encodedStr = mimeEncode(str, encoding).replace( 197 | /[^a-z0-9!*+\-/=]/gi, 198 | (chr: string) => 199 | chr === ' ' 200 | ? '_' 201 | : '=' + 202 | (chr.charCodeAt(0) < 0x10 ? '0' : '') + 203 | chr.charCodeAt(0).toString(16).toUpperCase() 204 | ); 205 | parts = 206 | encodedStr.length < MAX_MIME_WORD_LENGTH 207 | ? [encodedStr] 208 | : splitMimeEncodedString(encodedStr, MAX_MIME_WORD_LENGTH); 209 | } else { 210 | // Fits as much as possible into every line without breaking utf-8 multibyte characters' octets up across lines 211 | let j = 0; 212 | let i = 0; 213 | while (i < str.length) { 214 | if ( 215 | encoder.encode(str.substring(j, i)).length > 216 | MAX_B64_MIME_WORD_BYTE_LENGTH 217 | ) { 218 | // we went one character too far, substring at the char before 219 | parts.push(str.substring(j, i - 1)); 220 | j = i - 1; 221 | } else { 222 | i++; 223 | } 224 | } 225 | // add the remainder of the string 226 | str.substring(j) && parts.push(str.substring(j)); 227 | parts = parts.map((x) => encoder.encode(x)).map((x) => encodeBase64(x)); 228 | } 229 | 230 | return parts 231 | .map((p) => `=?UTF-8?${mimeWordEncoding}?${p}?= `) 232 | .join('') 233 | .trim(); 234 | } 235 | -------------------------------------------------------------------------------- /smtp/response.ts: -------------------------------------------------------------------------------- 1 | import { SMTPError, SMTPErrorStates } from './error.js'; 2 | import type { Socket } from 'net'; 3 | import type { TLSSocket } from 'tls'; 4 | 5 | export class SMTPResponseMonitor { 6 | public readonly stop: (err?: Error) => void; 7 | 8 | constructor( 9 | stream: Socket | TLSSocket, 10 | timeout: number, 11 | onerror: (err: Error) => void 12 | ) { 13 | let buffer = ''; 14 | 15 | const notify = () => { 16 | if (buffer.length) { 17 | // parse buffer for response codes 18 | const line = buffer.replace('\r', ''); 19 | if ( 20 | !( 21 | line 22 | .trim() 23 | .split(/\n/) 24 | .pop() 25 | ?.match(/^(\d{3})\s/) ?? false 26 | ) 27 | ) { 28 | return; 29 | } 30 | 31 | const match = line ? line.match(/(\d+)\s?(.*)/) : null; 32 | const data = 33 | match !== null 34 | ? { code: match[1], message: match[2], data: line } 35 | : { code: -1, data: line }; 36 | 37 | stream.emit('response', null, data); 38 | buffer = ''; 39 | } 40 | }; 41 | 42 | const error = (err: Error) => { 43 | stream.emit( 44 | 'response', 45 | SMTPError.create( 46 | 'connection encountered an error', 47 | SMTPErrorStates.ERROR, 48 | err 49 | ) 50 | ); 51 | }; 52 | 53 | const timedout = (err?: Error) => { 54 | stream.end(); 55 | stream.emit( 56 | 'response', 57 | SMTPError.create( 58 | 'timedout while connecting to smtp server', 59 | SMTPErrorStates.TIMEDOUT, 60 | err 61 | ) 62 | ); 63 | }; 64 | 65 | const watch = (data: string | Buffer) => { 66 | if (data !== null) { 67 | buffer += data.toString(); 68 | notify(); 69 | } 70 | }; 71 | 72 | const close = (err: Error) => { 73 | stream.emit( 74 | 'response', 75 | SMTPError.create( 76 | 'connection has closed', 77 | SMTPErrorStates.CONNECTIONCLOSED, 78 | err 79 | ) 80 | ); 81 | }; 82 | 83 | const end = (err: Error) => { 84 | stream.emit( 85 | 'response', 86 | SMTPError.create( 87 | 'connection has ended', 88 | SMTPErrorStates.CONNECTIONENDED, 89 | err 90 | ) 91 | ); 92 | }; 93 | 94 | this.stop = (err) => { 95 | stream.removeAllListeners('response'); 96 | stream.removeListener('data', watch); 97 | stream.removeListener('end', end); 98 | stream.removeListener('close', close); 99 | stream.removeListener('error', error); 100 | 101 | if (err != null && typeof onerror === 'function') { 102 | onerror(err); 103 | } 104 | }; 105 | 106 | stream.on('data', watch); 107 | stream.on('end', end); 108 | stream.on('close', close); 109 | stream.on('error', error); 110 | stream.setTimeout(timeout, timedout); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /test/address.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { addressparser } from '../email.js'; 3 | 4 | test('addressparser should handle single address correctly', async (t) => { 5 | t.deepEqual(addressparser('andris@tr.ee'), [ 6 | { address: 'andris@tr.ee', name: '' }, 7 | ]); 8 | }); 9 | 10 | test('addressparser should handle multiple addresses correctly', async (t) => { 11 | t.deepEqual(addressparser('andris@tr.ee, andris@example.com'), [ 12 | { address: 'andris@tr.ee', name: '' }, 13 | { address: 'andris@example.com', name: '' }, 14 | ]); 15 | }); 16 | 17 | test('addressparser should handle unquoted name correctly', async (t) => { 18 | t.deepEqual(addressparser('andris '), [ 19 | { name: 'andris', address: 'andris@tr.ee' }, 20 | ]); 21 | }); 22 | 23 | test('addressparser should handle quoted name correctly', async (t) => { 24 | t.deepEqual(addressparser('"reinman, andris" '), [ 25 | { name: 'reinman, andris', address: 'andris@tr.ee' }, 26 | ]); 27 | }); 28 | 29 | test('addressparser should handle quoted semicolons correctly', async (t) => { 30 | t.deepEqual(addressparser('"reinman; andris" '), [ 31 | { name: 'reinman; andris', address: 'andris@tr.ee' }, 32 | ]); 33 | }); 34 | 35 | test('addressparser should handle unquoted name, unquoted address correctly', async (t) => { 36 | t.deepEqual(addressparser('andris andris@tr.ee'), [ 37 | { name: 'andris', address: 'andris@tr.ee' }, 38 | ]); 39 | }); 40 | 41 | test('addressparser should handle empty group correctly', async (t) => { 42 | t.deepEqual(addressparser('Undisclosed:;'), [ 43 | { name: 'Undisclosed', group: [] }, 44 | ]); 45 | }); 46 | 47 | test('addressparser should handle address group correctly', async (t) => { 48 | t.deepEqual(addressparser('Disclosed:andris@tr.ee, andris@example.com;'), [ 49 | { 50 | name: 'Disclosed', 51 | group: [ 52 | { address: 'andris@tr.ee', name: '' }, 53 | { address: 'andris@example.com', name: '' }, 54 | ], 55 | }, 56 | ]); 57 | }); 58 | 59 | test('addressparser should handle semicolon as a delimiter', async (t) => { 60 | t.deepEqual(addressparser('andris@tr.ee; andris@example.com;'), [ 61 | { address: 'andris@tr.ee', name: '' }, 62 | { address: 'andris@example.com', name: '' }, 63 | ]); 64 | }); 65 | 66 | test('addressparser should handle mixed group correctly', async (t) => { 67 | t.deepEqual( 68 | addressparser( 69 | 'Test User , Disclosed:andris@tr.ee, andris@example.com;,,,, Undisclosed:;' 70 | ), 71 | [ 72 | { address: 'test.user@mail.ee', name: 'Test User' }, 73 | { 74 | name: 'Disclosed', 75 | group: [ 76 | { address: 'andris@tr.ee', name: '' }, 77 | { address: 'andris@example.com', name: '' }, 78 | ], 79 | }, 80 | { name: 'Undisclosed', group: [] }, 81 | ] 82 | ); 83 | }); 84 | 85 | test('addressparser semicolon as delimiter should not break group parsing ', async (t) => { 86 | t.deepEqual( 87 | addressparser( 88 | 'Test User ; Disclosed:andris@tr.ee, andris@example.com;,,,, Undisclosed:; bob@example.com;' 89 | ), 90 | [ 91 | { address: 'test.user@mail.ee', name: 'Test User' }, 92 | { 93 | name: 'Disclosed', 94 | group: [ 95 | { 96 | address: 'andris@tr.ee', 97 | name: '', 98 | }, 99 | { 100 | address: 'andris@example.com', 101 | name: '', 102 | }, 103 | ], 104 | }, 105 | { name: 'Undisclosed', group: [] }, 106 | { address: 'bob@example.com', name: '' }, 107 | ] 108 | ); 109 | }); 110 | 111 | test('addressparser should handle name from comment correctly', async (t) => { 112 | t.deepEqual(addressparser('andris@tr.ee (andris)'), [ 113 | { name: 'andris', address: 'andris@tr.ee' }, 114 | ]); 115 | }); 116 | 117 | test('addressparser should handle skip comment correctly', async (t) => { 118 | t.deepEqual(addressparser('andris@tr.ee (reinman) andris'), [ 119 | { name: 'andris', address: 'andris@tr.ee' }, 120 | ]); 121 | }); 122 | 123 | test('addressparser should handle missing address correctly', async (t) => { 124 | t.deepEqual(addressparser('andris'), [{ name: 'andris', address: '' }]); 125 | }); 126 | 127 | test('addressparser should handle apostrophe in name correctly', async (t) => { 128 | t.deepEqual(addressparser("O'Neill"), [{ name: "O'Neill", address: '' }]); 129 | }); 130 | 131 | test('addressparser should handle particularly bad input, unescaped colon correctly', async (t) => { 132 | t.deepEqual( 133 | addressparser( 134 | 'FirstName Surname-WithADash :: Company ' 135 | ), 136 | [ 137 | { 138 | name: 'FirstName Surname-WithADash', 139 | group: [ 140 | { 141 | name: undefined, 142 | group: [{ address: 'firstname@company.com', name: 'Company' }], 143 | }, 144 | ], 145 | }, 146 | ] 147 | ); 148 | }); 149 | -------------------------------------------------------------------------------- /test/attachments/postfix-2.8.7.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleith/emailjs/fc175932f6bc7fd6d24668666b62e2ba00d2630c/test/attachments/postfix-2.8.7.tar.gz -------------------------------------------------------------------------------- /test/attachments/smtp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleith/emailjs/fc175932f6bc7fd6d24668666b62e2ba00d2630c/test/attachments/smtp.gif -------------------------------------------------------------------------------- /test/attachments/smtp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleith/emailjs/fc175932f6bc7fd6d24668666b62e2ba00d2630c/test/attachments/smtp.pdf -------------------------------------------------------------------------------- /test/attachments/smtp2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | this is how smtp works: 5 |
6 |
7 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /test/auth.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import type { ExecutionContext } from 'ava'; 3 | import { simpleParser } from 'mailparser'; 4 | import type { AddressObject } from 'mailparser'; 5 | import { SMTPServer } from 'smtp-server'; 6 | 7 | import { AUTH_METHODS, SMTPClient, Message } from '../email.js'; 8 | 9 | let port = 2000; 10 | 11 | function send( 12 | t: ExecutionContext, 13 | { 14 | authMethods = [], 15 | authOptional = false, 16 | secure = false, 17 | password = 'honey', 18 | }: { 19 | authMethods?: (keyof typeof AUTH_METHODS)[]; 20 | authOptional?: boolean; 21 | secure?: boolean; 22 | password?: string; 23 | } = {} 24 | ) { 25 | return new Promise((resolve, reject) => { 26 | const msg = { 27 | subject: 'this is a test TEXT message from emailjs', 28 | from: 'piglet@gmail.com', 29 | to: 'pooh@gmail.com', 30 | text: "It is hard to be brave when you're only a Very Small Animal.", 31 | }; 32 | const server = new SMTPServer({ 33 | authMethods, 34 | secure: secure, 35 | hideSTARTTLS: !secure, 36 | authOptional, 37 | onAuth(auth, _session, callback) { 38 | const { accessToken, method, username, password } = auth; 39 | if ( 40 | (method === AUTH_METHODS.XOAUTH2 && password != null 41 | ? accessToken === 'pooh' 42 | : username === 'pooh') && 43 | (method === AUTH_METHODS.XOAUTH2 && password == null 44 | ? accessToken === 'honey' 45 | : password === 'honey') 46 | ) { 47 | t.plan(5); 48 | callback(null, { user: 'pooh' }); 49 | } else { 50 | return callback( 51 | new Error( 52 | `invalid user or pass: ${username || accessToken} ${password}` 53 | ) 54 | ); 55 | } 56 | }, 57 | async onData(stream, _session, callback: () => void) { 58 | const mail = await simpleParser(stream, { 59 | skipHtmlToText: true, 60 | skipTextToHtml: true, 61 | skipImageLinks: true, 62 | } as Record); 63 | 64 | t.is(mail.text, msg.text + '\n\n\n'); 65 | t.is(mail.subject, msg.subject); 66 | t.is(mail.from?.text, msg.from); 67 | t.is((mail.to as AddressObject).text, msg.to); 68 | 69 | callback(); 70 | }, 71 | }); 72 | const p = port++; 73 | server.listen(p, () => { 74 | const options = Object.assign( 75 | { port: p, ssl: secure, authentication: authMethods }, 76 | authOptional ? {} : { user: 'pooh', password } 77 | ); 78 | new SMTPClient(options).send(new Message(msg), (err) => { 79 | server.close(() => { 80 | if (err) { 81 | reject(err); 82 | } else { 83 | resolve(); 84 | } 85 | }); 86 | }); 87 | }); 88 | }); 89 | } 90 | 91 | test('no authentication (unencrypted) should succeed', async (t) => { 92 | await t.notThrowsAsync(send(t, { authOptional: true })); 93 | }); 94 | 95 | test('no authentication (encrypted) should succeed', async (t) => { 96 | await t.notThrowsAsync(send(t, { authOptional: true, secure: true })); 97 | }); 98 | 99 | test('PLAIN authentication (unencrypted) should succeed', async (t) => { 100 | await t.notThrowsAsync(send(t, { authMethods: [AUTH_METHODS.PLAIN] })); 101 | }); 102 | 103 | test('PLAIN authentication (encrypted) should succeed', async (t) => { 104 | await t.notThrowsAsync( 105 | send(t, { authMethods: [AUTH_METHODS.PLAIN], secure: true }) 106 | ); 107 | }); 108 | 109 | test('LOGIN authentication (unencrypted) should succeed', async (t) => { 110 | await t.notThrowsAsync(send(t, { authMethods: [AUTH_METHODS.LOGIN] })); 111 | }); 112 | 113 | test('LOGIN authentication (encrypted) should succeed', async (t) => { 114 | await t.notThrowsAsync( 115 | send(t, { authMethods: [AUTH_METHODS.LOGIN], secure: true }) 116 | ); 117 | }); 118 | 119 | test('XOAUTH2 authentication (unencrypted) should succeed', async (t) => { 120 | await t.notThrowsAsync(send(t, { authMethods: [AUTH_METHODS.XOAUTH2] })); 121 | }); 122 | 123 | test('XOAUTH2 authentication (encrypted) should succeed', async (t) => { 124 | await t.notThrowsAsync( 125 | send(t, { authMethods: [AUTH_METHODS.XOAUTH2], secure: true }) 126 | ); 127 | }); 128 | 129 | test('on authentication.failed error message should not contain password', async (t) => { 130 | t.plan(1); 131 | 132 | const password = 'passpot'; 133 | await send(t, { 134 | authMethods: [AUTH_METHODS.LOGIN], 135 | secure: true, 136 | password, 137 | }).catch((err) => { 138 | t.false(err.message.includes(password)); 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /test/client.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from 'util'; 2 | 3 | import test from 'ava'; 4 | import { simpleParser } from 'mailparser'; 5 | import type { ParsedMail, AddressObject } from 'mailparser'; 6 | import { SMTPServer } from 'smtp-server'; 7 | 8 | import type { MessageHeaders } from '../email.js'; 9 | import { 10 | DEFAULT_TIMEOUT, 11 | SMTPClient, 12 | Message, 13 | isRFC2822Date, 14 | } from '../email.js'; 15 | 16 | const parseMap = new Map(); 17 | const port = 3333; 18 | let greylistPort = 4444; 19 | 20 | const client = new SMTPClient({ 21 | port, 22 | user: 'pooh', 23 | password: 'honey', 24 | ssl: true, 25 | }); 26 | const server = new SMTPServer({ 27 | secure: true, 28 | onAuth(auth, _session, callback) { 29 | if (auth.username === 'pooh' && auth.password === 'honey') { 30 | callback(null, { user: 'pooh' }); 31 | } else { 32 | return callback(new Error('invalid user / pass')); 33 | } 34 | }, 35 | async onData(stream, _session, callback: () => void) { 36 | const mail = await simpleParser(stream, { 37 | skipHtmlToText: true, 38 | skipTextToHtml: true, 39 | skipImageLinks: true, 40 | } as Record); 41 | 42 | parseMap.set(mail.subject as string, mail); 43 | callback(); 44 | }, 45 | }); 46 | 47 | async function send(headers: Partial) { 48 | return new Promise((resolve, reject) => { 49 | client.send(new Message(headers), (err) => { 50 | if (err) { 51 | reject(err); 52 | } else { 53 | resolve(parseMap.get(headers.subject as string) as ParsedMail); 54 | } 55 | }); 56 | }); 57 | } 58 | 59 | test.before(async (t) => { 60 | server.listen(port, t.pass); 61 | }); 62 | test.after(async (t) => { 63 | server.close(t.pass); 64 | }); 65 | 66 | test('client invokes callback exactly once for invalid connection', async (t) => { 67 | const msg = { 68 | from: 'foo@bar.baz', 69 | to: 'foo@bar.baz', 70 | subject: 'hello world', 71 | text: 'hello world', 72 | }; 73 | await t.notThrowsAsync( 74 | new Promise((resolve, reject) => { 75 | let counter = 0; 76 | const invalidClient = new SMTPClient({ host: 'localhost' }); 77 | const incrementCounter = () => { 78 | if (counter > 0) { 79 | reject(); 80 | } else { 81 | counter++; 82 | } 83 | }; 84 | invalidClient.send(new Message(msg), (err) => { 85 | if (err == null) { 86 | reject(); 87 | } else { 88 | incrementCounter(); 89 | } 90 | }); 91 | // @ts-expect-error the error event is only accessible from the protected socket property 92 | invalidClient.smtp.sock.once('error', () => { 93 | if (counter === 1) { 94 | resolve(); 95 | } else { 96 | reject(); 97 | } 98 | }); 99 | }) 100 | ); 101 | }); 102 | 103 | test('client has a default connection timeout', async (t) => { 104 | const connectionOptions = { 105 | user: 'username', 106 | password: 'password', 107 | host: '127.0.0.1', 108 | port: 1234, 109 | timeout: undefined as number | null | undefined, 110 | }; 111 | t.is(new SMTPClient(connectionOptions).smtp.timeout, DEFAULT_TIMEOUT); 112 | 113 | connectionOptions.timeout = null; 114 | t.is(new SMTPClient(connectionOptions).smtp.timeout, DEFAULT_TIMEOUT); 115 | 116 | connectionOptions.timeout = undefined; 117 | t.is(new SMTPClient(connectionOptions).smtp.timeout, DEFAULT_TIMEOUT); 118 | }); 119 | 120 | test('client deduplicates recipients', async (t) => { 121 | const msg = { 122 | from: 'zelda@gmail.com', 123 | to: 'gannon@gmail.com', 124 | cc: 'gannon@gmail.com', 125 | bcc: 'gannon@gmail.com', 126 | }; 127 | const stack = client.createMessageStack(new Message(msg)); 128 | t.true(stack.to.length === 1); 129 | t.is(stack.to[0].address, 'gannon@gmail.com'); 130 | }); 131 | 132 | test('client accepts array recipients', async (t) => { 133 | const msg = new Message({ 134 | from: 'zelda@gmail.com', 135 | to: ['gannon1@gmail.com'], 136 | cc: ['gannon2@gmail.com'], 137 | bcc: ['gannon3@gmail.com'], 138 | }); 139 | 140 | msg.header.to = [msg.header.to as string]; 141 | msg.header.cc = [msg.header.cc as string]; 142 | msg.header.bcc = [msg.header.bcc as string]; 143 | 144 | const { isValid } = msg.checkValidity(); 145 | const stack = client.createMessageStack(msg); 146 | 147 | t.true(isValid); 148 | t.is(stack.to.length, 3); 149 | t.deepEqual( 150 | stack.to.map((x) => x.address), 151 | ['gannon1@gmail.com', 'gannon2@gmail.com', 'gannon3@gmail.com'] 152 | ); 153 | }); 154 | 155 | test('client accepts array sender', async (t) => { 156 | const msg = new Message({ 157 | from: ['zelda@gmail.com'], 158 | to: ['gannon1@gmail.com'], 159 | }); 160 | msg.header.from = [msg.header.from as string]; 161 | 162 | const { isValid } = msg.checkValidity(); 163 | t.true(isValid); 164 | }); 165 | 166 | test('client rejects message without `from` header', async (t) => { 167 | const error = await t.throwsAsync( 168 | send({ 169 | subject: 'this is a test TEXT message from emailjs', 170 | text: "It is hard to be brave when you're only a Very Small Animal.", 171 | }) 172 | ); 173 | t.is(error?.message, 'Message must have a `from` header'); 174 | }); 175 | 176 | test('client rejects message without `to`, `cc`, or `bcc` header', async (t) => { 177 | const error = await t.throwsAsync( 178 | send({ 179 | subject: 'this is a test TEXT message from emailjs', 180 | from: 'piglet@gmail.com', 181 | text: "It is hard to be brave when you're only a Very Small Animal.", 182 | }) 183 | ); 184 | t.is( 185 | error?.message, 186 | 'Message must have at least one `to`, `cc`, or `bcc` header' 187 | ); 188 | }); 189 | 190 | test('client allows message with only `cc` recipient header', async (t) => { 191 | const msg = { 192 | subject: 'this is a test TEXT message from emailjs', 193 | from: 'piglet@gmail.com', 194 | cc: 'pooh@gmail.com', 195 | text: "It is hard to be brave when you're only a Very Small Animal.", 196 | }; 197 | 198 | const mail = await send(msg); 199 | t.is(mail.text, msg.text + '\n\n\n'); 200 | t.is(mail.subject, msg.subject); 201 | t.is(mail.from?.text, msg.from); 202 | t.is((mail.cc as AddressObject).text, msg.cc); 203 | }); 204 | 205 | test('client allows message with only `bcc` recipient header', async (t) => { 206 | const msg = { 207 | subject: 'this is a test TEXT message from emailjs', 208 | from: 'piglet@gmail.com', 209 | bcc: 'pooh@gmail.com', 210 | text: "It is hard to be brave when you're only a Very Small Animal.", 211 | }; 212 | 213 | const mail = await send(msg); 214 | t.is(mail.text, msg.text + '\n\n\n'); 215 | t.is(mail.subject, msg.subject); 216 | t.is(mail.from?.text, msg.from); 217 | t.is(mail.bcc, undefined); 218 | }); 219 | 220 | test('client constructor throws if `password` supplied without `user`', async (t) => { 221 | t.notThrows(() => new SMTPClient({ user: 'anything', password: 'anything' })); 222 | t.throws(() => new SMTPClient({ password: 'anything' })); 223 | t.throws( 224 | () => 225 | new SMTPClient({ username: 'anything', password: 'anything' } as Record< 226 | string, 227 | unknown 228 | >) 229 | ); 230 | }); 231 | 232 | test('client supports greylisting', async (t) => { 233 | t.plan(3); 234 | 235 | const msg = { 236 | subject: 'this is a test TEXT message from emailjs', 237 | from: 'piglet@gmail.com', 238 | bcc: 'pooh@gmail.com', 239 | text: "It is hard to be brave when you're only a Very Small Animal.", 240 | }; 241 | 242 | const greylistServer = new SMTPServer({ 243 | secure: true, 244 | onRcptTo(_address, _session, callback) { 245 | t.pass(); 246 | callback(); 247 | }, 248 | onAuth(auth, _session, callback) { 249 | if (auth.username === 'pooh' && auth.password === 'honey') { 250 | callback(null, { user: 'pooh' }); 251 | } else { 252 | return callback(new Error('invalid user / pass')); 253 | } 254 | }, 255 | }); 256 | 257 | const { onRcptTo } = greylistServer; 258 | greylistServer.onRcptTo = (_address, _session, callback) => { 259 | greylistServer.onRcptTo = (a, s, cb) => { 260 | t.pass(); 261 | const err = new Error('greylist'); 262 | (err as never as { responseCode: number }).responseCode = 450; 263 | greylistServer.onRcptTo = onRcptTo; 264 | onRcptTo(a, s, cb); 265 | }; 266 | 267 | const err = new Error('greylist'); 268 | (err as never as { responseCode: number }).responseCode = 450; 269 | callback(err); 270 | }; 271 | 272 | const p = greylistPort++; 273 | await t.notThrowsAsync( 274 | new Promise((resolve, reject) => { 275 | greylistServer.listen(p, () => { 276 | new SMTPClient({ 277 | port: p, 278 | user: 'pooh', 279 | password: 'honey', 280 | ssl: true, 281 | }).send(new Message(msg), (err) => { 282 | greylistServer.close(); 283 | if (err) { 284 | reject(err); 285 | } else { 286 | resolve(); 287 | } 288 | }); 289 | }); 290 | }) 291 | ); 292 | }); 293 | 294 | test('client only responds once to greylisting', async (t) => { 295 | t.plan(4); 296 | 297 | const msg = { 298 | subject: 'this is a test TEXT message from emailjs', 299 | from: 'piglet@gmail.com', 300 | bcc: 'pooh@gmail.com', 301 | text: "It is hard to be brave when you're only a Very Small Animal.", 302 | }; 303 | 304 | const greylistServer = new SMTPServer({ 305 | secure: true, 306 | onRcptTo(_address, _session, callback) { 307 | t.pass(); 308 | const err = new Error('greylist'); 309 | (err as never as { responseCode: number }).responseCode = 450; 310 | callback(err); 311 | }, 312 | onAuth(auth, _session, callback) { 313 | if (auth.username === 'pooh' && auth.password === 'honey') { 314 | callback(null, { user: 'pooh' }); 315 | } else { 316 | return callback(new Error('invalid user / pass')); 317 | } 318 | }, 319 | }); 320 | 321 | const p = greylistPort++; 322 | const error = await t.throwsAsync( 323 | new Promise((resolve, reject) => { 324 | greylistServer.listen(p, () => { 325 | new SMTPClient({ 326 | port: p, 327 | user: 'pooh', 328 | password: 'honey', 329 | ssl: true, 330 | }).send(new Message(msg), (err) => { 331 | greylistServer.close(); 332 | if (err) { 333 | reject(err); 334 | } else { 335 | resolve(); 336 | } 337 | }); 338 | }); 339 | }) 340 | ); 341 | t.is(error?.message, "bad response on command 'RCPT': greylist"); 342 | }); 343 | 344 | test('client send can have result awaited when promisified', async (t) => { 345 | // bind necessary to retain internal access to client prototype 346 | const sendAsync = promisify(client.send.bind(client)); 347 | 348 | const msg = { 349 | subject: 'this is a test TEXT message from emailjs', 350 | from: 'piglet@gmail.com', 351 | bcc: 'pooh@gmail.com', 352 | text: "It is hard to be brave when you're only a Very Small Animal.", 353 | }; 354 | 355 | try { 356 | const message = (await sendAsync(new Message(msg))) as Message; 357 | t.true(message instanceof Message); 358 | t.like(message, { 359 | alternative: null, 360 | content: 'text/plain; charset=utf-8', 361 | text: "It is hard to be brave when you're only a Very Small Animal.", 362 | header: { 363 | bcc: 'pooh@gmail.com', 364 | from: 'piglet@gmail.com', 365 | subject: '=?UTF-8?Q?this_is_a_test_TEXT_message_from_emailjs?=', 366 | }, 367 | }); 368 | t.deepEqual(message.attachments, []); 369 | t.true(isRFC2822Date(message.header.date as string)); 370 | t.regex(message.header['message-id'] as string, /^<.*[@]{1}.*>$/); 371 | } catch (err) { 372 | if (err instanceof Error) { 373 | t.fail(err.message); 374 | } else if (typeof err === 'string') { 375 | t.fail(err); 376 | } else { 377 | t.fail(); 378 | } 379 | } 380 | }); 381 | 382 | test('client sendAsync can have result awaited', async (t) => { 383 | const msg = { 384 | subject: 'this is a test TEXT message from emailjs', 385 | from: 'piglet@gmail.com', 386 | bcc: 'pooh@gmail.com', 387 | text: "It is hard to be brave when you're only a Very Small Animal.", 388 | }; 389 | 390 | try { 391 | const message = await client.sendAsync(new Message(msg)); 392 | t.true(message instanceof Message); 393 | t.like(message, { 394 | alternative: null, 395 | content: 'text/plain; charset=utf-8', 396 | text: "It is hard to be brave when you're only a Very Small Animal.", 397 | header: { 398 | bcc: 'pooh@gmail.com', 399 | from: 'piglet@gmail.com', 400 | subject: '=?UTF-8?Q?this_is_a_test_TEXT_message_from_emailjs?=', 401 | }, 402 | }); 403 | t.deepEqual(message.attachments, []); 404 | t.true(isRFC2822Date(message.header.date as string)); 405 | t.regex(message.header['message-id'] as string, /^<.*[@]{1}.*>$/); 406 | } catch (err) { 407 | if (err instanceof Error) { 408 | t.fail(err.message); 409 | } else if (typeof err === 'string') { 410 | t.fail(err); 411 | } else { 412 | t.fail(); 413 | } 414 | } 415 | }); 416 | 417 | test('client sendAsync can have error caught when awaited', async (t) => { 418 | const msg = { 419 | subject: 'this is a test TEXT message from emailjs', 420 | from: 'piglet@gmail.com', 421 | bcc: 'pooh@gmail.com', 422 | text: "It is hard to be brave when you're only a Very Small Animal.", 423 | }; 424 | 425 | try { 426 | const invalidClient = new SMTPClient({ host: '127.0.0.1' }); 427 | const message = await invalidClient.sendAsync(new Message(msg)); 428 | t.true(message instanceof Message); 429 | t.fail(); 430 | } catch (err) { 431 | t.true(err instanceof Error); 432 | t.pass(); 433 | } 434 | }); 435 | -------------------------------------------------------------------------------- /test/connection.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { SMTPConnection } from '../email.js'; 4 | 5 | test('accepts a custom logger', async (t) => { 6 | const logger = () => { 7 | /** ø */ 8 | }; 9 | const connection = new SMTPConnection({ logger }); 10 | t.is(Reflect.get(connection, 'log'), logger); 11 | }); 12 | -------------------------------------------------------------------------------- /test/date.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { getRFC2822Date, getRFC2822DateUTC, isRFC2822Date } from '../email.js'; 3 | 4 | const toD_utc = (dt: number) => getRFC2822DateUTC(new Date(dt)); 5 | const toD = (dt: number, utc = false) => getRFC2822Date(new Date(dt), utc); 6 | 7 | test('rfc2822 non-UTC', async (t) => { 8 | t.true(isRFC2822Date(toD(0))); 9 | t.true(isRFC2822Date(toD(329629726785))); 10 | t.true(isRFC2822Date(toD(729629726785))); 11 | t.true(isRFC2822Date(toD(1129629726785))); 12 | t.true(isRFC2822Date(toD(1529629726785))); 13 | }); 14 | 15 | test('rfc2822 UTC', async (t) => { 16 | t.is(toD_utc(0), 'Thu, 01 Jan 1970 00:00:00 +0000'); 17 | t.is(toD_utc(0), toD(0, true)); 18 | 19 | t.is(toD_utc(329629726785), 'Thu, 12 Jun 1980 03:48:46 +0000'); 20 | t.is(toD_utc(329629726785), toD(329629726785, true)); 21 | 22 | t.is(toD_utc(729629726785), 'Sat, 13 Feb 1993 18:55:26 +0000'); 23 | t.is(toD_utc(729629726785), toD(729629726785, true)); 24 | 25 | t.is(toD_utc(1129629726785), 'Tue, 18 Oct 2005 10:02:06 +0000'); 26 | t.is(toD_utc(1129629726785), toD(1129629726785, true)); 27 | 28 | t.is(toD_utc(1529629726785), 'Fri, 22 Jun 2018 01:08:46 +0000'); 29 | t.is(toD_utc(1529629726785), toD(1529629726785, true)); 30 | }); 31 | -------------------------------------------------------------------------------- /test/message.ts: -------------------------------------------------------------------------------- 1 | import { createReadStream, readFileSync } from 'fs'; 2 | import { URL } from 'url'; 3 | 4 | import test from 'ava'; 5 | import { simpleParser } from 'mailparser'; 6 | import type { AddressObject, ParsedMail } from 'mailparser'; 7 | import { SMTPServer } from 'smtp-server'; 8 | 9 | import { SMTPClient, Message } from '../email.js'; 10 | import type { MessageAttachment, MessageHeaders } from '../email.js'; 11 | 12 | const textFixtureUrl = new URL('attachments/smtp.txt', import.meta.url); 13 | const textFixture = readFileSync(textFixtureUrl, 'utf-8'); 14 | 15 | const htmlFixtureUrl = new URL('attachments/smtp.html', import.meta.url); 16 | const htmlFixture = readFileSync(htmlFixtureUrl, 'utf-8'); 17 | 18 | const pdfFixtureUrl = new URL('attachments/smtp.pdf', import.meta.url); 19 | const pdfFixture = readFileSync(pdfFixtureUrl, 'base64'); 20 | 21 | const tarFixtureUrl = new URL( 22 | 'attachments/postfix-2.8.7.tar.gz', 23 | import.meta.url 24 | ); 25 | const tarFixture = readFileSync(tarFixtureUrl, 'base64'); 26 | 27 | /** 28 | * \@types/mailparser@3.0.2 breaks our code 29 | * @see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/50744 30 | */ 31 | type ParsedMailCompat = Omit & { to?: AddressObject }; 32 | 33 | const port = 5555; 34 | const parseMap = new Map(); 35 | 36 | const client = new SMTPClient({ 37 | port, 38 | user: 'pooh', 39 | password: 'honey', 40 | ssl: true, 41 | }); 42 | const server = new SMTPServer({ 43 | secure: true, 44 | onAuth(auth, _session, callback) { 45 | if (auth.username == 'pooh' && auth.password == 'honey') { 46 | callback(null, { user: 'pooh' }); 47 | } else { 48 | return callback(new Error('invalid user / pass')); 49 | } 50 | }, 51 | async onData(stream, _session, callback: () => void) { 52 | const mail = (await simpleParser(stream, { 53 | skipHtmlToText: true, 54 | skipTextToHtml: true, 55 | skipImageLinks: true, 56 | } as Record)) as ParsedMailCompat; 57 | 58 | parseMap.set(mail.subject as string, mail); 59 | callback(); 60 | }, 61 | }); 62 | 63 | function send(headers: Partial) { 64 | return new Promise((resolve, reject) => { 65 | client.send(new Message(headers), (err) => { 66 | if (err) { 67 | reject(err); 68 | } else { 69 | resolve(parseMap.get(headers.subject as string) as ParsedMailCompat); 70 | } 71 | }); 72 | }); 73 | } 74 | 75 | test.before(async (t) => { 76 | server.listen(port, t.pass); 77 | }); 78 | test.after(async (t) => { 79 | server.close(t.pass); 80 | }); 81 | 82 | test('simple text message', async (t) => { 83 | const msg = { 84 | subject: 'this is a test TEXT message from emailjs', 85 | from: 'zelda@gmail.com', 86 | to: 'gannon@gmail.com', 87 | cc: 'gannon@gmail.com', 88 | bcc: 'gannon@gmail.com', 89 | text: 'hello friend, i hope this message finds you well.', 90 | 'message-id': 'this is a special id', 91 | }; 92 | 93 | const mail = await send(msg); 94 | t.is(mail.text, msg.text + '\n\n\n'); 95 | t.is(mail.subject, msg.subject); 96 | t.is(mail.from?.text, msg.from); 97 | t.is(mail.to?.text, msg.to); 98 | t.is(mail.messageId, '<' + msg['message-id'] + '>'); 99 | }); 100 | 101 | test('null text message', async (t) => { 102 | const msg = { 103 | subject: 'this is a test TEXT message from emailjs', 104 | from: 'zelda@gmail.com', 105 | to: 'gannon@gmail.com', 106 | text: null, 107 | 'message-id': 'this is a special id', 108 | }; 109 | 110 | const mail = await send(msg); 111 | t.is(mail.text, '\n\n\n'); 112 | }); 113 | 114 | test('empty text message', async (t) => { 115 | const msg = { 116 | subject: 'this is a test TEXT message from emailjs', 117 | from: 'zelda@gmail.com', 118 | to: 'gannon@gmail.com', 119 | text: '', 120 | 'message-id': 'this is a special id', 121 | }; 122 | 123 | const mail = await send(msg); 124 | t.is(mail.text, '\n\n\n'); 125 | }); 126 | 127 | test('simple unicode text message', async (t) => { 128 | const msg = { 129 | subject: 'this ✓ is a test ✓ TEXT message from emailjs', 130 | from: 'zelda✓ ', 131 | to: 'gannon✓ ', 132 | text: 'hello ✓ friend, i hope this message finds you well.', 133 | }; 134 | 135 | const mail = await send(msg); 136 | t.is(mail.text, msg.text + '\n\n\n'); 137 | t.is(mail.subject, msg.subject); 138 | t.is(mail.from?.text, msg.from); 139 | t.is(mail.to?.text, msg.to); 140 | }); 141 | 142 | test('very large text message', async (t) => { 143 | // thanks to jart+loberstech for this one! 144 | const msg = { 145 | subject: 'this is a test TEXT message from emailjs', 146 | from: 'ninjas@gmail.com', 147 | to: 'pirates@gmail.com', 148 | text: textFixture, 149 | }; 150 | 151 | const mail = await send(msg); 152 | t.is(mail.text, msg.text.replace(/\r/g, '') + '\n\n\n'); 153 | t.is(mail.subject, msg.subject); 154 | t.is(mail.from?.text, msg.from); 155 | t.is(mail.to?.text, msg.to); 156 | }); 157 | 158 | test('very large text data message', async (t) => { 159 | const text = '
' + textFixture + '
'; 160 | 161 | const msg = { 162 | subject: 'this is a test TEXT+DATA message from emailjs', 163 | from: 'lobsters@gmail.com', 164 | to: 'lizards@gmail.com', 165 | text: 'hello friend if you are seeing this, you can not view html emails. it is attached inline.', 166 | attachment: { 167 | data: text, 168 | alternative: true, 169 | }, 170 | }; 171 | 172 | const mail = await send(msg); 173 | t.is(mail.html, text.replace(/\r/g, '')); 174 | t.is(mail.text, msg.text + '\n'); 175 | t.is(mail.subject, msg.subject); 176 | t.is(mail.from?.text, msg.from); 177 | t.is(mail.to?.text, msg.to); 178 | }); 179 | 180 | test('html data message', async (t) => { 181 | const msg = { 182 | subject: 'this is a test TEXT+HTML+DATA message from emailjs', 183 | from: 'obama@gmail.com', 184 | to: 'mitt@gmail.com', 185 | attachment: { 186 | data: htmlFixture, 187 | alternative: true, 188 | }, 189 | }; 190 | 191 | const mail = await send(msg); 192 | t.is(mail.html, htmlFixture.replace(/\r/g, '')); 193 | t.is(mail.text, '\n'); 194 | t.is(mail.subject, msg.subject); 195 | t.is(mail.from?.text, msg.from); 196 | t.is(mail.to?.text, msg.to); 197 | }); 198 | 199 | test('html file message', async (t) => { 200 | const msg = { 201 | subject: 'this is a test TEXT+HTML+FILE message from emailjs', 202 | from: 'thomas@gmail.com', 203 | to: 'nikolas@gmail.com', 204 | attachment: { 205 | path: new URL('attachments/smtp.html', import.meta.url), 206 | alternative: true, 207 | }, 208 | }; 209 | 210 | const mail = await send(msg); 211 | t.is(mail.html, htmlFixture.replace(/\r/g, '')); 212 | t.is(mail.text, '\n'); 213 | t.is(mail.subject, msg.subject); 214 | t.is(mail.from?.text, msg.from); 215 | t.is(mail.to?.text, msg.to); 216 | }); 217 | 218 | test('html with image embed message', async (t) => { 219 | const htmlFixture2Url = new URL('attachments/smtp2.html', import.meta.url); 220 | const imageFixtureUrl = new URL('attachments/smtp.gif', import.meta.url); 221 | const msg = { 222 | subject: 'this is a test TEXT+HTML+IMAGE message from emailjs', 223 | from: 'ninja@gmail.com', 224 | to: 'pirate@gmail.com', 225 | attachment: { 226 | path: htmlFixture2Url, 227 | alternative: true, 228 | related: [ 229 | { 230 | path: imageFixtureUrl, 231 | type: 'image/gif', 232 | name: 'smtp-diagram.gif', 233 | headers: { 'Content-ID': '' }, 234 | }, 235 | ], 236 | }, 237 | }; 238 | 239 | const mail = await send(msg); 240 | t.is( 241 | mail.attachments[0].content.toString('base64'), 242 | readFileSync(imageFixtureUrl, 'base64') 243 | ); 244 | t.is(mail.html, readFileSync(htmlFixture2Url, 'utf-8').replace(/\r/g, '')); 245 | t.is(mail.text, '\n'); 246 | t.is(mail.subject, msg.subject); 247 | t.is(mail.from?.text, msg.from); 248 | t.is(mail.to?.text, msg.to); 249 | }); 250 | 251 | test('html data and attachment message', async (t) => { 252 | const msg = { 253 | subject: 'this is a test TEXT+HTML+FILE message from emailjs', 254 | from: 'thomas@gmail.com', 255 | to: 'nikolas@gmail.com', 256 | attachment: [ 257 | { 258 | path: new URL('attachments/smtp.html', import.meta.url), 259 | alternative: true, 260 | }, 261 | { path: new URL('attachments/smtp.gif', import.meta.url) }, 262 | ] as MessageAttachment[], 263 | }; 264 | 265 | const mail = await send(msg); 266 | t.is(mail.html, htmlFixture.replace(/\r/g, '')); 267 | t.is(mail.text, '\n'); 268 | t.is(mail.subject, msg.subject); 269 | t.is(mail.from?.text, msg.from); 270 | t.is(mail.to?.text, msg.to); 271 | }); 272 | 273 | test('attachment message', async (t) => { 274 | const msg = { 275 | subject: 'this is a test TEXT+ATTACHMENT message from emailjs', 276 | from: 'washing@gmail.com', 277 | to: 'lincoln@gmail.com', 278 | text: 'hello friend, i hope this message and pdf finds you well.', 279 | attachment: { 280 | path: pdfFixtureUrl, 281 | type: 'application/pdf', 282 | name: 'smtp-info.pdf', 283 | } as MessageAttachment, 284 | }; 285 | 286 | const mail = await send(msg); 287 | t.is(mail.attachments[0].content.toString('base64'), pdfFixture); 288 | t.is(mail.text, msg.text + '\n'); 289 | t.is(mail.subject, msg.subject); 290 | t.is(mail.from?.text, msg.from); 291 | t.is(mail.to?.text, msg.to); 292 | }); 293 | 294 | test('attachment sent with unicode filename message', async (t) => { 295 | const msg = { 296 | subject: 'this is a test TEXT+ATTACHMENT message from emailjs', 297 | from: 'washing@gmail.com', 298 | to: 'lincoln@gmail.com', 299 | text: 'hello friend, i hope this message and pdf finds you well.', 300 | attachment: { 301 | path: pdfFixtureUrl, 302 | type: 'application/pdf', 303 | name: 'smtp-✓-info.pdf', 304 | } as MessageAttachment, 305 | }; 306 | 307 | const mail = await send(msg); 308 | t.is(mail.attachments[0].content.toString('base64'), pdfFixture); 309 | t.is(mail.attachments[0].filename, 'smtp-✓-info.pdf'); 310 | t.is(mail.text, msg.text + '\n'); 311 | t.is(mail.subject, msg.subject); 312 | t.is(mail.from?.text, msg.from); 313 | t.is(mail.to?.text, msg.to); 314 | }); 315 | 316 | test('attachments message', async (t) => { 317 | const msg = { 318 | subject: 'this is a test TEXT+2+ATTACHMENTS message from emailjs', 319 | from: 'sergey@gmail.com', 320 | to: 'jobs@gmail.com', 321 | text: 'hello friend, i hope this message and attachments finds you well.', 322 | attachment: [ 323 | { 324 | path: pdfFixtureUrl, 325 | type: 'application/pdf', 326 | name: 'smtp-info.pdf', 327 | }, 328 | { 329 | path: tarFixtureUrl, 330 | type: 'application/tar-gz', 331 | name: 'postfix.source.2.8.7.tar.gz', 332 | }, 333 | ] as MessageAttachment[], 334 | }; 335 | 336 | const mail = await send(msg); 337 | t.is(mail.attachments[0].content.toString('base64'), pdfFixture); 338 | t.is(mail.attachments[1].content.toString('base64'), tarFixture); 339 | t.is(mail.text, msg.text + '\n'); 340 | t.is(mail.subject, msg.subject); 341 | t.is(mail.from?.text, msg.from); 342 | t.is(mail.to?.text, msg.to); 343 | }); 344 | 345 | test('streams message', async (t) => { 346 | const msg = { 347 | subject: 'this is a test TEXT+2+STREAMED+ATTACHMENTS message from emailjs', 348 | from: 'stanford@gmail.com', 349 | to: 'mit@gmail.com', 350 | text: 'hello friend, i hope this message and streamed attachments finds you well.', 351 | attachment: [ 352 | { 353 | stream: createReadStream(pdfFixtureUrl), 354 | type: 'application/pdf', 355 | name: 'smtp-info.pdf', 356 | }, 357 | { 358 | stream: createReadStream(tarFixtureUrl), 359 | type: 'application/x-gzip', 360 | name: 'postfix.source.2.8.7.tar.gz', 361 | }, 362 | ], 363 | }; 364 | 365 | for (const { stream } of msg.attachment) { 366 | stream.pause(); 367 | } 368 | 369 | const mail = await send(msg); 370 | t.is(mail.attachments[0].content.toString('base64'), pdfFixture); 371 | t.is(mail.attachments[1].content.toString('base64'), tarFixture); 372 | t.is(mail.text, msg.text + '\n'); 373 | t.is(mail.subject, msg.subject); 374 | t.is(mail.from?.text, msg.from); 375 | t.is(mail.to?.text, msg.to); 376 | }); 377 | 378 | test('message validation fails without `from` header', async (t) => { 379 | const msg = new Message({}); 380 | const { isValid, validationError } = msg.checkValidity(); 381 | t.false(isValid); 382 | t.is(validationError, 'Message must have a `from` header'); 383 | }); 384 | 385 | test('message validation fails without `to`, `cc`, or `bcc` header', async (t) => { 386 | const { isValid, validationError } = new Message({ 387 | from: 'piglet@gmail.com', 388 | }).checkValidity(); 389 | 390 | t.false(isValid); 391 | t.is( 392 | validationError, 393 | 'Message must have at least one `to`, `cc`, or `bcc` header' 394 | ); 395 | }); 396 | 397 | test('message validation succeeds with only `to` recipient header (string)', async (t) => { 398 | const { isValid, validationError } = new Message({ 399 | from: 'piglet@gmail.com', 400 | to: 'pooh@gmail.com', 401 | }).checkValidity(); 402 | 403 | t.true(isValid); 404 | t.is(validationError, undefined); 405 | }); 406 | 407 | test('message validation succeeds with only `to` recipient header (array)', async (t) => { 408 | const { isValid, validationError } = new Message({ 409 | from: 'piglet@gmail.com', 410 | to: ['pooh@gmail.com'], 411 | }).checkValidity(); 412 | 413 | t.true(isValid); 414 | t.is(validationError, undefined); 415 | }); 416 | 417 | test('message validation succeeds with only `cc` recipient header (string)', async (t) => { 418 | const { isValid, validationError } = new Message({ 419 | from: 'piglet@gmail.com', 420 | cc: 'pooh@gmail.com', 421 | }).checkValidity(); 422 | 423 | t.true(isValid); 424 | t.is(validationError, undefined); 425 | }); 426 | 427 | test('message validation succeeds with only `cc` recipient header (array)', async (t) => { 428 | const { isValid, validationError } = new Message({ 429 | from: 'piglet@gmail.com', 430 | cc: ['pooh@gmail.com'], 431 | }).checkValidity(); 432 | 433 | t.true(isValid); 434 | t.is(validationError, undefined); 435 | }); 436 | 437 | test('message validation succeeds with only `bcc` recipient header (string)', async (t) => { 438 | const { isValid, validationError } = new Message({ 439 | from: 'piglet@gmail.com', 440 | bcc: 'pooh@gmail.com', 441 | }).checkValidity(); 442 | 443 | t.true(isValid); 444 | t.is(validationError, undefined); 445 | }); 446 | 447 | test('message validation succeeds with only `bcc` recipient header (array)', async (t) => { 448 | const { isValid, validationError } = new Message({ 449 | from: 'piglet@gmail.com', 450 | bcc: ['pooh@gmail.com'], 451 | }).checkValidity(); 452 | 453 | t.true(isValid); 454 | t.is(validationError, undefined); 455 | }); 456 | -------------------------------------------------------------------------------- /test/mime.ts: -------------------------------------------------------------------------------- 1 | // adapted from https://github.com/emailjs/emailjs-mime-codec/blob/6909c706b9f09bc0e5c3faf48f723cca53e5b352/src/mimecodec-unit.js 2 | import test from 'ava'; 3 | import { mimeEncode, mimeWordEncode } from '../email.js'; 4 | 5 | test('mimeEncode should encode UTF-8', async (t) => { 6 | t.is(mimeEncode('tere ÕÄÖÕ'), 'tere =C3=95=C3=84=C3=96=C3=95'); 7 | }); 8 | 9 | test('mimeEncode should encode trailing whitespace', async (t) => { 10 | t.is(mimeEncode('tere '), 'tere =20'); 11 | }); 12 | 13 | test('mimeEncode should encode non UTF-8', async (t) => { 14 | t.is(mimeEncode(new Uint8Array([0xbd, 0xc5]), 'utf-16be'), '=EB=B7=85'); 15 | }); 16 | 17 | test('mimeWordEncode should encode', async (t) => { 18 | t.is('=?UTF-8?Q?See_on_=C3=B5hin_test?=', mimeWordEncode('See on õhin test')); 19 | }); 20 | 21 | test('mimeWordEncode should QP-encode mime word', async (t) => { 22 | t.is( 23 | '=?UTF-8?Q?=E4=AB=B5=E6=9D=A5=E2=B5=B6=E6=87=9E?=', 24 | mimeWordEncode( 25 | new Uint8Array([0x4a, 0xf5, 0x67, 0x65, 0x2d, 0x76, 0x61, 0xde]), 26 | 'Q', 27 | 'utf-16be' 28 | ) 29 | ); 30 | }); 31 | 32 | test('mimeWordEncode should Base64-encode mime word', async (t) => { 33 | t.is( 34 | mimeWordEncode('Привет и до свидания', 'B'), 35 | '=?UTF-8?B?0J/RgNC40LLQtdGCINC4INC00L4g0YHQstC40LTQsNC90LjRjw==?=' 36 | ); 37 | }); 38 | 39 | test('mimeWordEncode should Base64-encode a long mime word', async (t) => { 40 | const payload = 41 | 'üöß‹€Привет и до свиданияПривет и до свиданияПривет и до свиданияПривет и до свиданияПривет и до свиданияПривет и до свиданияПривет и до свиданияПривет и до свидания'; 42 | const expected = 43 | '=?UTF-8?B?w7zDtsOf4oC54oKs0J/RgNC40LLQtdGCINC4INC00L4g0YHQstC4?= ' + 44 | '=?UTF-8?B?0LTQsNC90LjRj9Cf0YDQuNCy0LXRgiDQuCDQtNC+INGB0LLQuNC0?= ' + 45 | '=?UTF-8?B?0LDQvdC40Y/Qn9GA0LjQstC10YIg0Lgg0LTQviDRgdCy0LjQtNCw?= ' + 46 | '=?UTF-8?B?0L3QuNGP0J/RgNC40LLQtdGCINC4INC00L4g0YHQstC40LTQsNC9?= ' + 47 | '=?UTF-8?B?0LjRj9Cf0YDQuNCy0LXRgiDQuCDQtNC+INGB0LLQuNC00LDQvdC4?= ' + 48 | '=?UTF-8?B?0Y/Qn9GA0LjQstC10YIg0Lgg0LTQviDRgdCy0LjQtNCw0L3QuNGP?= ' + 49 | '=?UTF-8?B?0J/RgNC40LLQtdGCINC4INC00L4g0YHQstC40LTQsNC90LjRj9Cf?= ' + 50 | '=?UTF-8?B?0YDQuNCy0LXRgiDQuCDQtNC+INGB0LLQuNC00LDQvdC40Y8=?='; 51 | t.is(mimeWordEncode(payload, 'B'), expected); 52 | }); 53 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@ledge/configs/tsconfig.json", 3 | "include": [ 4 | "email.ts", 5 | "smtp", 6 | "test", 7 | ], 8 | "ts-node": { 9 | "transpileOnly": true 10 | } 11 | } 12 | --------------------------------------------------------------------------------