[Pkg-javascript-commits] [node-simplesmtp] 01/02: Imported Upstream version 0.3.35

Thorsten Alteholz alteholz at moszumanska.debian.org
Sat Feb 27 14:07:56 UTC 2016


This is an automated email from the git hooks/post-receive script.

alteholz pushed a commit to branch master
in repository node-simplesmtp.

commit 26513ef326e9f788fb05695e7b031a3da424644f
Author: Thorsten Alteholz <debian at alteholz.de>
Date:   Sat Feb 27 15:07:48 2016 +0100

    Imported Upstream version 0.3.35
---
 .gitignore                     |    2 +
 .npmignore                     |    3 +
 .travis.yml                    |   11 +
 CHANGELOG.md                   |  170 ++++++
 LICENSE                        |   16 +
 README.md                      |  357 +++++++++++++
 examples/send.js               |   46 ++
 examples/simpleserver.js       |   19 +
 examples/size.js               |   67 +++
 examples/validate-recipient.js |   37 ++
 index.js                       |    8 +
 lib/client.js                  | 1145 ++++++++++++++++++++++++++++++++++++++++
 lib/pool.js                    |  374 +++++++++++++
 lib/server.js                  |  804 ++++++++++++++++++++++++++++
 lib/simpleserver.js            |  126 +++++
 package.json                   |   36 ++
 test/client.js                 |  498 +++++++++++++++++
 test/pool.js                   |  400 ++++++++++++++
 test/server.js                 |  739 ++++++++++++++++++++++++++
 test/testmessage.eml           |    5 +
 20 files changed, 4863 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fd4f2b0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+.DS_Store
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..8a9ec42
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,3 @@
+.travis.yml
+test
+examples
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..7b2b382
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,11 @@
+language: node_js
+node_js:
+  - 0.8
+  - "0.10"
+
+notifications:
+  email:
+    recipients:
+      - andris at kreata.ee
+    on_success: change
+    on_failure: change
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..91718eb
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,170 @@
+# CHANGELOG
+
+## v0.3.34 2014-01-14
+
+  * Bumped version to 0.3.34
+  * Fixed a bug with ES6 strict mode (can't set properties to a `false` value)
+
+## v0.3.33 2014-09-04
+
+  * Bumped version to 0.3.33
+  * Added deprecation notice
+
+## v0.3.32 2014-05-30
+
+  * Bumped version to 0.3.32
+  * ignore close if end was already called [004ebaee]
+
+## v0.3.30 2014-05-13
+
+  * Bumped version to 0.3.30
+  * Added .npmignore [a7344b49]
+
+## v0.3.29 2014-05-07
+
+  * Bumped version to 0.3.29
+  * Changed formatting rules, use single quotes instead of double quotes [92b581c8]
+  * rollback NOOP usage [e47e24bb]
+
+## v0.3.28 2014-05-03
+
+  * Bumped version to 0.3.28
+  * handle errors with NOOP [deb18352]
+
+## v0.3.27 2014-04-23
+
+  * Bumped version to 0.3.27
+  * get tests running in node 0.8, 0.10, 0.11 [9b3f9043..833388d5]
+
+## v0.3.26 2014-04-23
+
+  * Bumped version to 0.3.26
+  * Server: Added support for XOAUTH2 authentication [87b6ed66]
+  * Client: Use interval NOOPing to keep the connection up [184d8623]
+  * Client: do not throw if recipients are note set [785a2b09]
+
+## v0.3.25 2014-04-16
+
+  * Bumped version to 0.3.25
+  * disabled server test for max incoming connections [476f8cf5]
+  * Added socketTimeout option [b83a4838]
+  * fix invalid tests [cf22d390]
+
+## v0.3.24 2014-03-31
+
+  * Bumped version to 0.3.24
+  * Added test for empty MAIL FROM [7f17174d]
+  * Allow null return sender in mail command (coxeh) [08bc6a6f]
+  * incorrect mail format fix (siterra) [d42d364e]
+  * support for `form` and `to` structure: {address:"...",name:"..."} siterra) [2b054740]
+  * Improved auth supports detection (finian) [863dc019]
+  * Fixed a Buffer building bug (finian) [6dc9a4e2]
+
+## v0.3.23 2014-03-10
+
+  * Bumped version to 0.3.23
+  * removed pipelining [4f0a382f]
+  * Rename disableDotEscaping to enableDotEscaping [5534bd85]
+  * Ignore OAuth2 errors from destroyed connections (SLaks) [e8ff3356]
+
+## v0.3.22 2014-02-16
+
+  * Bumped version to 0.3.22
+  * Emit error on unexpected close [111da167]
+  * Allowed persistence of custom properties when resetting envelope state. (garbetjie) [b49b7ead]
+
+## v0.3.21 2014-02-16
+
+  * Bumped version to 0.3.21
+  * Ignore OAuth errors from destroyed connections (SLaks) [d50a7571]
+
+## v0.3.20 2014-01-28
+
+  * Bumped version to 0.3.20
+  * Re-emit 'drain' from tcp socket [5bfb1fcc]
+
+## v0.3.19 2014-01-28
+
+  * Bumped version to 0.3.19
+  * Prefer setImmediate over nextTick if available [f53e2d44]
+  * Server: Implemented "NOOP" command (codingphil) [707485c0]
+  * Server: Allow SIZE with MAIL [3b404028]
+
+## v0.3.18 2014-01-05
+
+  * Bumped version to 0.3.18
+  * Added limiting of max client connections (garbetjie) [bcd5c0b3]
+
+## v0.3.17 2014-01-05
+
+  * Bumped version to 0.3.17
+  * Do not create a server instance with invalid socket (47d17420)
+  * typo (chrisdew) [fe4df83f]
+  * Only emit rcptFailed if there actually was an address that was rejected [4c75523f]
+
+## v0.3.16 2013-12-02
+
+  * Bumped version to 0.3.16
+  * Expose simplesmtp version number [c2382203]
+  * typo in SMTP (chrisdew) [6c39a8d7]
+  * Fix typo in README.md (Meekohi) [597a25cb]
+
+## v0.3.15 2013-11-15
+
+  * Bumped version to 0.3.15
+  * Fixed bugs in connection timeout implementation (finian) [1a25d5af]
+
+## v0.3.14 2013-11-08
+
+  * Bumped version to 0.3.14
+  * fixed: typo causing connection.remoteAddress to be undefined (johnnyleung) 795fe81f
+  * improvements to handling stage (mysz) 5a79e6a1
+  * fixes TypeError: Cannot use 'in' operator to search for 'dsn' in undefined (mysz) 388d9b82
+  * lost saving stage in "DATA" (mysz) de694f67
+  * more info on smtp error (mysz) 42a4f964
+
+## v0.3.13 2013-10-29
+
+  * Bumped version to 0.3.13
+  * Handling errors which close connection on or before EHLO (mysz) 03345d4d
+
+## v0.3.12 2013-10-29
+
+  * Bumped version to 0.3.12
+  * Allow setting maxMessages to pool 5d185708
+
+## v0.3.11 2013-10-22
+
+  * Bumped version to 0.3.11
+  * style update 2095d3a9
+  * fix tests 17a3632f
+  * DSN Support implemented. (irvinzz) d1e8ba29
+
+## v0.3.10 2013-09-09
+
+  * Bumped version to 0.3.10
+  * added greetingTimeout, connectionTimeout and rejectUnathorized options to connection pool 8fa55cd3
+
+## v0.3.9 2013-09-09
+
+  * Bumped version to 0.3.9
+  * added "use strict" definitions, added new options for client: greetingTimeout, connectionTimeout, rejectUnathorized 51047ae0
+  * Do not include localAddress in the options if it is unset 7eb0e8fc
+
+## v0.3.8 2013-08-21
+
+  * Bumped version to 0.3.8
+  * short fix for #42, Client parser hangs on certain input (dannycoates) 089f5cd4
+
+## v0.3.7 2013-08-16
+
+  * Bumped version to 0.3.7
+  * minor adjustments for better portability with browserify (whiteout-io) 15715498
+  * Added raw message to error object (andremetzen) 15715498
+  * Passing to error handler the message sent from SMTP server when an error occurred (andremetzen) 15d4cbb4
+
+## v0.3.6 2013-08-06
+
+  * Bumped version to 0.3.6
+  * Added changelog
+  * Timeout if greeting is not received after connection is established
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7d4a384
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,16 @@
+Copyright (c) 2012-2014 Andris Reinman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..302a2bb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,357 @@
+# simplesmtp
+
+## DEPRECATION NOTICE
+
+This module is deprecated. For SMTP servers use [smtp-server](https://github.com/andris9/smtp-server), for SMTP clients use [smtp-connection](https://www.npmjs.org/package/smtp-connection). Alternatively, for full featured SMTP server applications, you should use [Haraka](https://www.npmjs.org/package/Haraka).
+
+--------
+
+Simplesmtp is a module written for Node v0.6 and slightly updated for Node v0.8. It does not use Node v0.10 streams and probably is going to have a rocky future with Node v0.12. I do not have time to keep it up to date, the thing probably needs a major rewrite for Node v0.12.
+
+Should be fine though for integration testing purposes.
+
+## Info
+
+This is a module to easily create custom SMTP servers and clients - use SMTP as a first class protocol in Node.JS!
+
+[![Build Status](https://secure.travis-ci.org/andris9/simplesmtp.png)](http://travis-ci.org/andris9/simplesmtp)
+[![NPM version](https://badge.fury.io/js/simplesmtp.png)](http://badge.fury.io/js/simplesmtp)
+
+## Version warning!
+
+If you are using node v0.6, then the last usable version of **simplesmtp** is v0.2.7
+
+Current version of simplesmtp is fully supported for Node v0.8+
+
+ˇ## SMTP Server
+
+## Simple SMTP server
+
+For a simple inbound only, no authentication SMTP server you can use
+
+    simplesmtp.createSimpleServer([options], requestListener).listen(port);
+
+Example
+
+    simplesmtp.createSimpleServer({SMTPBanner:"My Server"}, function(req){
+        req.pipe(process.stdout);
+        req.accept();
+    }).listen(port);
+
+Properties
+
+  * **req.from** - From address
+  * **req.to** - an array of To addresses
+  * **req.host** - hostname reported by the client
+  * **req.remodeAddress** - client IP address
+
+Methods
+
+  * **req.accept** *([id])* - Accept the message with the selected ID
+  * **req.reject** *([message])* - Reject the message with the selected message
+  * **req.pipe** *(stream)* - Pipe the incoming data to a writable stream
+
+Events
+
+  * **'data'** *(chunk)* - A chunk (Buffer) of the message.
+  * **'end'** - The message has been transferred
+
+
+## Advanced SMTP server
+
+### Usage
+
+Create a new SMTP server instance with
+
+    var smtp = simplesmtp.createServer([options]);
+
+And start listening on selected port
+
+    smtp.listen(25, [function(err){}]);
+
+SMTP options can include the following:
+
+  * **name** - the hostname of the server, will be used for informational messages
+  * **debug** - if set to true, print out messages about the connection
+  * **timeout** - client timeout in milliseconds, defaults to 60 000 (60 sec.)
+  * **secureConnection** - start a server on secure connection
+  * **SMTPBanner** - greeting banner that is sent to the client on connection
+  * **requireAuthentication** - if set to true, require that the client must authenticate itself
+  * **enableAuthentication** - if set to true, client may authenticate itself but don't have to (as opposed to `requireAuthentication` that explicitly requires clients to authenticate themselves)
+  * **maxSize** - maximum size of an e-mail in bytes (currently informational only)
+  * **credentials** - TLS credentials (`{key:'', cert:'', ca:['']}`) for the server
+  * **authMethods** - allowed authentication methods, defaults to `["PLAIN", "LOGIN"]`
+  * **disableEHLO** - if set to true, support HELO command only
+  * **ignoreTLS** - if set to true, allow client do not use STARTTLS
+  * **disableDNSValidation** - if set, do not validate sender domains
+  * **disableSTARTTLS** - if set, do not use STARTTLS
+
+### Example
+
+    var simplesmtp = require("simplesmtp"),
+        fs = require("fs");
+
+    var smtp = simplesmtp.createServer();
+    smtp.listen(25);
+
+    smtp.on("startData", function(connection){
+        console.log("Message from:", connection.from);
+        console.log("Message to:", connection.to);
+        connection.saveStream = fs.createWriteStream("/tmp/message.txt");
+    });
+
+    smtp.on("data", function(connection, chunk){
+        connection.saveStream.write(chunk);
+    });
+
+    smtp.on("dataReady", function(connection, callback){
+        connection.saveStream.end();
+        console.log("Incoming message saved to /tmp/message.txt");
+        callback(null, "ABC1"); // ABC1 is the queue id to be advertised to the client
+        // callback(new Error("Rejected as spam!")); // reported back to the client
+    });
+
+### Events
+
+  * **startData** *(connection)* - DATA stream is opened by the client (`connection` is an object with `from`, `to`, `host` and `remoteAddress` properties)
+  * **data** *(connection, chunk)* - e-mail data chunk is passed from the client
+  * **dataReady** *(connection, callback)* - client is finished passing e-mail data, `callback` returns the queue id to the client
+  * **authorizeUser** *(connection, username, password, callback)* - will be emitted if `requireAuthentication` option is set to true. `callback` has two parameters *(err, success)* where `success` is Boolean and should be true, if user is authenticated successfully
+  * **validateSender** *(connection, email, callback)* - will be emitted if `validateSender` listener is set up
+  * **validateRecipient** *(connection, email, callback)* - will be emitted it `validataRecipients` listener is set up
+  * **close** *(connection)* - emitted when the connection to client is closed
+
+## SMTP Client
+
+### Usage
+
+SMTP client can be created with `simplesmtp.connect(port[,host][, options])`
+where
+
+  * **port** is the port to connect to
+  * **host** is the hostname to connect to (defaults to "localhost")
+  * **options** is an optional options object (see below)
+
+### Connection options
+
+The following connection options can be used with `simplesmtp.connect`:
+
+  * **secureConnection** - use SSL
+  * **name** - the name of the client server
+  * **auth** - authentication object `{user:"...", pass:"..."}` or `{XOAuthToken:"base64data"}`
+  * **ignoreTLS** - ignore server support for STARTTLS
+  * **tls** - optional options object for `tls.connect`, also applies to STARTTLS. For example `rejectUnauthorized` is set to `false` by default. You can override this option by setting `tls: {rejectUnauthorized: true}`
+  * **debug** - output client and server messages to console
+  * **logFile** - optional filename where communication with remote server has to be logged
+  * **instanceId** - unique instance id for debugging (will be output console with the messages)
+  * **localAddress** - local interface to bind to for network connections (needs Node.js >= 0.11.3 for working with tls)
+  * **greetingTimeout** (defaults to 10000) - Time to wait in ms until greeting message is received from the server
+  * **connectionTimeout** (system default if not set) - Time to wait in ms until the socket is opened to the server
+  * **socketTimeout** (defaults to 1 hour) - Time of inactivity until the connection is closed
+  * **rejectUnathorized** (defaults to false) - if set to true accepts only valid server certificates. You can override this option with the `tls` option, this is just a shorthand
+  * **dsn** - An object with methods `success`, `failure` and `delay`. If any of these are set to true, DSN will be used
+  * **enableDotEscaping** set to true if you want to escape dots at the begining of each line. Defaults to false.
+
+### Connection events
+
+Once a connection is set up the following events can be listened to:
+
+  * **'idle'** - the connection to the SMTP server has been successfully set up and the client is waiting for an envelope
+  * **'message'** - the envelope is passed successfully to the server and a message stream can be started
+  * **'ready'** `(success)` - the message was sent
+  * **'rcptFailed'** `(addresses)` - not all recipients were accepted (invalid addresses are included as an array)
+  * **'error'** `(err, stage)` - An error occurred. The connection is closed and an 'end' event is emitted shortly. Second argument indicates on which SMTP session stage an error occured.
+  * **'end'** - connection to the client is closed
+
+### Sending an envelope
+
+When an `'idle'` event is emitted, an envelope object can be sent to the server.
+This includes a string `from` and an array of strings `to` property.
+
+Envelope can be sent with `client.useEnvelope(envelope)`
+
+    // run only once as 'idle' is emitted again after message delivery
+    client.once("idle", function(){
+        client.useEnvelope({
+            from: "me at example.com",
+            to: ["receiver1 at example.com", "receiver2 at example.com"]
+        });
+    });
+
+The `to` part of the envelope includes **all** recipients from `To:`, `Cc:` and `Bcc:` fields.
+
+If setting the envelope up fails, an error is emitted. If only some (not all)
+recipients are not accepted, the mail can still be sent but an `rcptFailed`
+event is emitted.
+
+    client.on("rcptFailed", function(addresses){
+        console.log("The following addresses were rejected: ", addresses);
+    });
+
+If the envelope is set up correctly a `'message'` event is emitted.
+
+### Sending a message
+
+When `'message'` event is emitted, it is possible to send mail. To do this
+you can pipe directly a message source (for example an .eml file) to the client
+or alternatively you can send the message with `client.write` calls (you also
+need to call `client.end()` once the message is completed.
+
+If you are piping a stream to the client, do not leave the `'end'` event out,
+this is needed to complete the message sequence by the client.
+
+    client.on("message", function(){
+        fs.createReadStream("test.eml").pipe(client);
+    });
+
+Once the message is delivered a `'ready'` event is emitted. The event has an
+parameter which indicates if the message was transmitted( (true) or not (false)
+and another which includes the last received data from the server.
+
+    client.on("ready", function(success, response){
+        if(success){
+            console.log("The message was transmitted successfully with "+response);
+        }
+    });
+
+### XOAUTH
+
+**simplesmtp** supports [XOAUTH2 and XOAUTH](https://developers.google.com/google-apps/gmail/oauth_protocol) authentication.
+
+#### XOAUTH2
+
+To use this feature you can set `XOAuth2` param as an `auth` option
+
+    var mailOptions = {
+        ...,
+        auth:{
+            XOAuth2: {
+                user: "example.user at gmail.com",
+                clientId: "8819981768.apps.googleusercontent.com",
+                clientSecret: "{client_secret}",
+                refreshToken: "1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI",
+                accessToken: "vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==",
+                timeout: 3600
+            }
+        }
+    }
+
+`accessToken` and `timeout` values are optional. If login fails a new access token is generated automatically.
+
+#### XOAUTH
+
+To use this feature you can set `XOAuthToken` param as an `auth` option
+
+    var mailOptions = {
+        ...,
+        auth:{
+            XOAuthToken: "R0VUIGh0dHBzOi8vbWFpbC5nb29...."
+        }
+    }
+
+Alternatively it is also possible to use XOAuthToken generators (supported by Nodemailer) - this
+needs to be an object with a mandatory method `generate` that takes a callback function for
+generating a XOAUTH token string. This is better for generating tokens only when needed -
+there is no need to calculate unique token for every e-mail request, since a lot of these
+might share the same connection and thus the cleint needs not to re-authenticate itself
+with another token.
+
+    var XOGen = {
+        token: "abc",
+        generate: function(callback){
+            if(1 != 1){
+                return callback(new Error("Tokens can't be generated in strange environments"));
+            }
+            callback(null, new Buffer(this.token, "utf-8").toString("base64"));
+        }
+    }
+
+    var mailOptions = {
+        ...,
+        auth:{
+            XOAuthToken: XOGen
+        }
+    }
+
+### Error types
+
+Emitted errors include the reason for failing in the `name` property
+
+  * **UnknowAuthError** - the client tried to authenticate but the method was not supported
+  * **AuthError** - the username/password used were rejected
+  * **TLSError** - STARTTLS failed
+  * **SenderError** - the sender e-mail address was rejected
+  * **RecipientError** - all recipients were rejected (if only some of the recipients are rejected, a `'rcptFailed'` event is raised instead
+
+There's also an additional property in the error object called `data` that includes
+the last response received from the server (if available for the current error type).
+
+### About reusing the connection
+
+You can reuse the same connection several times but you can't send a mail
+through the same connection concurrently. So if you catch and `'idle'` event
+lock the connection to a message process and unlock after `'ready'`.
+
+On `'error'` events you should reschedule the message and on `'end'` events
+you should recreate the connection.
+
+### Closing the client
+
+By default the client tries to keep the connection up. If you want to close it,
+run `client.quit()` - this sends a `QUIT` command to the server and closes the
+connection
+
+    client.quit();
+
+## SMTP Client Connection pool
+
+**simplesmtp** has the option for connection pooling if you want to reuse a bulk
+of connections.
+
+### Usage
+
+Create a connection pool of SMTP clients with
+
+    simplesmtp.createClientPool(port[,host][, options])
+
+where
+
+  * **port** is the port to connect to
+  * **host** is the hostname to connect to (defaults to "localhost")
+  * **options** is an optional options object (see below)
+
+### Connection options
+
+The following connection options can be used with `simplesmtp.connect`:
+
+  * **secureConnection** - use SSL
+  * **name** - the name of the client server
+  * **auth** - authentication object `{user:"...", pass:"..."}` or  `{XOAuthToken:"base64data"}`
+  * **ignoreTLS** - ignore server support for STARTTLS
+  * **debug** - output client and server messages to console
+  * **logFile** - optional filename where communication with remote server has to be logged
+  * **maxConnections** - how many connections to keep in the pool (defaults to 5)
+  * **localAddress** - local interface to bind to for network connections (needs Node.js >= 0.11.3 for working with tls)
+  * **maxMessages** - limit the count of messages to send through a single connection (no limit by default)
+
+### Send an e-mail
+
+E-mails can be sent through the pool with
+
+    pool.sendMail(mail[, callback])
+
+where
+
+  * **mail** is a [MailComposer](https://github.com/andris9/mailcomposer) compatible object
+  * **callback** `(error, responseObj)` - is the callback function to run after the message is delivered or an error occured. `responseObj` may include `failedRecipients` which is an array with e-mail addresses that were rejected and `message` which is the last response from the server.
+
+### Errors
+
+In addition to SMTP client errors another error name is used
+
+  * **DeliveryError** - used if the message was not accepted by the SMTP server
+
+## License
+
+**MIT**
+
diff --git a/examples/send.js b/examples/send.js
new file mode 100644
index 0000000..6185a9c
--- /dev/null
+++ b/examples/send.js
@@ -0,0 +1,46 @@
+var simplesmtp = require('../index');
+
+mail('sender at example.com', 'receiver at example.com', 'subject: test\r\n\r\nhello world!');
+
+/**
+ * Send a raw email
+ *
+ * @param {String} from E-mail address of the sender
+ * @param {String|Array} to E-mail address or a list of addresses of the receiver
+ * @param {[type]} message Mime message
+ */
+function mail(from, to, message) {
+    var client = simplesmtp.connect(465, 'smtp.gmail.com', {
+        secureConnection: true,
+        auth: {
+            user: 'gmail.username at gmail.com',
+            pass: 'gmail_pass'
+        },
+        debug: true
+    });
+
+    client.once('idle', function() {
+        client.useEnvelope({
+            from: from,
+            to: [].concat(to || [])
+        });
+    });
+
+    client.on('message', function() {
+        client.write(message.replace(/\r?\n/g, '\r\n').replace(/^\./gm, '..'));
+        client.end();
+    });
+
+    client.on('ready', function(success) {
+        client.quit();
+    });
+
+    client.on('error', function(err) {
+        console.log('ERROR');
+        console.log(err);
+    });
+
+    client.on('end', function() {
+        console.log('DONE')
+    });
+}
\ No newline at end of file
diff --git a/examples/simpleserver.js b/examples/simpleserver.js
new file mode 100644
index 0000000..2b44086
--- /dev/null
+++ b/examples/simpleserver.js
@@ -0,0 +1,19 @@
+"use strict";
+
+//console.log(process.stdout.writable);
+var simplesmtp = require("../index");
+
+simplesmtp.createSimpleServer({SMTPBanner:"My Server", debug: true}, function(req){
+    process.stdout.write("\r\nNew Mail:\r\n");
+    req.on("data", function(chunk){
+        process.stdout.write(chunk);
+    });
+    req.accept();
+}).listen(25, function(err){
+    if(!err){
+        console.log("SMTP server listening on port 25");
+    }else{
+        console.log("Could not start server on port 25. Ports under 1000 require root privileges.");
+        console.log(err.message);
+    }
+});
diff --git a/examples/size.js b/examples/size.js
new file mode 100644
index 0000000..4c24476
--- /dev/null
+++ b/examples/size.js
@@ -0,0 +1,67 @@
+"use strict";
+
+var simplesmtp = require("../index"),
+    fs = require("fs");
+
+// Example for http://tools.ietf.org/search/rfc1870
+
+var maxMessageSize = 10;
+
+var smtp = simplesmtp.createServer({
+    maxSize: maxMessageSize, // maxSize must be set in order to support SIZE
+    disableDNSValidation: true,
+    debug: true
+});
+smtp.listen(25);
+
+// Set up sender validation function
+smtp.on("validateSender", function(connection, email, done){
+    console.log(1, connection.messageSize, maxMessageSize);
+    // SIZE value can be found from connection.messageSize
+    if(connection.messageSize > maxMessageSize){
+        var err = new Error("Max space reached");
+        err.SMTPResponse = "452 This server can only accept messages up to " + maxMessageSize + " bytes";
+        done(err);
+    }else{
+        done();
+    }
+});
+
+// Set up recipient validation function
+smtp.on("validateRecipient", function(connection, email, done){
+    // Allow only messages up to 100 bytes
+    if(connection.messageSize > 100){
+        var err = new Error("Max space reached");
+        err.SMTPResponse = "552 Channel size limit exceeded: " + email;
+        done(err);
+    }else{
+        done();
+    }
+});
+
+smtp.on("startData", function(connection){
+    connection.messageSize = 0;
+    connection.saveStream = fs.createWriteStream("/tmp/message.txt");
+});
+
+smtp.on("data", function(connection, chunk){
+    connection.messageSize += chunk.length;
+    connection.saveStream.write(chunk);
+});
+
+smtp.on("dataReady", function(connection, done){
+    connection.saveStream.end();
+
+    // check if message
+    if(connection.messageSize > maxMessageSize){
+        // mail was too big and therefore ignored
+        var err = new Error("Max fileSize reached");
+        err.SMTPResponse = "552 message exceeds fixed maximum message size";
+        done(err);
+    }else{
+        done();
+        console.log("Delivered message by " + connection.from +
+            " to " + connection.to.join(", ") + ", sent from " + connection.host +
+            " (" + connection.remoteAddress + ")");
+    }
+});
\ No newline at end of file
diff --git a/examples/validate-recipient.js b/examples/validate-recipient.js
new file mode 100644
index 0000000..a8c6237
--- /dev/null
+++ b/examples/validate-recipient.js
@@ -0,0 +1,37 @@
+"use strict";
+
+var simplesmtp = require("simplesmtp"),
+    fs = require("fs");
+
+var allowedRecipientDomains = ["node.ee", "neti.ee"];
+
+var smtp = simplesmtp.createServer();
+smtp.listen(25);
+
+// Set up recipient validation function
+smtp.on("validateRecipient", function(connection, email, done){
+    var domain = ((email || "").split("@").pop() || "").toLowerCase().trim();
+
+    if(allowedRecipientDomains.indexOf(domain) < 0){
+        done(new Error("Invalid domain"));
+    }else{
+        done();
+    }
+});
+
+smtp.on("startData", function(connection){
+    connection.saveStream = fs.createWriteStream("/tmp/message.txt");
+});
+
+smtp.on("data", function(connection, chunk){
+    connection.saveStream.write(chunk);
+});
+
+smtp.on("dataReady", function(connection, done){
+    connection.saveStream.end();
+    done();
+
+    console.log("Delivered message by " + connection.from +
+        " to " + connection.to.join(", ") + ", sent from " + connection.host +
+        " (" + connection.remoteAddress + ")");
+});
\ No newline at end of file
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..4f02973
--- /dev/null
+++ b/index.js
@@ -0,0 +1,8 @@
+var packageData = require('./package.json');
+
+// expose the API to the world
+module.exports.createServer = require('./lib/server.js');
+module.exports.createSimpleServer = require('./lib/simpleserver.js');
+module.exports.connect = require('./lib/client.js');
+module.exports.createClientPool = require('./lib/pool.js');
+module.exports.version = packageData.version;
\ No newline at end of file
diff --git a/lib/client.js b/lib/client.js
new file mode 100644
index 0000000..d8adf31
--- /dev/null
+++ b/lib/client.js
@@ -0,0 +1,1145 @@
+'use strict';
+
+var Stream = require('stream').Stream,
+    utillib = require('util'),
+    net = require('net'),
+    tls = require('tls'),
+    oslib = require('os'),
+    xoauth2 = require('xoauth2'),
+    crypto = require('crypto'),
+    fs = require('fs');
+
+// expose to the world
+module.exports = function(port, host, options) {
+    var connection = new SMTPClient(port, host, options);
+
+    if (typeof setImmediate == 'function') {
+        setImmediate(connection.connect.bind(connection));
+    } else {
+        process.nextTick(connection.connect.bind(connection));
+    }
+
+    return connection;
+};
+
+/**
+ * <p>Generates a SMTP connection object</p>
+ *
+ * <p>Optional options object takes the following possible properties:</p>
+ * <ul>
+ *     <li><b>secureConnection</b> - use SSL</li>
+ *     <li><b>name</b> - the name of the client server</li>
+ *     <li><b>auth</b> - authentication object <code>{user:'...', pass:'...'}</code>
+ *     <li><b>ignoreTLS</b> - ignore server support for STARTTLS</li>
+ *     <li><b>tls</b> - options for createCredentials</li>
+ *     <li><b>debug</b> - output client and server messages to console</li>
+ *     <li><b>logFile</b> - output client and server messages to file</li>
+ *     <li><b>instanceId</b> - unique instance id for debugging</li>
+ *     <li><b>localAddress</b> - outbound address to bind to (see: http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener)</li>
+ *     <li><b>greetingTimeout</b> - Time to wait in ms until greeting message is received from the server (defaults to 10000)</li>
+ *     <li><b>socketTimeout</b> - Time of inactivity until the connection is closed (defaults to 1 hour)</li>
+ * </ul>
+ *
+ * @constructor
+ * @namespace SMTP Client module
+ * @param {Number} [port=25] Port number to connect to
+ * @param {String} [host='localhost'] Hostname to connect to
+ * @param {Object} [options] Option properties
+ */
+function SMTPClient(port, host, options) {
+    Stream.call(this);
+    this.writable = true;
+    this.readable = true;
+
+    this.stage = 'init';
+
+    this.options = options || {};
+
+    this.port = port || (this.options.secureConnection ? 465 : 25);
+    this.host = host || 'localhost';
+
+    this.options.secureConnection = !! this.options.secureConnection;
+    this.options.auth = this.options.auth || false;
+    this.options.maxConnections = this.options.maxConnections || 5;
+    this.options.enableDotEscaping = this.options.enableDotEscaping || false;
+
+    this._closing = false;
+
+    if (!this.options.name) {
+        // defaul hostname is machine hostname or [IP]
+        var defaultHostname = (oslib.hostname && oslib.hostname()) || '';
+
+        if (defaultHostname.indexOf('.') < 0) {
+            defaultHostname = '[127.0.0.1]';
+        }
+        if (defaultHostname.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
+            defaultHostname = '[' + defaultHostname + ']';
+        }
+
+        this.options.name = defaultHostname;
+    }
+
+    this._init();
+}
+utillib.inherits(SMTPClient, Stream);
+
+/**
+ * <p>Initializes instance variables</p>
+ */
+SMTPClient.prototype._init = function() {
+    /**
+     * Defines if the current connection is secure or not. If not,
+     * STARTTLS can be used if available
+     * @private
+     */
+    this._secureMode = false;
+
+    /**
+     * Ignore incoming data on TLS negotiation
+     * @private
+     */
+    this._ignoreData = false;
+
+    /**
+     * Store incomplete messages coming from the server
+     * @private
+     */
+    this._remainder = '';
+
+    /**
+     * If set to true, then this object is no longer active
+     * @private
+     */
+    this.destroyed = false;
+
+    /**
+     * The socket connecting to the server
+     * @publick
+     */
+    this.socket = false;
+
+    /**
+     * Lists supported auth mechanisms
+     * @private
+     */
+    this._supportedAuth = [];
+
+    /**
+     * Currently in data transfer state
+     * @private
+     */
+    this._dataMode = false;
+
+    /**
+     * Keep track if the client sends a leading \r\n in data mode
+     * @private
+     */
+    this._lastDataBytes = new Buffer(2);
+    this._lastDataBytes[0] = 0x0D;
+    this._lastDataBytes[1] = 0x0A;
+
+
+    /**
+     * Function to run if a data chunk comes from the server
+     * @private
+     */
+    this._currentAction = false;
+
+    /**
+     * Timeout variable for waiting the greeting
+     * @private
+     */
+    this._greetingTimeout = false;
+
+    /**
+     * Timeout variable for waiting the connection to start
+     * @private
+     */
+    this._connectionTimeout = false;
+
+    if (this.options.ignoreTLS || this.options.secureConnection) {
+        this._secureMode = true;
+    }
+
+    /**
+     * XOAuth2 token generator if XOAUTH2 auth is used
+     * @private
+     */
+    this._xoauth2 = false;
+
+    if (typeof this.options.auth.XOAuth2 == 'object' && typeof this.options.auth.XOAuth2.getToken == 'function') {
+        this._xoauth2 = this.options.auth.XOAuth2;
+    } else if (typeof this.options.auth.XOAuth2 == 'object') {
+        if (!this.options.auth.XOAuth2.user && this.options.auth.user) {
+            this.options.auth.XOAuth2.user = this.options.auth.user;
+        }
+        this._xoauth2 = xoauth2.createXOAuth2Generator(this.options.auth.XOAuth2);
+    }
+};
+
+/**
+ * <p>Creates a connection to a SMTP server and sets up connection
+ * listener</p>
+ */
+SMTPClient.prototype.connect = function() {
+    var opts = {};
+    if (this.options.secureConnection) {
+        if (this.options.tls) {
+            Object.keys(this.options.tls).forEach((function(key) {
+                opts[key] = this.options.tls[key];
+            }).bind(this));
+        }
+
+        if (!('rejectUnauthorized' in opts)) {
+            opts.rejectUnauthorized = !! this.options.rejectUnauthorized;
+        }
+
+        if (this.options.localAddress) {
+            opts.localAddress = this.options.localAddress;
+        }
+
+        this.socket = tls.connect(this.port, this.host, opts, this._onConnect.bind(this));
+    } else {
+        opts = {
+            port: this.port,
+            host: this.host
+        };
+        if (this.options.localAddress) {
+            opts.localAddress = this.options.localAddress;
+        }
+        this.socket = net.connect(opts, this._onConnect.bind(this));
+    }
+
+    if (this.options.connectionTimeout) {
+        this._connectionTimeout = setTimeout((function() {
+            var error = new Error('Connection timeout');
+            error.code = 'ETIMEDOUT';
+            error.errno = 'ETIMEDOUT';
+            error.stage = this.stage;
+            this.emit('error', error);
+            this.close();
+        }).bind(this), this.options.connectionTimeout);
+    }
+
+    this.socket.on('drain', this._onDrain.bind(this));
+
+    this.socket.on('error', this._onError.bind(this));
+};
+
+/**
+ * <p>Upgrades the connection to TLS</p>
+ *
+ * @param {Function} callback Callback function to run when the connection
+ *        has been secured
+ */
+SMTPClient.prototype._upgradeConnection = function(callback) {
+    this._ignoreData = true;
+    this.socket.removeAllListeners('data');
+    this.socket.removeAllListeners('error');
+
+    var opts = {
+        socket: this.socket,
+        host: this.host,
+        rejectUnauthorized: !! this.options.rejectUnauthorized
+    };
+
+    Object.keys(this.options.tls || {}).forEach((function(key) {
+        opts[key] = this.options.tls[key];
+    }).bind(this));
+
+    this.socket = tls.connect(opts, (function() {
+        this._ignoreData = false;
+        this._secureMode = true;
+        this.socket.on('data', this._onData.bind(this));
+
+        return callback(null, true);
+    }).bind(this));
+    this.socket.on('error', this._onError.bind(this));
+};
+
+/**
+ * <p>Connection listener that is run when the connection to
+ * the server is opened</p>
+ *
+ * @event
+ */
+SMTPClient.prototype._onConnect = function() {
+    this.stage = 'connect';
+
+    clearTimeout(this._connectionTimeout);
+
+    if ('setKeepAlive' in this.socket) {
+        this.socket.setKeepAlive(true);
+    }
+
+    if ('setNoDelay' in this.socket) {
+        this.socket.setNoDelay(true);
+    }
+
+    this.socket.on('data', this._onData.bind(this));
+    this.socket.on('close', this._onClose.bind(this));
+    this.socket.on('end', this._onEnd.bind(this));
+
+    this.socket.setTimeout(this.options.socketTimeout || (3 * 3600 * 1000)); // 1 hours
+    this.socket.on('timeout', this._onTimeout.bind(this));
+
+    this._greetingTimeout = setTimeout((function() {
+        // if still waiting for greeting, give up
+        if (this.socket && !this._destroyed && this._currentAction == this._actionGreeting) {
+            var error = new Error('Greeting never received');
+            error.code = 'ETIMEDOUT';
+            error.errno = 'ETIMEDOUT';
+            error.stage = this.stage;
+            this.emit('error', error);
+            this.close();
+        }
+    }).bind(this), this.options.greetingTimeout || 10000);
+
+    this._currentAction = this._actionGreeting;
+};
+
+/**
+ * <p>Destroys the client - removes listeners etc.</p>
+ */
+SMTPClient.prototype._destroy = function() {
+    if (this._destroyed) {
+        return;
+    }
+    this._destroyed = true;
+    this._ignoreData = true;
+    this.emit('end');
+    this.removeAllListeners();
+    // keep the error handler around, just in case
+    this.socket.on('error', this._onError.bind(this));
+};
+
+/**
+ * <p>'data' listener for data coming from the server</p>
+ *
+ * @event
+ * @param {Buffer} chunk Data chunk coming from the server
+ */
+SMTPClient.prototype._onData = function(chunk) {
+    var str;
+
+    if (this._ignoreData || !chunk || !chunk.length) {
+        return;
+    }
+
+    // Wait until end of line
+    if (chunk.readUInt8(chunk.length - 1) != 0x0A) {
+        this._remainder += chunk.toString();
+        return;
+    } else {
+        str = (this._remainder + chunk.toString()).trim();
+        this._remainder = '';
+    }
+
+    // if this is a multi line reply, wait until the ending
+    if (str.match(/(?:^|\n)\d{3}-.+$/)) {
+        this._remainder = str + '\r\n';
+        return;
+    }
+
+    if (this.options.debug) {
+        console.log('SERVER' + (this.options.instanceId ? ' ' +
+            this.options.instanceId : '') + ':\n└──' + str.replace(/\r?\n/g, '\n   '));
+    }
+    if (this.options.logFile) {
+        this.log('SERVER' + (this.options.instanceId ? ' ' +
+            this.options.instanceId : '') + ':\n└──' + str.replace(/\r?\n/g, '\n   '));
+    }
+
+    if (typeof this._currentAction == 'function') {
+        this._currentAction.call(this, str);
+    }
+};
+
+/**
+ * <p>'error' listener for the socket</p>
+ *
+ * @event
+ * @param {Error} err Error object
+ * @param {String} type Error name
+ */
+SMTPClient.prototype._onError = function(err, type, data) {
+    if (type && type != 'Error') {
+        err.name = type;
+    }
+    if (data) {
+        err.data = data;
+    }
+    err.stage = this.stage;
+    this.emit('error', err);
+    this.close();
+};
+
+/**
+ * <p>'drain' listener for the socket</p>
+ *
+ * @event
+ */
+SMTPClient.prototype._onDrain = function() {
+    this.emit('drain');
+};
+
+
+/**
+ * <p>'close' listener for the socket</p>
+ *
+ * @event
+ */
+SMTPClient.prototype._onClose = function() {
+    if ([this._actionGreeting, this._actionIdle, this.close].indexOf(this._currentAction) < 0 && !this._destroyed) {
+        return this._onError(new Error('Connection closed unexpectedly'));
+    }
+
+    this.stage = 'close';
+
+    this._destroy();
+};
+
+/**
+ * <p>'end' listener for the socket</p>
+ *
+ * @event
+ */
+SMTPClient.prototype._onEnd = function() {
+    this.stage = 'end';
+
+    this._destroy();
+};
+
+/**
+ * <p>'timeout' listener for the socket</p>
+ *
+ * @event
+ */
+SMTPClient.prototype._onTimeout = function() {
+    this.close();
+};
+
+/**
+ * <p>Passes data stream to socket if in data mode</p>
+ *
+ * @param {Buffer} chunk Chunk of data to be sent to the server
+ */
+SMTPClient.prototype.write = function(chunk) {
+    // works only in data mode
+    if (!this._dataMode || this._destroyed) {
+        // this line should never be reached but if it does, then
+        // say act like everything's normal.
+        return true;
+    }
+
+    if (typeof chunk == 'string') {
+        chunk = new Buffer(chunk, 'utf-8');
+    }
+
+    if (!this.options.enableDotEscaping) {
+        if (chunk.length >= 2) {
+            this._lastDataBytes[0] = chunk[chunk.length - 2];
+            this._lastDataBytes[1] = chunk[chunk.length - 1];
+        } else if (chunk.length == 1) {
+            this._lastDataBytes[0] = this._lastDataBytes[1];
+            this._lastDataBytes[1] = chunk[0];
+        }
+    } else {
+        chunk = this._escapeDot(chunk);
+    }
+
+    if (this.options.debug) {
+        console.log('CLIENT (DATA)' + (this.options.instanceId ? ' ' +
+            this.options.instanceId : '') + ':\n└──' + chunk.toString().trim().replace(/\n/g, '\n   '));
+    }
+    if (this.options.logFile) {
+        this.log('CLIENT (DATA)' + (this.options.instanceId ? ' ' +
+            this.options.instanceId : '') + ':\n└──' + chunk.toString().trim().replace(/\n/g, '\n   '));
+    }
+
+    // pass the chunk to the socket
+    return this.socket.write(chunk);
+};
+
+/**
+ * <p>Indicates that a data stream for the socket is ended. Works only
+ * in data mode.</p>
+ *
+ * @param {Buffer} [chunk] Chunk of data to be sent to the server
+ */
+SMTPClient.prototype.end = function(chunk) {
+    // works only in data mode
+    if (!this._dataMode || this._destroyed) {
+        // this line should never be reached but if it does, then
+        // say act like everything's normal.
+        return true;
+    }
+
+    if (chunk && chunk.length) {
+        this.write(chunk);
+    }
+
+    // redirect output from the server to _actionStream
+    this._currentAction = this._actionStream;
+
+    // indicate that the stream has ended by sending a single dot on its own line
+    // if the client already closed the data with \r\n no need to do it again
+    if (this._lastDataBytes[0] == 0x0D && this._lastDataBytes[1] == 0x0A) {
+        this.socket.write(new Buffer('.\r\n', 'utf-8'));
+    } else if (this._lastDataBytes[1] == 0x0D) {
+        this.socket.write(new Buffer('\n.\r\n'));
+    } else {
+        this.socket.write(new Buffer('\r\n.\r\n'));
+    }
+    this._lastDataBytes[0] = 0x0D;
+    this._lastDataBytes[1] = 0x0A;
+
+
+    // end data mode
+    this._dataMode = false;
+};
+
+/**
+ * <p>Send a command to the server, append \r\n</p>
+ *
+ * @param {String} str String to be sent to the server
+ */
+SMTPClient.prototype.sendCommand = function(str) {
+    if (this._destroyed) {
+        // Connection already closed, can't send any more data
+        return;
+    }
+    if (this.socket.destroyed) {
+        return this.close();
+    }
+    if (this.options.debug) {
+        console.log('CLIENT' + (this.options.instanceId ? ' ' +
+            this.options.instanceId : '') + ':\n└──' + (str || '').toString().trim().replace(/\n/g, '\n   '));
+    }
+    if (this.options.logFile) {
+        this.log('CLIENT' + (this.options.instanceId ? ' ' +
+            this.options.instanceId : '') + ':\n└──' + (str || '').toString().trim().replace(/\n/g, '\n   '));
+    }
+    this.socket.write(new Buffer(str + '\r\n', 'utf-8'));
+};
+
+/**
+ * <p>Sends QUIT</p>
+ */
+SMTPClient.prototype.quit = function() {
+    this._closing = true;
+    this.sendCommand('QUIT');
+    this._currentAction = this.close;
+};
+
+/**
+ * <p>Closes the connection to the server</p>
+ */
+SMTPClient.prototype.close = function() {
+    this._closing = true;
+
+    if (this.options.debug) {
+        console.log('Closing connection to the server');
+    }
+
+    if (this.options.logFile) {
+        this.log('Closing connection to the server');
+    }
+
+    var closeMethod = 'end';
+
+    // Clear current job
+    this._currentAction = this._actionIdle;
+
+    if (this.stage === 'init') {
+        // Clear connection timeout timer if other than timeout error occurred
+        clearTimeout(this._connectionTimeout);
+        // Close the socket immediately when connection timed out
+        closeMethod = 'destroy';
+    }
+
+    if (this.socket && this.socket.socket && this.socket.socket[closeMethod] && !this.socket.socket.destroyed) {
+        this.socket.socket[closeMethod]();
+    }
+    if (this.socket && this.socket[closeMethod] && !this.socket.destroyed) {
+        this.socket[closeMethod]();
+    }
+    this._destroy();
+};
+
+/**
+ * <p>Initiates a new message by submitting envelope data, starting with
+ * <code>MAIL FROM:</code> command</p>
+ *
+ * @param {Object} envelope Envelope object in the form of
+ *        <code>{from:'...', to:['...']}</code>
+ *        or
+ *        <code>{from:{address:'...',name:'...'}, to:[address:'...',name:'...']}</code>
+ */
+SMTPClient.prototype.useEnvelope = function(envelope) {
+    this._envelope = envelope || {};
+    this._envelope.from = this._envelope.from && this._envelope.from.address || this._envelope.from || ('anonymous@' + this.options.name);
+
+    this._envelope.to = [].concat(this._envelope.to || []).map(function(to) {
+        return to && to.address || to;
+    });
+
+    // clone the recipients array for latter manipulation
+    this._envelope.rcptQueue = JSON.parse(JSON.stringify(this._envelope.to || []));
+    this._envelope.rcptFailed = [];
+
+    this._currentAction = this._actionMAIL;
+    this.sendCommand('MAIL FROM:<' + (this._envelope.from) + '>');
+};
+
+/**
+ * <p>If needed starts the authentication, if not emits 'idle' to
+ * indicate that this client is ready to take in an outgoing mail</p>
+ */
+SMTPClient.prototype._authenticateUser = function() {
+    this.stage = 'auth';
+
+    if (!this.options.auth) {
+        // no need to authenticate, at least no data given
+        this._enterIdle();
+        return;
+    }
+
+    var auth;
+    if (this.options.auth.XOAuthToken && this._supportedAuth.indexOf('XOAUTH') >= 0) {
+        auth = 'XOAUTH';
+    } else if (this._xoauth2 && this._supportedAuth.indexOf('XOAUTH2') >= 0) {
+        auth = 'XOAUTH2';
+    } else if (this.options.authMethod) {
+        auth = this.options.authMethod.toUpperCase().trim();
+    } else {
+        // use first supported
+        auth = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
+    }
+
+    switch (auth) {
+        case 'XOAUTH':
+            this._currentAction = this._actionAUTHComplete;
+
+            if (typeof this.options.auth.XOAuthToken == 'object' &&
+                typeof this.options.auth.XOAuthToken.generate == 'function') {
+                this.options.auth.XOAuthToken.generate((function(err, XOAuthToken) {
+                    if (this._destroyed) {
+                        // Nothing to do here anymore, connection already closed
+                        return;
+                    }
+                    if (err) {
+                        return this._onError(err, 'XOAuthTokenError');
+                    }
+                    this.sendCommand('AUTH XOAUTH ' + XOAuthToken);
+                }).bind(this));
+            } else {
+                this.sendCommand('AUTH XOAUTH ' + this.options.auth.XOAuthToken.toString());
+            }
+            return;
+        case 'XOAUTH2':
+            this._currentAction = this._actionAUTHComplete;
+            this._xoauth2.getToken((function(err, token) {
+                if (this._destroyed) {
+                    // Nothing to do here anymore, connection already closed
+                    return;
+                }
+                if (err) {
+                    this._onError(err, 'XOAUTH2Error');
+                    return;
+                }
+                this.sendCommand('AUTH XOAUTH2 ' + token);
+            }).bind(this));
+            return;
+        case 'LOGIN':
+            this._currentAction = this._actionAUTH_LOGIN_USER;
+            this.sendCommand('AUTH LOGIN');
+            return;
+        case 'PLAIN':
+            this._currentAction = this._actionAUTHComplete;
+            this.sendCommand('AUTH PLAIN ' + new Buffer(
+                //this.options.auth.user+'\u0000'+
+                '\u0000' + // skip authorization identity as it causes problems with some servers
+                this.options.auth.user + '\u0000' +
+                this.options.auth.pass, 'utf-8').toString('base64'));
+            return;
+        case 'CRAM-MD5':
+            this._currentAction = this._actionAUTH_CRAM_MD5;
+            this.sendCommand('AUTH CRAM-MD5');
+            return;
+    }
+
+    this._onError(new Error('Unknown authentication method - ' + auth), 'UnknowAuthError');
+};
+
+/** ACTIONS **/
+
+/**
+ * <p>Will be run after the connection is created and the server sends
+ * a greeting. If the incoming message starts with 220 initiate
+ * SMTP session by sending EHLO command</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionGreeting = function(str) {
+    this.stage = 'greeting';
+
+    clearTimeout(this._greetingTimeout);
+
+    if (str.substr(0, 3) != '220') {
+        this._onError(new Error('Invalid greeting from server - ' + str), false, str);
+        return;
+    }
+
+    this._currentAction = this._actionEHLO;
+    this.sendCommand('EHLO ' + this.options.name);
+};
+
+/**
+ * <p>Handles server response for EHLO command. If it yielded in
+ * error, try HELO instead, otherwise initiate TLS negotiation
+ * if STARTTLS is supported by the server or move into the
+ * authentication phase.</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionEHLO = function(str) {
+    this.stage = 'ehlo';
+
+    if (str.substr(0, 3) == '421') {
+        this._onError(new Error('Server terminates connection - ' + str), false, str);
+        return;
+    }
+
+    if (str.charAt(0) != '2') {
+        // Try HELO instead
+        this._currentAction = this._actionHELO;
+        this.sendCommand('HELO ' + this.options.name);
+        return;
+    }
+
+    // Detect if the server supports STARTTLS
+    if (!this._secureMode && str.match(/[ \-]STARTTLS\r?$/mi)) {
+        this.sendCommand('STARTTLS');
+        this._currentAction = this._actionSTARTTLS;
+        return;
+    }
+
+    // Detect if the server supports PLAIN auth
+    if (str.match(/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)PLAIN/i)) {
+        this._supportedAuth.push('PLAIN');
+    }
+
+    // Detect if the server supports LOGIN auth
+    if (str.match(/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)LOGIN/i)) {
+        this._supportedAuth.push('LOGIN');
+    }
+
+    // Detect if the server supports CRAM-MD5 auth
+    if (str.match(/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)CRAM-MD5/i)) {
+        this._supportedAuth.push('CRAM-MD5');
+    }
+
+    // Detect if the server supports XOAUTH auth
+    if (str.match(/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)XOAUTH/i)) {
+        this._supportedAuth.push('XOAUTH');
+    }
+
+    // Detect if the server supports XOAUTH2 auth
+    if (str.match(/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)XOAUTH2/i)) {
+        this._supportedAuth.push('XOAUTH2');
+    }
+
+    this._authenticateUser.call(this);
+};
+
+/**
+ * <p>Handles server response for HELO command. If it yielded in
+ * error, emit 'error', otherwise move into the authentication phase.</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionHELO = function(str) {
+    this.stage = 'helo';
+
+    if (str.charAt(0) != '2') {
+        this._onError(new Error('Invalid response for EHLO/HELO - ' + str), false, str);
+        return;
+    }
+    this._authenticateUser.call(this);
+};
+
+/**
+ * <p>Handles server response for STARTTLS command. If there's an error
+ * try HELO instead, otherwise initiate TLS upgrade. If the upgrade
+ * succeedes restart the EHLO</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionSTARTTLS = function(str) {
+    this.stage = 'starttls';
+
+    if (str.charAt(0) != '2') {
+        // Try HELO instead
+        this._currentAction = this._actionHELO;
+        this.sendCommand('HELO ' + this.options.name);
+        return;
+    }
+
+    this._upgradeConnection((function(err, secured) {
+        if (err) {
+            this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'TLSError');
+            return;
+        }
+        if (this.options.debug) {
+            console.log('Connection secured');
+        }
+        if (this.options.logFile) {
+            this.log('Connection secured');
+        }
+
+        if (secured) {
+            // restart session
+            this._currentAction = this._actionEHLO;
+            this.sendCommand('EHLO ' + this.options.name);
+        } else {
+            this._authenticateUser.call(this);
+        }
+    }).bind(this));
+};
+
+/**
+ * <p>Handle the response for AUTH LOGIN command. We are expecting
+ * '334 VXNlcm5hbWU6' (base64 for 'Username:'). Data to be sent as
+ * response needs to be base64 encoded username.</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionAUTH_LOGIN_USER = function(str) {
+    if (str != '334 VXNlcm5hbWU6') {
+        this._onError(new Error('Invalid login sequence while waiting for "334 VXNlcm5hbWU6" - ' + str), false, str);
+        return;
+    }
+    this._currentAction = this._actionAUTH_LOGIN_PASS;
+    this.sendCommand(new Buffer(
+        this.options.auth.user + '', 'utf-8').toString('base64'));
+};
+
+/**
+ * <p>Handle the response for AUTH CRAM-MD5 command. We are expecting
+ * '334 <challenge string>'. Data to be sent as response needs to be
+ * base64 decoded challenge string, MD5 hashed using the password as
+ * a HMAC key, prefixed by the username and a space, and finally all
+ * base64 encoded again.</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionAUTH_CRAM_MD5 = function(str) {
+    var challengeMatch = str.match(/^334\s+(.+)$/),
+        challengeString = '';
+
+    if (!challengeMatch) {
+        this._onError(new Error('Invalid login sequence while waiting for server challenge string - ' + str), false, str);
+        return;
+    } else {
+        challengeString = challengeMatch[1];
+    }
+
+    // Decode from base64
+    var base64decoded = new Buffer(challengeString, 'base64').toString('ascii'),
+        hmac_md5 = crypto.createHmac('md5', this.options.auth.pass);
+    hmac_md5.update(base64decoded);
+    var hex_hmac = hmac_md5.digest('hex'),
+        prepended = this.options.auth.user + ' ' + hex_hmac;
+
+    this._currentAction = this._actionAUTH_CRAM_MD5_PASS;
+
+    this.sendCommand(new Buffer(prepended).toString('base64'));
+};
+
+/**
+ * <p>Handles the response to CRAM-MD5 authentication, if there's no error,
+ * the user can be considered logged in. Emit 'idle' and start
+ * waiting for a message to send</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionAUTH_CRAM_MD5_PASS = function(str) {
+    if (!str.match(/^235\s+/)) {
+        this._onError(new Error('Invalid login sequence while waiting for "235 go ahead" - ' + str), false, str);
+        return;
+    }
+    this._enterIdle();
+};
+
+/**
+ * <p>Handle the response for AUTH LOGIN command. We are expecting
+ * '334 UGFzc3dvcmQ6' (base64 for 'Password:'). Data to be sent as
+ * response needs to be base64 encoded password.</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionAUTH_LOGIN_PASS = function(str) {
+    if (str != '334 UGFzc3dvcmQ6') {
+        this._onError(new Error('Invalid login sequence while waiting for "334 UGFzc3dvcmQ6" - ' + str), false, str);
+        return;
+    }
+    this._currentAction = this._actionAUTHComplete;
+    this.sendCommand(new Buffer(this.options.auth.pass + '', 'utf-8').toString('base64'));
+};
+
+/**
+ * <p>Handles the response for authentication, if there's no error,
+ * the user can be considered logged in. Emit 'idle' and start
+ * waiting for a message to send</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionAUTHComplete = function(str) {
+    var response;
+
+    if (this._xoauth2 && str.substr(0, 3) == '334') {
+        try {
+            response = str.split(' ');
+            response.shift();
+            response = JSON.parse(new Buffer(response.join(' '), 'base64').toString('utf-8'));
+
+            if ((!this._xoauth2.reconnectCount || this._xoauth2.reconnectCount < 200) && ['400', '401'].indexOf(response.status) >= 0) {
+                this._xoauth2.reconnectCount = (this._xoauth2.reconnectCount || 0) + 1;
+                this._currentAction = this._actionXOAUTHRetry;
+            } else {
+                this._xoauth2.reconnectCount = 0;
+                this._currentAction = this._actionAUTHComplete;
+            }
+            this.sendCommand(new Buffer(0));
+            return;
+
+        } catch (E) {}
+    }
+
+    if(this._xoauth2){
+        this._xoauth2.reconnectCount = 0;
+    }
+
+    if (str.charAt(0) != '2') {
+        this._onError(new Error('Invalid login - ' + str), 'AuthError', str);
+        return;
+    }
+
+    this._enterIdle();
+};
+
+/**
+ * If XOAUTH2 authentication failed, try again by generating
+ * new access token
+ */
+SMTPClient.prototype._actionXOAUTHRetry = function() {
+
+    // ensure that something is listening unexpected responses
+    this._currentAction = this._actionIdle;
+
+    this._xoauth2.generateToken((function(err, token) {
+        if (this._destroyed) {
+            // Nothing to do here anymore, connection already closed
+            return;
+        }
+        if (err) {
+            this._onError(err, 'XOAUTH2Error');
+            return;
+        }
+        this._currentAction = this._actionAUTHComplete;
+        this.sendCommand('AUTH XOAUTH2 ' + token);
+    }).bind(this));
+};
+
+/**
+ * <p>This function is not expected to run. If it does then there's probably
+ * an error (timeout etc.)</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionIdle = function(str) {
+    this.stage = 'idle';
+
+    if (Number(str.charAt(0)) > 3) {
+        this._onError(new Error(str), false, str);
+        return;
+    }
+
+    // this line should never get called
+};
+
+/**
+ * <p>Handle response for a <code>MAIL FROM:</code> command</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionMAIL = function(str) {
+    this.stage = 'mail';
+
+    if (Number(str.charAt(0)) != '2') {
+        this._onError(new Error('Mail from command failed - ' + str), 'SenderError', str);
+        return;
+    }
+
+    if (!this._envelope.rcptQueue.length) {
+        this._onError(new Error('Can\'t send mail - no recipients defined'), 'RecipientError', str);
+    } else {
+        this._envelope.curRecipient = this._envelope.rcptQueue.shift();
+        this._currentAction = this._actionRCPT;
+        this.sendCommand('RCPT TO:<' + this._envelope.curRecipient + '>' + this._getDSN());
+    }
+};
+
+/**
+ * Emits 'idle'
+ */
+SMTPClient.prototype._enterIdle = function() {
+    this._currentAction = this._actionIdle;
+    this.emit('idle'); // ready to take orders
+};
+
+/**
+ * <p>SetsUp DSN</p>
+ */
+SMTPClient.prototype._getDSN = function() {
+    var ret = '',
+        n = [],
+        dsn;
+
+    if (this.currentMessage && this.currentMessage.options && 'dsn' in this.currentMessage.options) {
+        dsn = this.currentMessage.options.dsn;
+
+        if (dsn.success) {
+            n.push('SUCCESS');
+        }
+
+        if (dsn.failure) {
+            n.push('FAILURE');
+        }
+
+        if (dsn.delay) {
+            n.push('DELAY');
+        }
+
+        if (n.length > 0) {
+            ret = ' NOTIFY=' + n.join(',') + ' ORCPT=rfc822;' + this.currentMessage._message.from;
+        }
+    }
+
+    return ret;
+};
+
+/**
+ * <p>Handle response for a <code>RCPT TO:</code> command</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionRCPT = function(str) {
+    this.stage = 'rcpt';
+
+    if (Number(str.charAt(0)) != '2') {
+        // this is a soft error
+        this._envelope.rcptFailed.push(this._envelope.curRecipient);
+    }
+
+    if (!this._envelope.rcptQueue.length) {
+        if (this._envelope.rcptFailed.length < this._envelope.to.length) {
+            if (this._envelope.rcptFailed.length) {
+                this.emit('rcptFailed', this._envelope.rcptFailed);
+            }
+            this._currentAction = this._actionDATA;
+            this.sendCommand('DATA');
+        } else {
+            this._onError(new Error('Can\'t send mail - all recipients were rejected'), 'RecipientError', str);
+            return;
+        }
+    } else {
+        this._envelope.curRecipient = this._envelope.rcptQueue.shift();
+        this._currentAction = this._actionRCPT;
+        this.sendCommand('RCPT TO:<' + this._envelope.curRecipient + '>');
+    }
+};
+
+/**
+ * <p>Handle response for a <code>DATA</code> command</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionDATA = function(str) {
+    this.stage = 'data';
+
+    // response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24
+    // some servers might use 250 instead, so lets check for 2 or 3 as the first digit
+    if ([2, 3].indexOf(Number(str.charAt(0))) < 0) {
+        this._onError(new Error('Data command failed - ' + str), false, str);
+        return;
+    }
+
+    // Emit that connection is set up for streaming
+    this._dataMode = true;
+    this._currentAction = this._actionIdle;
+    this.emit('message');
+};
+
+/**
+ * <p>Handle response for a <code>DATA</code> stream</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionStream = function(str) {
+    if (Number(str.charAt(0)) != '2') {
+        // Message failed
+        this.emit('ready', false, str);
+    } else {
+        // Message sent succesfully
+        this.emit('ready', true, str);
+    }
+
+    // Waiting for new connections
+    this._currentAction = this._actionIdle;
+
+    if (typeof setImmediate == 'function') {
+        setImmediate(this._enterIdle.bind(this));
+    } else {
+        process.nextTick(this._enterIdle.bind(this));
+    }
+};
+
+/**
+ * <p>Log debugs to given file</p>
+ *
+ * @param {String} str Log message
+ */
+SMTPClient.prototype.log = function(str) {
+    fs.appendFile(this.options.logFile, str + '\n', function(err) {
+        if (err) {
+            console.log('Log write failed. Data to log: ' + str);
+        }
+    });
+};
+
+/**
+ * <p>Inserts an extra dot at the begining of a line if it starts with a dot
+ * See RFC 2821 Section 4.5.2</p>
+ *
+ * @param {Buffer} chunk The chunk that will be send.
+ */
+SMTPClient.prototype._escapeDot = function(chunk) {
+    var pos, OutBuff, i;
+    OutBuff = new Buffer(chunk.length * 2);
+    pos = 0;
+
+    for (i = 0; i < chunk.length; i++) {
+        if (this._lastDataBytes[0] == 0x0D && this._lastDataBytes[1] == 0x0A && chunk[i] == 0x2E) {
+            OutBuff[pos] = 0x2E;
+            pos += 1;
+        }
+        OutBuff[pos] = chunk[i];
+        pos += 1;
+        this._lastDataBytes[0] = this._lastDataBytes[1];
+        this._lastDataBytes[1] = chunk[i];
+    }
+
+    return OutBuff.slice(0, pos);
+};
\ No newline at end of file
diff --git a/lib/pool.js b/lib/pool.js
new file mode 100644
index 0000000..6ddd66e
--- /dev/null
+++ b/lib/pool.js
@@ -0,0 +1,374 @@
+'use strict';
+
+var simplesmtp = require('../index'),
+    EventEmitter = require('events').EventEmitter,
+    utillib = require('util'),
+    xoauth2 = require('xoauth2');
+
+// expose to the world
+module.exports = function(port, host, options) {
+    var pool = new SMTPConnectionPool(port, host, options);
+    return pool;
+};
+
+/**
+ * <p>Creates a SMTP connection pool</p>
+ *
+ * <p>Optional options object takes the following possible properties:</p>
+ * <ul>
+ *     <li><b>secureConnection</b> - use SSL</li>
+ *     <li><b>name</b> - the name of the client server</li>
+ *     <li><b>auth</b> - authentication object <code>{user:'...', pass:'...'}</code>
+ *     <li><b>ignoreTLS</b> - ignore server support for STARTTLS</li>
+ *     <li><b>tls</b> - options for createCredentials</li>
+ *     <li><b>debug</b> - output client and server messages to console</li>
+ *     <li><b>maxConnections</b> - how many connections to keep in the pool</li>
+ * </ul>
+ *
+ * @constructor
+ * @namespace SMTP Client Pool module
+ * @param {Number} [port=25] The port number to connecto to
+ * @param {String} [host='localhost'] THe hostname to connect to
+ * @param {Object} [options] optional options object
+ */
+function SMTPConnectionPool(port, host, options) {
+    EventEmitter.call(this);
+
+    /**
+     * Port number to connect to
+     * @public
+     */
+    this.port = port || 25;
+
+    /**
+     * Hostname to connect to
+     * @public
+     */
+    this.host = host || 'localhost';
+
+    /**
+     * Options object
+     * @public
+     */
+    this.options = options || {};
+    this.options.maxConnections = this.options.maxConnections || 5;
+    this.options.maxMessages = this.options.maxMessages || Infinity;
+
+    /**
+     * An array of connections that are currently idle
+     * @private
+     */
+    this._connectionsAvailable = [];
+
+    /**
+     * An array of connections that are currently in use
+     * @private
+     */
+    this._connectionsInUse = [];
+
+    /**
+     * Message queue (FIFO)
+     * @private
+     */
+    this._messageQueue = [];
+
+    /**
+     * Counter for generating ID values for debugging
+     * @private
+     */
+    this._idgen = 0;
+
+    // Initialize XOAUTH2 if needed
+    if (this.options.auth && typeof this.options.auth.XOAuth2 == 'object') {
+        if (!this.options.auth.XOAuth2.user && this.options.auth.user) {
+            this.options.auth.XOAuth2.user = this.options.auth.user;
+        }
+        this.options.auth.XOAuth2 = xoauth2.createXOAuth2Generator(this.options.auth.XOAuth2);
+    }
+}
+utillib.inherits(SMTPConnectionPool, EventEmitter);
+
+/**
+ * <p>Sends a message. If there's any idling connections available
+ * use one to send the message immediatelly, otherwise add to queue.</p>
+ *
+ * @param {Object} message MailComposer object
+ * @param {Function} callback Callback function to run on finish, gets an
+ *        <code>error</code> object as a parameter if the sending failed
+ *        and on success an object with <code>failedRecipients</code> array as
+ *        a list of addresses that were rejected (if any) and
+ *        <code>message</code> which indicates the last message received from
+ *        the server
+ */
+SMTPConnectionPool.prototype.sendMail = function(message, callback) {
+    var connection;
+
+    message.returnCallback = callback;
+
+    if (this._connectionsAvailable.length) {
+        // if available connections pick one
+        connection = this._connectionsAvailable.pop();
+        this._connectionsInUse.push(connection);
+        this._processMessage(message, connection);
+    } else {
+        this._messageQueue.push(message);
+        if (this._connectionsAvailable.length + this._connectionsInUse.length < this.options.maxConnections) {
+            this._createConnection();
+        }
+    }
+};
+
+/**
+ * <p>Closes all connections</p>
+ */
+SMTPConnectionPool.prototype.close = function(callback) {
+    var connection;
+
+    // for some reason destroying the connections seem to be the only way :S
+    while (this._connectionsAvailable.length) {
+        connection = this._connectionsAvailable.pop();
+        connection.quit();
+    }
+
+    while (this._connectionsInUse.length) {
+        connection = this._connectionsInUse.pop();
+        connection.quit();
+    }
+
+    if (callback) {
+        if (typeof setImmediate == 'function') {
+            setImmediate(callback);
+        } else {
+            process.nextTick(callback);
+        }
+    }
+};
+
+/**
+ * <p>Initiates a connection to the SMTP server and adds it to the pool</p>
+ */
+SMTPConnectionPool.prototype._createConnection = function() {
+
+    var connectionOptions = {
+        instanceId: ++this._idgen,
+        debug: !! this.options.debug,
+        logFile: this.options.logFile,
+        ignoreTLS: !! this.options.ignoreTLS,
+        tls: this.options.tls || false,
+        auth: this.options.auth || false,
+        authMethod: this.options.authMethod,
+        name: this.options.name || false,
+        secureConnection: !! this.options.secureConnection
+    },
+        connection;
+
+    if ('greetingTimeout' in this.options) {
+        connectionOptions.greetingTimeout = this.options.greetingTimeout;
+    }
+
+    if ('socketTimeout' in this.options) {
+        connectionOptions.socketTimeout = this.options.socketTimeout;
+    }
+
+    if ('connectionTimeout' in this.options) {
+        connectionOptions.connectionTimeout = this.options.connectionTimeout;
+    }
+
+    if ('rejectUnathorized' in this.options) {
+        connectionOptions.rejectUnathorized = this.options.rejectUnathorized;
+    }
+
+    if ('localAddress' in this.options) {
+        connectionOptions.localAddress = this.options.localAddress;
+    }
+
+    connection = simplesmtp.connect(this.port, this.host, connectionOptions);
+
+    connection._messagesProcessed = 0;
+
+    connection.on('idle', this._onConnectionIdle.bind(this, connection));
+    connection.on('message', this._onConnectionMessage.bind(this, connection));
+    connection.on('ready', this._onConnectionReady.bind(this, connection));
+    connection.on('error', this._onConnectionError.bind(this, connection));
+    connection.on('end', this._onConnectionEnd.bind(this, connection));
+    connection.on('rcptFailed', this._onConnectionRCPTFailed.bind(this, connection));
+
+    this.emit('connectionCreated', connection);
+
+    // as the connection is not ready yet, add to 'in use' queue
+    this._connectionsInUse.push(connection);
+};
+
+/**
+ * <p>Processes a message by assigning it to a connection object and initiating
+ * the sending process by setting the envelope</p>
+ *
+ * @param {Object} message MailComposer message object
+ * @param {Object} connection <code>simplesmtp.connect</code> connection
+ */
+SMTPConnectionPool.prototype._processMessage = function(message, connection) {
+    connection.currentMessage = message;
+    message.currentConnection = connection;
+
+    connection._messagesProcessed++;
+
+    // send envelope
+    connection.useEnvelope(message.getEnvelope());
+};
+
+/**
+ * <p>Will be fired on <code>'idle'</code> events by the connection, if
+ * there's a message currently in queue</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ */
+SMTPConnectionPool.prototype._onConnectionIdle = function(connection) {
+    var message = this._messageQueue.shift();
+
+    if (message) {
+        this._processMessage(message, connection);
+    } else {
+        for (var i = 0, len = this._connectionsInUse.length; i < len; i++) {
+            if (this._connectionsInUse[i] == connection) {
+                this._connectionsInUse.splice(i, 1); // remove from list
+                break;
+            }
+        }
+        this._connectionsAvailable.push(connection);
+    }
+};
+
+/**
+ * <p>Will be called when not all recipients were accepted</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ * @param {Array} addresses Failed addresses as an array of strings
+ */
+SMTPConnectionPool.prototype._onConnectionRCPTFailed = function(connection, addresses) {
+    if (connection.currentMessage) {
+        connection.currentMessage.failedRecipients = addresses;
+    }
+};
+
+/**
+ * <p>Will be called when the client is waiting for a message to deliver</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ */
+SMTPConnectionPool.prototype._onConnectionMessage = function(connection) {
+    if (connection.currentMessage) {
+        connection.currentMessage.streamMessage();
+        connection.currentMessage.pipe(connection);
+    }
+};
+
+/**
+ * <p>Will be called when a message has been delivered</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ * @param {Boolean} success True if the message was queued by the SMTP server
+ * @param {String} message Last message received from the server
+ */
+SMTPConnectionPool.prototype._onConnectionReady = function(connection, success, message) {
+    var error, responseObj = {};
+
+    if (connection._messagesProcessed >= this.options.maxMessages && connection.socket) {
+
+        connection.emit('end');
+        connection.removeAllListeners();
+        if (connection.socket) {
+            connection.socket.destroy();
+        }
+
+        this.emit('released', connection);
+    }
+
+    if (connection.currentMessage && connection.currentMessage.returnCallback) {
+        if (success) {
+
+            if (connection.currentMessage.failedRecipients) {
+                responseObj.failedRecipients = connection.currentMessage.failedRecipients;
+            }
+
+            if (message) {
+                responseObj.message = message;
+            }
+
+            if (connection.currentMessage._messageId) {
+                responseObj.messageId = connection.currentMessage._messageId;
+            }
+
+            connection.currentMessage.returnCallback(null, responseObj);
+
+        } else {
+            error = new Error('Message delivery failed' + (message ? ': ' + message : ''));
+            error.name = 'DeliveryError';
+            error.data = message;
+            connection.currentMessage.returnCallback(error);
+        }
+    }
+    connection.currentMessage = false;
+};
+
+/**
+ * <p>Will be called when an error occurs</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ * @param {Object} error Error object
+ */
+SMTPConnectionPool.prototype._onConnectionError = function(connection, error) {
+    var message = connection.currentMessage;
+    connection.currentMessage = false;
+
+    // clear a first message from the list, otherwise an infinite loop will emerge
+    if (!message) {
+        message = this._messageQueue.shift();
+    }
+
+    if (message && message.returnCallback) {
+        message.returnCallback(error);
+    }
+};
+
+/**
+ * <p>Will be called when a connection to the client is closed</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ */
+SMTPConnectionPool.prototype._onConnectionEnd = function(connection) {
+    var removed = false,
+        i, len;
+
+    // if in 'available' list, remove
+    for (i = 0, len = this._connectionsAvailable.length; i < len; i++) {
+        if (this._connectionsAvailable[i] == connection) {
+            this._connectionsAvailable.splice(i, 1); // remove from list
+            removed = true;
+            break;
+        }
+    }
+
+    if (!removed) {
+        // if in 'in use' list, remove
+        for (i = 0, len = this._connectionsInUse.length; i < len; i++) {
+            if (this._connectionsInUse[i] == connection) {
+                this._connectionsInUse.splice(i, 1); // remove from list
+                removed = true;
+                break;
+            }
+        }
+    }
+
+    // if there's still unprocessed mail and available connection slots, create
+    // a new connection
+    if (this._messageQueue.length &&
+        this._connectionsInUse.length + this._connectionsAvailable.length < this.options.maxConnections) {
+        this._createConnection();
+    }
+};
\ No newline at end of file
diff --git a/lib/server.js b/lib/server.js
new file mode 100644
index 0000000..5aab9fc
--- /dev/null
+++ b/lib/server.js
@@ -0,0 +1,804 @@
+'use strict';
+
+/**
+ * @fileOverview This is the main file for the simplesmtp library to create custom SMTP servers
+ * @author <a href='mailto:andris at node.ee'>Andris Reinman</a>
+ */
+
+var RAIServer = require('rai').RAIServer,
+    EventEmitter = require('events').EventEmitter,
+    oslib = require('os'),
+    utillib = require('util'),
+    dnslib = require('dns'),
+    crypto = require('crypto');
+
+// expose to the world
+module.exports = function(options) {
+    return new SMTPServer(options);
+};
+
+/**
+ * <p>Constructs a SMTP server</p>
+ *
+ * <p>Possible options are:</p>
+ *
+ * <ul>
+ *     <li><b>name</b> - the hostname of the server, will be used for
+ *         informational messages</li>
+ *     <li><b>debug</b> - if set to true, print out messages about the connection</li>
+ *     <li><b>timeout</b> - client timeout in milliseconds, defaults to 60 000</li>
+ *     <li><b>secureConnection</b> - start a server on secure connection</li>
+ *     <li><b>SMTPBanner</b> - greeting banner that is sent to the client on connection</li>
+ *     <li><b>requireAuthentication</b> - if set to true, require that the client
+ *         must authenticate itself</li>
+ *     <li><b>enableAuthentication</b> - if set to true, client may authenticate itself but don't have to</li>
+ *     <li><b>maxSize</b> - maximum size of an e-mail in bytes</li>
+ *     <li><b>credentials</b> - TLS credentials</li>
+ *     <li><b>authMethods</b> - allowed authentication methods, defaults to <code>['PLAIN', 'LOGIN']</code></li>
+ *     <li><b>disableEHLO</b> - if set, support HELO only</li>
+ *     <li><b>ignoreTLS</b> - if set, allow client do not use STARTTLS</li>
+ *     <li><b>disableDNSValidation</b> - if set, do not validate sender domains</li>
+ *     <li><b>maxClients</b> - if set, limit the number of simultaneous connections to the server</li>
+ * </ul>
+ *
+ * @constructor
+ * @namespace SMTP Server module
+ * @param {Object} [options] Options object
+ */
+function SMTPServer(options) {
+    EventEmitter.call(this);
+
+    this.connectedClients = 0;
+    this.options = options || {};
+    this.options.name = this.options.name || (oslib.hostname && oslib.hostname()) ||
+        (oslib.getHostname && oslib.getHostname()) ||
+        '127.0.0.1';
+
+    this.options.authMethods = (this.options.authMethods || ['PLAIN', 'LOGIN']).map(
+        function(auth) {
+            return auth.toUpperCase().trim();
+        });
+
+    this.options.disableEHLO = !! this.options.disableEHLO;
+    this.options.ignoreTLS = !! this.options.ignoreTLS;
+
+    this.SMTPServer = new RAIServer({
+        secureConnection: !! this.options.secureConnection,
+        credentials: this.options.credentials,
+        timeout: this.options.timeout || 60 * 1000,
+        disconnectOnTimeout: false,
+        debug: !! this.options.debug
+    });
+
+    this.SMTPServer.on('connect', this._createSMTPServerConnection.bind(this));
+}
+utillib.inherits(SMTPServer, EventEmitter);
+
+/**
+ * Server starts listening on defined port and hostname
+ *
+ * @param {Number} port The port number to listen
+ * @param {String} [host] The hostname to listen
+ * @param {Function} callback The callback function to run when the server is listening
+ */
+SMTPServer.prototype.listen = function(port, host, callback) {
+    this.SMTPServer.listen(port, host, callback);
+};
+
+/**
+ * <p>Closes the server</p>
+ *
+ * @param {Function} callback The callback function to run when the server is closed
+ */
+SMTPServer.prototype.end = function(callback) {
+    this.SMTPServer.end(callback);
+};
+
+/**
+ * <p>Creates a new {@link SMTPServerConnection} object and links the main server with
+ * the client socket</p>
+ *
+ * @param {Object} client RAISocket object to a client
+ */
+SMTPServer.prototype._createSMTPServerConnection = function(client) {
+    new SMTPServerConnection(this, client);
+};
+
+/**
+ * <p>Sets up a handler for the connected client</p>
+ *
+ * <p>Restarts the state and sets up event listeners for client actions</p>
+ *
+ * @constructor
+ * @param {Object} server {@link SMTPServer} instance
+ * @param {Object} client RAISocket instance for the client
+ */
+function SMTPServerConnection(server, client) {
+    this.server = server;
+    this.client = client;
+
+    this.init();
+    this.server.connectedClients++;
+
+    if (!this.client.remoteAddress) {
+        if (this.server.options.debug) {
+            console.log('Client already disconnected');
+        }
+        this.client.end();
+        return;
+    }
+
+    if (this.server.options.debug) {
+        console.log('Connection from', this.client.remoteAddress);
+    }
+
+    this.client.on('timeout', this._onTimeout.bind(this));
+    this.client.on('error', this._onError.bind(this));
+    this.client.on('command', this._onCommand.bind(this));
+    this.client.on('end', this._onEnd.bind(this));
+
+    this.client.on('data', this._onData.bind(this));
+    this.client.on('ready', this._onDataReady.bind(this));
+
+    // Too many clients. Disallow processing
+    if (this.server.options.maxClients && this.server.connectedClients > this.server.options.maxClients) {
+        this.end('421 ' + this.server.options.name + ' ESMTP - Too many connections. Please try again later.');
+    } else {
+        // Send the greeting banner. Force ESMTP notice
+        this.client.send('220 ' + this.server.options.name + ' ESMTP ' + (this.server.options.SMTPBanner || 'node.js simplesmtp'));
+    }
+}
+
+/**
+ * <p>Reset the envelope state</p>
+ *
+ * <p>If <code>keepAuthData</code> is set to true, then doesn't remove
+ * authentication data</p>
+ *
+ * @param {Boolean} [keepAuthData=false] If set to true keep authentication data
+ */
+SMTPServerConnection.prototype.init = function(keepAuthData) {
+    if (this.envelope === undefined) {
+        this.envelope = {};
+    }
+
+    this.envelope.from = '';
+    this.envelope.to = [];
+    this.envelope.date = new Date();
+
+    if (this.hostNameAppearsAs) {
+        this.envelope.host = this.hostNameAppearsAs;
+    }
+
+    if (this.client.remoteAddress) {
+        this.envelope.remoteAddress = this.client.remoteAddress;
+    }
+
+    if (!keepAuthData) {
+        this.authentication = {
+            username: false,
+            authenticated: false,
+            state: 'NORMAL'
+        };
+    }
+
+    this.envelope.authentication = this.authentication;
+};
+
+/**
+ * <p>Sends a message to the client and closes the connection</p>
+ *
+ * @param {String} [message] if set, send it to the client before disconnecting
+ */
+SMTPServerConnection.prototype.end = function(message) {
+    if (message) {
+        this.client.send(message);
+    }
+    this.client.end();
+};
+
+/**
+ * <p>Will be called when the connection to the client is closed</p>
+ *
+ * @event
+ */
+SMTPServerConnection.prototype._onEnd = function() {
+    if (this.server.options.debug) {
+        console.log('Connection closed to', this.client.remoteAddress);
+    }
+    this.server.connectedClients--;
+    try {
+        this.client.end();
+    } catch (E) {}
+    this.server.emit('close', this.envelope);
+};
+
+/**
+ * <p>Will be called when timeout occurs</p>
+ *
+ * @event
+ */
+SMTPServerConnection.prototype._onTimeout = function() {
+    this.end('421 4.4.2 ' + this.server.options.name + ' Error: timeout exceeded');
+};
+
+/**
+ * <p>Will be called when an error occurs</p>
+ *
+ * @event
+ */
+SMTPServerConnection.prototype._onError = function() {
+    this.end('421 4.4.2 ' + this.server.options.name + ' Error: client error');
+};
+
+/**
+ * <p>Will be called when a command is received from the client</p>
+ *
+ * <p>If there's curently an authentication process going on, route
+ * the data to <code>_handleAuthLogin</code>, otherwise act as
+ * defined</p>
+ *
+ * @event
+ * @param {String} command Command
+ * @param {Buffer} command Payload related to the command
+ */
+SMTPServerConnection.prototype._onCommand = function(command, payload) {
+    if (this.authentication.state == 'AUTHPLAINUSERDATA') {
+        this._handleAuthPlain(command.toString('utf-8').trim().split(' '));
+        return;
+    }
+
+    if (this.authentication.state == 'AUTHENTICATING') {
+        this._handleAuthLogin(command);
+        return;
+    }
+
+    if (this.authentication.state == 'AUTHXOAUTH2') {
+        this._handleAuthXOAuth2(command);
+        return;
+    }
+
+    switch ((command || '').toString().trim().toUpperCase()) {
+
+        // Should not occur too often
+        case 'HELO':
+            this._onCommandHELO(payload.toString('utf-8').trim());
+            break;
+
+            // Lists server capabilities
+        case 'EHLO':
+            if (!this.server.options.disableEHLO) {
+                this._onCommandEHLO(payload.toString('utf-8').trim());
+            } else {
+                this.client.send('502 5.5.2 Error: command not recognized');
+            }
+            break;
+
+            // Closes the connection
+        case 'QUIT':
+            this.end('221 2.0.0 Goodbye!');
+            break;
+
+            // Resets the current state
+        case 'RSET':
+            this._onCommandRSET();
+            break;
+
+            // Doesn't work for spam related purposes
+        case 'VRFY':
+            this.client.send('252 2.1.5 Send some mail, I\'ll try my best');
+            break;
+
+            // Initiate an e-mail by defining a sender
+        case 'MAIL':
+            this._onCommandMAIL(payload.toString('utf-8').trim());
+            break;
+
+            // Add recipients to the e-mail envelope
+        case 'RCPT':
+            this._onCommandRCPT(payload.toString('utf-8').trim());
+            break;
+
+            // Authenticate if needed
+        case 'AUTH':
+            this._onCommandAUTH(payload);
+            break;
+
+            // Start accepting binary data stream
+        case 'DATA':
+            this._onCommandDATA();
+            break;
+
+            // Upgrade connection to secure TLS
+        case 'STARTTLS':
+            this._onCommandSTARTTLS();
+            break;
+
+            // No operation
+        case 'NOOP':
+            this._onCommandNOOP();
+            break;
+
+            // No operation
+        case '':
+            // ignore blank lines
+            break;
+
+            // Display an error on anything else
+        default:
+            this.client.send('502 5.5.2 Error: command not recognized');
+    }
+};
+
+/**
+ * <p>Initiate an e-mail by defining a sender.</p>
+ *
+ * <p>This doesn't work if authorization is required but the client is
+ * not logged in yet.</p>
+ *
+ * <p>If <code>validateSender</code> option is set to true, then emits
+ * <code>'validateSender'</code> and wait for the callback before moving
+ * on</p>
+ *
+ * @param {String} mail Address payload in the form of 'FROM:<address>'
+ */
+SMTPServerConnection.prototype._onCommandMAIL = function(mail) {
+    var self = this,
+        match,
+        email,
+        domain;
+
+    if (!this.hostNameAppearsAs) {
+        return this.client.send('503 5.5.1 Error: send HELO/EHLO first');
+    }
+
+    if (this.server.options.requireAuthentication && !this.authentication.authenticated) {
+        return this.client.send('530 5.5.1 Authentication Required');
+    }
+
+    if (this.envelope.from) {
+        return this.client.send('503 5.5.1 Error: nested MAIL command');
+    }
+
+    if (!(match = mail.match(/^from\:\s*<([^@>]+\@([^@>]+))>(\s|$)/i)) && !(mail.match(/^from\:\s*<>/i))) {
+        return this.client.send('501 5.1.7 Bad sender address syntax');
+    }
+
+    if (this.server.options.maxSize) {
+        mail.replace(/> size=(\d+)\b\s*/i, function(o, size) {
+            self.envelope.messageSize = size;
+        });
+    }
+
+    email = (match !== null && match[1]) || '';
+    domain = ((match !== null && match[2]) || '').toLowerCase();
+
+    this._validateAddress('sender', email, domain, function(err) {
+        if (err) {
+            return self.client.send(err.message);
+        }
+        email = email.substr(0, email.length - domain.length) + domain;
+        self.envelope.from = email;
+        self.client.send('250 2.1.0 Ok');
+    });
+};
+
+/**
+ * <p>Add recipients to the e-mail envelope</p>
+ *
+ * <p>This doesn't work if <code>MAIL</code> command is not yet executed</p>
+ *
+ * <p>If <code>validateRecipients</code> option is set to true, then emits
+ * <code>'validateRecipient'</code> and wait for the callback before moving
+ * on</p>
+ *
+ * @param {String} mail Address payload in the form of 'TO:<address>'
+ */
+SMTPServerConnection.prototype._onCommandRCPT = function(mail) {
+    var self = this,
+        match,
+        email,
+        domain;
+
+    if (!this.envelope.from) {
+        return this.client.send('503 5.5.1 Error: need MAIL command');
+    }
+
+    if (!(match = mail.match(/^to\:\s*<([^@>]+\@([^@>]+))>$/i))) {
+        return this.client.send('501 5.1.7 Bad recipient address syntax');
+    }
+
+    email = match[1] || '';
+    domain = (match[2] || '').toLowerCase();
+
+    this._validateAddress('recipient', email, domain, function(err) {
+        if (err) {
+            return self.client.send(err.message);
+        }
+
+        // force domain part to be lowercase
+        email = email.substr(0, email.length - domain.length) + domain;
+
+        // add to recipients list
+        if (self.envelope.to.indexOf(email) < 0) {
+            self.envelope.to.push(email);
+        }
+        self.client.send('250 2.1.0 Ok');
+    });
+
+};
+
+/**
+ * <p>If <code>disableDNSValidation</code> option is set to false, then performs
+ * validation via DNS lookup.
+ *
+ * <p>If <code>validate{type}</code> option is set to true, then emits
+ * <code>'validate{type}'</code> and waits for the callback before moving
+ * on</p>
+ *
+ * @param {String} addressType 'sender' or 'recipient'
+ * @param {String} email
+ * @param {String} domain
+ * @param {Function} callback
+ */
+SMTPServerConnection.prototype._validateAddress = function(addressType, email, domain, callback) {
+
+    var validateEvent,
+        validationFailedEvent,
+        dnsErrorMessage,
+        localErrorMessage;
+
+    if (addressType === 'sender') {
+        validateEvent = 'validateSender';
+        validationFailedEvent = 'senderValidationFailed';
+        dnsErrorMessage = '450 4.1.8 <' + email + '>: Sender address rejected: Domain not found';
+        localErrorMessage = '550 5.1.1 <' + email + '>: Sender address rejected: User unknown in local sender table';
+    } else if (addressType === 'recipient') {
+        validateEvent = 'validateRecipient';
+        validationFailedEvent = 'recipientValidationFailed';
+        dnsErrorMessage = '450 4.1.8 <' + email + '>: Recipient address rejected: Domain not found';
+        localErrorMessage = '550 5.1.1 <' + email + '>: Recipient address rejected: User unknown in local recipient table';
+    } else {
+        // How are internal errors handled?
+        throw new Error('Address type not supported');
+    }
+
+    var validateViaLocal = function() {
+        if (this.server.listeners(validateEvent).length) {
+            this.server.emit(validateEvent, this.envelope, email, (function(err) {
+                if (err) {
+                    return callback(new Error(err.SMTPResponse || localErrorMessage));
+                }
+                return callback();
+            }).bind(this));
+        } else {
+            return callback();
+        }
+    };
+
+    var validateViaDNS = function() {
+        dnslib.resolveMx(domain, (function(err, addresses) {
+            if (err || !addresses || !addresses.length) {
+                this.server.emit(validationFailedEvent, email);
+                return callback(new Error(err && err.SMTPResponse || dnsErrorMessage));
+            }
+            validateViaLocal.call(this);
+        }).bind(this));
+    };
+
+    if (!this.server.options.disableDNSValidation) {
+        validateViaDNS.call(this);
+    } else {
+        return validateViaLocal.call(this);
+    }
+};
+
+/**
+ * <p>Switch to data mode and starts waiting for a binary data stream. Emits
+ * <code>'startData'</code>.</p>
+ *
+ * <p>If <code>RCPT</code> is not yet run, stop</p>
+ */
+SMTPServerConnection.prototype._onCommandDATA = function() {
+
+    if (!this.envelope.to.length) {
+        return this.client.send('503 5.5.1 Error: need RCPT command');
+    }
+
+    this.client.startDataMode();
+    this.client.send('354 End data with <CR><LF>.<CR><LF>');
+    this.server.emit('startData', this.envelope);
+};
+
+/**
+ * <p>Resets the current state - e-mail data and authentication info</p>
+ */
+SMTPServerConnection.prototype._onCommandRSET = function() {
+    this.init();
+    this.client.send('250 2.0.0 Ok');
+};
+
+/**
+ * <p>If the server is in secure connection mode, start the authentication
+ * process. Param <code>payload</code> defines the authentication mechanism.</p>
+ *
+ * <p>Currently supported - PLAIN and LOGIN. There is no need for more
+ * complicated mechanisms (different CRAM versions etc.) since authentication
+ * is only done in secure connection mode</p>
+ *
+ * @param {Buffer} payload Defines the authentication mechanism
+ */
+SMTPServerConnection.prototype._onCommandAUTH = function(payload) {
+    var method;
+
+    if (!this.server.options.requireAuthentication && !this.server.options.enableAuthentication) {
+        return this.client.send('503 5.5.1 Error: authentication not enabled');
+    }
+
+    if (!this.server.options.ignoreTLS && !this.client.secureConnection) {
+        return this.client.send('530 5.7.0 Must issue a STARTTLS command first');
+    }
+
+    if (this.authentication.authenticated) {
+        return this.client.send('503 5.7.0 No identity changes permitted');
+    }
+
+    payload = payload.toString('utf-8').trim().split(' ');
+    method = payload.shift().trim().toUpperCase();
+
+    if (this.server.options.authMethods.indexOf(method) < 0) {
+        return this.client.send('535 5.7.8 Error: authentication failed: no mechanism available');
+    }
+
+    switch (method) {
+        case 'PLAIN':
+            this._handleAuthPlain(payload);
+            break;
+        case 'XOAUTH2':
+            this._handleAuthXOAuth2(payload);
+            break;
+        case 'LOGIN':
+            var username = payload.shift();
+            if (username) {
+                username = username.trim();
+                this.authentication.state = 'AUTHENTICATING';
+            }
+            this._handleAuthLogin(username);
+            break;
+    }
+};
+
+/**
+ * <p>Upgrade the connection to a secure TLS connection</p>
+ */
+SMTPServerConnection.prototype._onCommandSTARTTLS = function() {
+    if(this.server.options.disableSTARTTLS) {
+        return this.client.send('502 5.5.2 Error: command not recognized');
+    }
+    if (this.client.secureConnection) {
+        return this.client.send('554 5.5.1 Error: TLS already active');
+    }
+
+    this.client.send('220 2.0.0 Ready to start TLS');
+
+    this.client.startTLS(this.server.options.credentials, (function() {
+        // Connection secured
+        // nothing to do here, since it is the client that should
+        // make the next move
+    }).bind(this));
+};
+
+/**
+ * <p>Retrieve hostname from the client. Not very important, since client
+ * IP is already known and the client can send fake data</p>
+ *
+ * @param {String} host Hostname of the client
+ */
+SMTPServerConnection.prototype._onCommandHELO = function(host) {
+    if (!host) {
+        return this.client.send('501 Syntax: EHLO hostname');
+    } else {
+        this.hostNameAppearsAs = host;
+        this.envelope.host = host;
+    }
+    this.client.send('250 ' + this.server.options.name + ' at your service, [' +
+        this.client.remoteAddress + ']');
+};
+
+/**
+ * <p>Retrieve hostname from the client. Not very important, since client
+ * IP is already known and the client can send fake data</p>
+ *
+ * <p>Additionally displays server capability list to the client</p>
+ *
+ * @param {String} host Hostname of the client
+ */
+SMTPServerConnection.prototype._onCommandEHLO = function(host) {
+    var response = [this.server.options.name + ' at your service, [' +
+        this.client.remoteAddress + ']', '8BITMIME', 'ENHANCEDSTATUSCODES'
+    ];
+
+    if (this.server.options.maxSize) {
+        response.push('SIZE ' + this.server.options.maxSize);
+    }
+
+    if ((this.client.secureConnection || this.server.options.ignoreTLS) && (this.server.options.requireAuthentication || this.server.options.enableAuthentication)) {
+        response.push('AUTH ' + this.server.options.authMethods.join(' '));
+        response.push('AUTH=' + this.server.options.authMethods.join(' '));
+    }
+
+    if (!this.client.secureConnection && !this.server.options.disableSTARTTLS) {
+        response.push('STARTTLS');
+    }
+
+    if (!host) {
+        return this.client.send('501 Syntax: EHLO hostname');
+    } else {
+        this.hostNameAppearsAs = host;
+        this.envelope.host = host;
+    }
+
+    this.client.send(response.map(function(feature, i, arr) {
+        return '250' + (i < arr.length - 1 ? '-' : ' ') + feature;
+    }).join('\r\n'));
+};
+
+/**
+ * <p>No operation. Just returns OK.</p>
+ */
+SMTPServerConnection.prototype._onCommandNOOP = function() {
+    this.client.send('250 OK');
+};
+
+/**
+ * <p>Detect login information from the payload and initiate authentication
+ * by emitting <code>'authorizeUser'</code> and waiting for its callback</p>
+ *
+ * @param {Buffer} payload AUTH PLAIN login information
+ */
+SMTPServerConnection.prototype._handleAuthPlain = function(payload) {
+    if (payload.length) {
+        var userdata = new Buffer(payload.join(' '), 'base64'),
+            password;
+        userdata = userdata.toString('utf-8').split('\u0000');
+
+        if (userdata.length != 3) {
+            return this.client.send('500 5.5.2 Error: invalid userdata to decode');
+        }
+
+        this.authentication.username = userdata[1] || userdata[0] || '';
+        password = userdata[2] || '';
+
+        this.server.emit('authorizeUser',
+            this.envelope,
+            this.authentication.username,
+            password, (function(err, success) {
+                if (err || !success) {
+                    this.authentication.authenticated = false;
+                    this.authentication.username = false;
+                    this.authentication.state = 'NORMAL';
+                    return this.client.send('535 5.7.8 Error: authentication failed: generic failure');
+                }
+                this.client.send('235 2.7.0 Authentication successful');
+                this.authentication.authenticated = true;
+                this.authentication.state = 'AUTHENTICATED';
+            }).bind(this));
+    } else {
+        if (this.authentication.state == 'NORMAL') {
+            this.authentication.state = 'AUTHPLAINUSERDATA';
+            this.client.send('334');
+        }
+    }
+};
+
+/**
+ * <p>Sets authorization state to 'AUTHENTICATING' and reuqests for the
+ * username and password from the client</p>
+ *
+ * <p>If username and password are set initiate authentication
+ * by emitting <code>'authorizeUser'</code> and waiting for its callback</p>
+ *
+ * @param {Buffer} payload AUTH LOGIN login information
+ */
+SMTPServerConnection.prototype._handleAuthLogin = function(payload) {
+    if (this.authentication.state == 'NORMAL') {
+        this.authentication.state = 'AUTHENTICATING';
+        this.client.send('334 VXNlcm5hbWU6');
+    } else if (this.authentication.state == 'AUTHENTICATING') {
+        if (this.authentication.username === false) {
+            this.authentication.username = new Buffer(payload, 'base64').toString('utf-8');
+            this.client.send('334 UGFzc3dvcmQ6');
+        } else {
+            this.authentication.state = 'VERIFYING';
+            this.server.emit('authorizeUser',
+                this.envelope,
+                this.authentication.username,
+                new Buffer(payload, 'base64').toString('utf-8'), (function(err, success) {
+                    if (err || !success) {
+                        this.authentication.authenticated = false;
+                        this.authentication.username = false;
+                        this.authentication.state = 'NORMAL';
+                        return this.client.send('535 5.7.8 Error: authentication failed: generic failure');
+                    }
+                    this.client.send('235 2.7.0 Authentication successful');
+                    this.authentication.authenticated = true;
+                    this.authentication.state = 'AUTHENTICATED';
+                }).bind(this));
+        }
+
+    }
+};
+
+/**
+ * <p>Detect login information from the payload and initiate authentication
+ * by emitting <code>'authorizeUser'</code> and waiting for its callback</p>
+ *
+ * @param {Buffer} payload AUTH XOAUTH2 login information
+ */
+SMTPServerConnection.prototype._handleAuthXOAuth2 = function(payload) {
+    if (this.authentication.state == 'AUTHXOAUTH2') {
+        // empty response from the client
+        this.authentication.authenticated = false;
+        this.authentication.username = false;
+        this.authentication.state = 'NORMAL';
+        return this.client.send('535 5.7.1 Username and Password not accepted');
+    }
+
+    var userdata = new Buffer(payload.join(' '), 'base64'),
+        token;
+    userdata = userdata.toString('utf-8').split('\x01');
+
+    if (userdata.length != 4) {
+        return this.client.send('500 5.5.2 Error: invalid userdata to decode');
+    }
+
+    this.authentication.username = userdata[0].substr(5) || '';
+    token = userdata[1].split(' ')[1] || '';
+
+    this.server.emit('authorizeUser',
+        this.envelope,
+        this.authentication.username,
+        token, (function(err, success) {
+            if (err || !success) {
+                this.authentication.state = 'AUTHXOAUTH2';
+                return this.client.send('334 eyJzdGF0dXMiOiI0MDEiLCJzY2hlbWVzIjoiYmVhcmVyIG1hYyIsInNjb3BlIjoiaHR0cHM6Ly9tYWlsLmdvb2dsZS5jb20vIn0K');
+            }
+            this.client.send('235 2.7.0 Authentication successful');
+            this.authentication.authenticated = true;
+            this.authentication.state = 'AUTHENTICATED';
+        }).bind(this));
+};
+
+/**
+ * <p>Emits the data received from the client with <code>'data'</code>
+ *
+ * @event
+ * @param {Buffer} chunk Binary data sent by the client on data mode
+ */
+SMTPServerConnection.prototype._onData = function(chunk) {
+    this.server.emit('data', this.envelope, chunk);
+};
+
+/**
+ * <p>If the data stream ends, emit <code>'dataReady'</code>and wait for
+ * the callback, only if server listened for it.</p>
+ *
+ * @event
+ */
+SMTPServerConnection.prototype._onDataReady = function() {
+    if (this.server.listeners('dataReady').length) {
+        this.server.emit('dataReady', this.envelope, (function(err, code) {
+            this.init(true); //reset state, keep auth data
+
+            if (err) {
+                this.client.send(err && err.SMTPResponse || ('550 ' + (err && err.message || 'FAILED')));
+            } else {
+                this.client.send('250 2.0.0 Ok: queued as ' + (code || crypto.randomBytes(10).toString('hex')));
+            }
+
+        }).bind(this));
+    } else {
+        this.init(true); //reset state, keep auth data
+        this.client.send('250 2.0.0 Ok: queued as ' + crypto.randomBytes(10).toString('hex'));
+    }
+};
\ No newline at end of file
diff --git a/lib/simpleserver.js b/lib/simpleserver.js
new file mode 100644
index 0000000..76ce239
--- /dev/null
+++ b/lib/simpleserver.js
@@ -0,0 +1,126 @@
+'use strict';
+
+var createSMTPServer = require('./server'),
+    Stream = require('stream').Stream,
+    utillib = require('util'),
+    oslib = require('os');
+
+module.exports = function(options, connectionCallback) {
+    return new SimpleServer(options, connectionCallback);
+};
+
+function SimpleServer(options, connectionCallback) {
+    if (!connectionCallback && typeof options == 'function') {
+        connectionCallback = options;
+        options = undefined;
+    }
+
+    this.connectionCallback = connectionCallback;
+
+    this.options = options || {};
+    this.initialChunk = true;
+
+    if (!('ignoreTLS' in this.options)) {
+        this.options.ignoreTLS = true;
+    }
+
+    if (!('disableDNSValidation' in this.options)) {
+        this.options.disableDNSValidation = true;
+    }
+
+    this.server = createSMTPServer(options);
+    this.listen = this.server.listen.bind(this.server);
+
+    this.server.on('startData', this._onStartData.bind(this));
+    this.server.on('data', this._onData.bind(this));
+    this.server.on('dataReady', this._onDataReady.bind(this));
+}
+
+SimpleServer.prototype._onStartData = function(connection) {
+    connection._session = new SimpleServerConnection(connection);
+    this.connectionCallback(connection._session);
+};
+
+SimpleServer.prototype._onData = function(connection, chunk) {
+    if (this.initialChunk) {
+        chunk = Buffer.concat([new Buffer(this._generateReceivedHeader(connection) + '\r\n', 'utf-8'), chunk]);
+        this.initialChunk = false;
+    }
+    connection._session.emit('data', chunk);
+};
+
+SimpleServer.prototype._onDataReady = function(connection, callback) {
+    connection._session._setCallback(callback);
+    connection._session.emit('end');
+};
+
+SimpleServer.prototype._generateReceivedHeader = function(connection) {
+    var parts = [];
+
+    if (connection.host && !connection.host.match(/^\[?\d+\.\d+\.\d+\.\d+\]?$/)) {
+        parts.push('from ' + connection.host);
+        parts.push('(' + connection.remoteAddress + ')');
+    } else {
+        parts.push('from ' + connection.remoteAddress);
+    }
+
+    parts.push('by ' + getHostName());
+
+    parts.push('with SMTP;');
+
+    parts.push(Date());
+
+    return 'Received: ' + parts.join(' ');
+};
+
+function SimpleServerConnection(connection) {
+    Stream.call(this);
+
+    this.accepted = false;
+    this.rejected = false;
+
+    this._callback = (function(err, code) {
+        if (err) {
+            this.rejected = err;
+        } else {
+            this.accepted = code || true;
+        }
+    });
+
+    ['from', 'to', 'host', 'remoteAddress'].forEach((function(key) {
+        if (connection[key]) {
+            this[key] = connection[key];
+        }
+    }).bind(this));
+}
+utillib.inherits(SimpleServerConnection, Stream);
+
+SimpleServerConnection.prototype._setCallback = function(callback) {
+
+    if (this.rejected) {
+        return callback(this.rejected);
+    } else if (this.accepted) {
+        return callback(null, this.accepted !== true ? this.accepted : undefined);
+    } else {
+        this._callback = callback;
+    }
+
+};
+
+SimpleServerConnection.prototype.pause = function() {};
+
+SimpleServerConnection.prototype.resume = function() {};
+
+SimpleServerConnection.prototype.accept = function(code) {
+    this._callback(null, code);
+};
+
+SimpleServerConnection.prototype.reject = function(reason) {
+    this._callback(new Error(reason || 'Rejected'));
+};
+
+function getHostName() {
+    return (oslib.hostname && oslib.hostname()) ||
+        (oslib.getHostname && oslib.getHostname()) ||
+        '127.0.0.1';
+}
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e68d4c1
--- /dev/null
+++ b/package.json
@@ -0,0 +1,36 @@
+{
+    "name": "simplesmtp",
+    "description": "Simple SMTP server module to create custom SMTP servers",
+    "version": "0.3.35",
+    "author" : "Andris Reinman",
+    "maintainers":[
+        {
+            "name":"andris",
+            "email":"andris at node.ee"
+        }
+    ],
+    "repository" : {
+        "type" : "git",
+        "url" : "http://github.com/andris9/simplesmtp.git"
+    },
+    "scripts":{
+        "test": "nodeunit test/"
+    },
+    "main" : "./lib/smtp",
+    "licenses" : [
+        {
+            "type": "MIT",
+            "url": "http://github.com/andris9/simplesmtp/blob/master/LICENSE"
+        }
+    ],
+    "dependencies": {
+        "rai": "~0.1.11",
+        "xoauth2": "~0.1.8"
+    },
+    "devDependencies": {
+        "nodeunit": "*",
+        "mailcomposer": "*"
+    },
+    "engines" : { "node" : ">=0.8.0" },
+    "keywords": ["servers", "text-based", "smtp", "email", "mail", "e-mail"]
+}
diff --git a/test/client.js b/test/client.js
new file mode 100644
index 0000000..1cf9d1e
--- /dev/null
+++ b/test/client.js
@@ -0,0 +1,498 @@
+'use strict';
+
+var simplesmtp = require('../index'),
+    packageData = require('../package.json'),
+    fs = require('fs');
+
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
+
+var PORT_NUMBER = 8397;
+
+exports['Version test'] = {
+    'Should expose version number': function(test) {
+        test.ok(simplesmtp.version);
+        test.equal(simplesmtp.version, packageData.version);
+        test.done();
+    }
+};
+
+exports['General tests'] = {
+    setUp: function(callback) {
+        this.server = new simplesmtp.createServer();
+        this.server.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+    },
+
+    tearDown: function(callback) {
+        this.server.end(callback);
+    },
+
+    'Connect and setup': function(test) {
+        var client = simplesmtp.connect(PORT_NUMBER);
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(true);
+            client.close();
+        });
+
+        client.on('error', function() {
+            test.ok(false);
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    },
+
+    'socketTimeout': function(test) {
+        var client = simplesmtp.connect(PORT_NUMBER, false, {
+            socketTimeout: 500
+        });
+
+        var waitTimeout = setTimeout(function() {
+            test.ok(false);
+            test.done();
+        }, 2000);
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(true);
+        });
+
+        client.on('error', function(err) {
+            test.ifError(err);
+        });
+
+        client.on('end', function() {
+            clearTimeout(waitTimeout);
+            test.done();
+        });
+    }
+};
+
+exports['Secure server'] = {
+    setUp: function(callback) {
+        this.server = new simplesmtp.createServer({
+            secureConnection: true
+        });
+        this.server.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+    },
+
+    tearDown: function(callback) {
+        this.server.end(callback);
+    },
+
+    'Connect and setup': function(test) {
+        var client = simplesmtp.connect(PORT_NUMBER, false, {
+            secureConnection: true
+        });
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(true);
+            client.close();
+        });
+
+        client.on('error', function() {
+            test.ok(false);
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    },
+
+    'Unsecure client should have timeout': function(test) {
+        var client = simplesmtp.connect(PORT_NUMBER, false, {
+            secureConnection: false
+        });
+
+        client.once('idle', function() {
+            test.ok(false);
+        });
+
+        client.on('error', function(err) {
+            test.equal(err.code, 'ETIMEDOUT');
+            client.close();
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    }
+};
+
+exports['Disabled EHLO'] = {
+    setUp: function(callback) {
+        this.server = new simplesmtp.createServer({
+            disableEHLO: true
+        });
+        this.server.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+    },
+
+    tearDown: function(callback) {
+        this.server.end(callback);
+    },
+
+    'Connect and setup': function(test) {
+        var client = simplesmtp.connect(PORT_NUMBER, false, {});
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(true);
+            client.close();
+        });
+
+        client.on('error', function() {
+            test.ok(false);
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    }
+};
+
+exports['Authentication needed'] = {
+    setUp: function(callback) {
+        this.server = new simplesmtp.createServer({
+            requireAuthentication: true
+        });
+
+        this.server.on('authorizeUser', function(envelope, user, pass, callback) {
+            callback(null, user == 'test1' && pass == 'test2');
+        });
+
+        this.server.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+    },
+
+    tearDown: function(callback) {
+        this.server.end(callback);
+    },
+
+    'Auth success': function(test) {
+        var client = simplesmtp.connect(PORT_NUMBER, false, {
+            auth: {
+                user: 'test1',
+                pass: 'test2'
+            }
+        });
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(true);
+            client.close();
+        });
+
+        client.on('error', function() {
+            test.ok(false);
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    },
+
+    'Auth fails': function(test) {
+        var client = simplesmtp.connect(PORT_NUMBER, false, {
+            auth: {
+                user: 'test3',
+                pass: 'test4'
+            }
+        });
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(false); // should not occur
+            client.close();
+        });
+
+        client.on('error', function() {
+            test.ok(true); // login failed
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    }
+};
+
+exports['Message tests'] = {
+    setUp: function(callback) {
+        this.server = new simplesmtp.createServer({
+            validateSender: true,
+            validateRecipients: true
+        });
+
+        this.server.on('validateSender', function(envelope, email, callback) {
+            callback(email != 'test at pangalink.net' ? new Error('Failed sender') : null);
+        });
+
+        this.server.on('validateRecipient', function(envelope, email, callback) {
+            callback(email.split('@').pop() != 'pangalink.net' ? new Error('Failed recipient') : null);
+        });
+
+        this.server.on('dataReady', function(envelope, callback) {
+            callback(null, 'ABC1'); // ABC1 is the queue id to be advertised to the client
+            // callback(new Error('That was clearly a spam!'));
+        });
+
+        this.server.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+    },
+
+    tearDown: function(callback) {
+        this.server.end(callback);
+    },
+
+    'Set envelope success': function(test) {
+        test.expect(2);
+
+        var client = simplesmtp.connect(PORT_NUMBER, false, {});
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(true); // waiting for envelope
+
+            client.useEnvelope({
+                from: 'test at pangalink.net',
+                to: [
+                    'test1 at pangalink.net',
+                    'test2 at pangalink.net'
+                ]
+            });
+        });
+
+        client.on('message', function() {
+            // Client is ready to take messages
+            test.ok(true); // waiting for message
+            client.close();
+        });
+
+        client.on('error', function() {
+            test.ok(false);
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    },
+
+    'Set envelope fails for sender': function(test) {
+        test.expect(2);
+
+        var client = simplesmtp.connect(PORT_NUMBER, false, {});
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(true); // waiting for envelope
+
+            client.useEnvelope({
+                from: 'test3 at pangalink.net',
+                to: [
+                    'test1 at pangalink.net',
+                    'test2 at pangalink.net'
+                ]
+            });
+        });
+
+        client.on('message', function() {
+            // Client is ready to take messages
+            test.ok(false); // waiting for message
+            client.close();
+        });
+
+        client.on('error', function() {
+            test.ok(true);
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    },
+
+    'Set envelope fails for receiver': function(test) {
+        test.expect(2);
+
+        var client = simplesmtp.connect(PORT_NUMBER, false, {});
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(true); // waiting for envelope
+
+            client.useEnvelope({
+                from: 'test at pangalink.net',
+                to: [
+                    'test1 at kreata.ee',
+                    'test2 at kreata.ee'
+                ]
+            });
+        });
+
+        client.on('message', function() {
+            // Client is ready to take messages
+            test.ok(false); // waiting for message
+            client.close();
+        });
+
+        client.on('error', function() {
+            test.ok(true);
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    },
+
+    'Set envelope partly fails': function(test) {
+        test.expect(3);
+
+        var client = simplesmtp.connect(PORT_NUMBER, false, {});
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(true); // waiting for envelope
+
+            client.useEnvelope({
+                from: 'test at pangalink.net',
+                to: [
+                    'test1 at pangalink.net',
+                    'test2 at kreata.ee'
+                ]
+            });
+        });
+
+        client.on('rcptFailed', function() {
+            // Client is ready to take messages
+            test.ok(true); // waiting for message
+        });
+
+        client.on('message', function() {
+            // Client is ready to take messages
+            test.ok(true); // waiting for message
+            client.close();
+        });
+
+        client.on('error', function() {
+            test.ok(false);
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    },
+
+    'Send message success': function(test) {
+        test.expect(3);
+
+        var client = simplesmtp.connect(PORT_NUMBER, false, {});
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(true); // waiting for envelope
+
+            client.useEnvelope({
+                from: 'test at pangalink.net',
+                to: [
+                    'test1 at pangalink.net',
+                    'test2 at pangalink.net'
+                ]
+            });
+        });
+
+        client.on('message', function() {
+            // Client is ready to take messages
+            test.ok(true); // waiting for message
+
+            client.write('From: abc at pangalink.net\r\nTo:cde at pangalink.net\r\nSubject: test\r\n\r\nHello World!');
+            client.end();
+        });
+
+        client.on('ready', function(success) {
+            test.ok(success);
+            client.close();
+        });
+
+        client.on('error', function() {
+            test.ok(false);
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    },
+
+    'Stream message': function(test) {
+        test.expect(3);
+
+        var client = simplesmtp.connect(PORT_NUMBER, false, {});
+
+        client.once('idle', function() {
+            // Client is ready to take messages
+            test.ok(true); // waiting for envelope
+
+            client.useEnvelope({
+                from: 'test at pangalink.net',
+                to: [
+                    'test1 at pangalink.net',
+                    'test2 at pangalink.net'
+                ]
+            });
+        });
+
+        client.on('message', function() {
+            // Client is ready to take messages
+            test.ok(true); // waiting for message
+
+            // pipe file to client
+            fs.createReadStream(__dirname + '/testmessage.eml').pipe(client);
+        });
+
+        client.on('ready', function(success) {
+            test.ok(success);
+            client.close();
+        });
+
+        client.on('error', function() {
+            test.ok(false);
+        });
+
+        client.on('end', function() {
+            test.done();
+        });
+    }
+};
\ No newline at end of file
diff --git a/test/pool.js b/test/pool.js
new file mode 100644
index 0000000..663a471
--- /dev/null
+++ b/test/pool.js
@@ -0,0 +1,400 @@
+'use strict';
+
+/* jshint loopfunc: true */
+
+var simplesmtp = require('../index'),
+    MailComposer = require('mailcomposer').MailComposer;
+
+var PORT_NUMBER = 8397;
+
+exports['General tests'] = {
+    setUp: function(callback) {
+        this.server = new simplesmtp.createServer({});
+        this.server.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+    },
+
+    tearDown: function(callback) {
+        this.server.end(callback);
+    },
+
+    'Send single message': function(test) {
+
+        var pool = simplesmtp.createClientPool(PORT_NUMBER),
+            mc = new MailComposer({
+                escapeSMTP: true
+            });
+
+        mc.setMessageOption({
+            from: 'andmekala at hot.ee',
+            to: 'andris at pangalink.net',
+            subject: 'Hello!',
+            body: 'Hello world!',
+            html: '<b>Hello world!</b>'
+        });
+
+        this.server.on('dataReady', function(envelope, callback) {
+            test.ok(true);
+            callback();
+        });
+
+        pool.sendMail(mc, function(error) {
+            test.ifError(error);
+            pool.close(function() {
+                test.ok(true);
+                test.done();
+            });
+        });
+    },
+
+    'Send several messages': function(test) {
+        var total = 10;
+
+        test.expect(total * 2);
+
+        var pool = simplesmtp.createClientPool(PORT_NUMBER),
+            mc;
+
+        this.server.on('dataReady', function(envelope, callback) {
+            process.nextTick(callback);
+        });
+
+        var completed = 0;
+        for (var i = 0; i < total; i++) {
+            mc = new MailComposer({
+                escapeSMTP: true
+            });
+            mc.setMessageOption({
+                from: 'andmekala at hot.ee',
+                to: 'andris at pangalink.net',
+                subject: 'Hello!',
+                body: 'Hello world!',
+                html: '<b>Hello world!</b>'
+            });
+            pool.sendMail(mc, function(error) {
+                test.ifError(error);
+                test.ok(true);
+                completed++;
+                if (completed >= total) {
+                    pool.close(function() {
+                        test.done();
+                    });
+                }
+            });
+        }
+    },
+
+    'Delivery error once': function(test) {
+
+        var pool = simplesmtp.createClientPool(PORT_NUMBER),
+            mc = new MailComposer({
+                escapeSMTP: true
+            });
+
+        mc.setMessageOption({
+            from: 'andmekala at hot.ee',
+            to: 'andris at pangalink.net',
+            subject: 'Hello!',
+            body: 'Hello world!',
+            html: '<b>Hello world!</b>'
+        });
+
+        this.server.on('dataReady', function(envelope, callback) {
+            test.ok(true);
+            callback(new Error('Spam!'));
+        });
+
+        pool.sendMail(mc, function(error) {
+            test.equal(error && error.name, 'DeliveryError');
+            pool.close(function() {
+                test.ok(true);
+                test.done();
+            });
+        });
+    },
+
+    'Delivery error several times': function(test) {
+        var total = 10;
+
+        test.expect(total);
+
+        var pool = simplesmtp.createClientPool(PORT_NUMBER),
+            mc;
+
+        this.server.on('dataReady', function(envelope, callback) {
+            process.nextTick(function() {
+                callback(new Error('Spam!'));
+            });
+        });
+
+        var completed = 0;
+        for (var i = 0; i < total; i++) {
+            mc = new MailComposer({
+                escapeSMTP: true
+            });
+            mc.setMessageOption({
+                from: 'andmekala at hot.ee',
+                to: 'andris at pangalink.net',
+                subject: 'Hello!',
+                body: 'Hello world!',
+                html: '<b>Hello world!</b>'
+            });
+
+            pool.sendMail(mc, function(error) {
+                test.equal(error && error.name, 'DeliveryError');
+                completed++;
+                if (completed >= total) {
+                    pool.close(function() {
+                        test.done();
+                    });
+                }
+            });
+        }
+    }
+};
+
+exports['Auth fail tests'] = {
+    setUp: function(callback) {
+        this.server = new simplesmtp.createServer({
+            requireAuthentication: true
+        });
+
+        this.server.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+        this.server.on('authorizeUser', function(envelope, username, password, callback) {
+            callback(null, username == password);
+        });
+    },
+
+    tearDown: function(callback) {
+        this.server.end(callback);
+    },
+
+    'Authentication passes once': function(test) {
+        var pool = simplesmtp.createClientPool(PORT_NUMBER, false, {
+            auth: {
+                'user': 'test',
+                'pass': 'test'
+            }
+        }),
+            mc = new MailComposer({
+                escapeSMTP: true
+            });
+
+        mc.setMessageOption({
+            from: 'andmekala2 at hot.ee',
+            to: 'andris2 at pangalink.net',
+            subject: 'Hello2!',
+            body: 'Hello2 world!',
+            html: '<b>Hello2 world!</b>'
+        });
+
+        this.server.on('dataReady', function(envelope, callback) {
+            test.ok(true);
+            callback();
+        });
+
+        pool.sendMail(mc, function(error) {
+            test.ifError(error);
+            pool.close(function() {
+                test.ok(true);
+                test.done();
+            });
+        });
+
+    },
+
+    'Authentication error once': function(test) {
+        var pool = simplesmtp.createClientPool(PORT_NUMBER, false, {
+            auth: {
+                'user': 'test1',
+                'pass': 'test2'
+            }
+        }),
+            mc = new MailComposer({
+                escapeSMTP: true
+            });
+
+        mc.setMessageOption({
+            from: 'andmekala2 at hot.ee',
+            to: 'andris2 at pangalink.net',
+            subject: 'Hello2!',
+            body: 'Hello2 world!',
+            html: '<b>Hello2 world!</b>'
+        });
+
+        this.server.on('dataReady', function(envelope, callback) {
+            test.ok(true);
+            callback();
+        });
+
+        pool.sendMail(mc, function(error) {
+            test.equal(error && error.name, 'AuthError');
+            pool.close(function() {
+                test.ok(true);
+                test.done();
+            });
+        });
+    }
+};
+
+exports['Max messages'] = {
+    setUp: function(callback) {
+        this.server = new simplesmtp.createServer({});
+        this.server.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+    },
+
+    tearDown: function(callback) {
+        this.server.end(callback);
+    },
+
+    'Limit 1': function(test) {
+        var total = 10;
+
+        test.expect(total * 3);
+
+        var pool = simplesmtp.createClientPool(PORT_NUMBER, false, {
+            maxMessages: 1,
+            maxConnections: 1
+        }),
+            mc;
+
+        pool.on('released', function() {
+            test.ok(1);
+        });
+
+        this.server.on('dataReady', function(envelope, callback) {
+            process.nextTick(callback);
+        });
+
+        var completed = 0;
+        for (var i = 0; i < total; i++) {
+            mc = new MailComposer({
+                escapeSMTP: true
+            });
+            mc.setMessageOption({
+                from: 'andmekala at hot.ee',
+                to: 'andris at pangalink.net',
+                subject: 'Hello!',
+                body: 'Hello world!',
+                html: '<b>Hello world!</b>'
+            });
+            pool.sendMail(mc, function(error) {
+                test.ifError(error);
+                test.ok(true);
+                completed++;
+                if (completed >= total) {
+                    pool.close(function() {
+                        test.done();
+                    });
+                }
+            });
+        }
+    },
+
+    'Limit 2': function(test) {
+        var total = 10;
+
+        test.expect(total * 2 + 5);
+
+        var pool = simplesmtp.createClientPool(PORT_NUMBER, false, {
+            maxMessages: 2,
+            maxConnections: 1
+        }),
+            mc;
+
+        pool.on('released', function() {
+            test.ok(1);
+        });
+
+        this.server.on('dataReady', function(envelope, callback) {
+            process.nextTick(callback);
+        });
+
+        var completed = 0;
+        for (var i = 0; i < total; i++) {
+            mc = new MailComposer({
+                escapeSMTP: true
+            });
+            mc.setMessageOption({
+                from: 'andmekala at hot.ee',
+                to: 'andris at pangalink.net',
+                subject: 'Hello!',
+                body: 'Hello world!',
+                html: '<b>Hello world!</b>'
+            });
+            pool.sendMail(mc, function(error) {
+                test.ifError(error);
+                test.ok(true);
+                completed++;
+                if (completed >= total) {
+                    pool.close(function() {
+                        test.done();
+                    });
+                }
+            });
+        }
+    },
+
+    'No limit': function(test) {
+        var total = 10;
+
+        test.expect(total * 2);
+
+        var pool = simplesmtp.createClientPool(PORT_NUMBER, false, {
+            maxConnections: 1
+        }),
+            mc;
+
+        pool.on('released', function() {
+            test.ok(1);
+        });
+
+        this.server.on('dataReady', function(envelope, callback) {
+            process.nextTick(callback);
+        });
+
+        var completed = 0;
+        for (var i = 0; i < total; i++) {
+            mc = new MailComposer({
+                escapeSMTP: true
+            });
+            mc.setMessageOption({
+                from: 'andmekala at hot.ee',
+                to: 'andris at pangalink.net',
+                subject: 'Hello!',
+                body: 'Hello world!',
+                html: '<b>Hello world!</b>'
+            });
+            pool.sendMail(mc, function(error) {
+                test.ifError(error);
+                test.ok(true);
+                completed++;
+                if (completed >= total) {
+                    pool.close(function() {
+                        test.done();
+                    });
+                }
+            });
+        }
+    }
+};
\ No newline at end of file
diff --git a/test/server.js b/test/server.js
new file mode 100644
index 0000000..d19877f
--- /dev/null
+++ b/test/server.js
@@ -0,0 +1,739 @@
+'use strict';
+
+var runClientMockup = require('rai').runClientMockup,
+    simplesmtp = require('../index'),
+    netlib = require('net');
+
+var PORT_NUMBER = 8397;
+
+// monkey patch net and tls to support nodejs 0.4
+if (!netlib.connect && netlib.createConnection) {
+    netlib.connect = netlib.createConnection;
+}
+
+exports['General tests'] = {
+    setUp: function(callback) {
+
+        this.smtp = new simplesmtp.createServer({
+            SMTPBanner: 'SCORPIO',
+            name: 'MYRDO',
+            maxSize: 1234,
+            maxClients: 1,
+        });
+        this.smtp.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+    },
+    tearDown: function(callback) {
+        this.smtp.end(callback);
+    },
+    'QUIT': function(test) {
+        var cmds = ['QUIT'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('221', resp.toString('utf-8').trim().substr(0, 3));
+            test.done();
+        });
+
+    },
+    'HELO': function(test) {
+        var cmds = ['HELO FOO'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('250', resp.toString('utf-8').trim().substr(0, 3));
+            test.done();
+        });
+
+    },
+    'HELO fails': function(test) {
+        var cmds = ['HELO'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'EHLO': function(test) {
+        var cmds = ['EHLO FOO'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            resp = resp.toString('utf-8').trim();
+            var lines = resp.split('\r\n');
+            for (var i = 0; i < lines.length - 1; i++) {
+                test.equal('250-', lines[i].substr(0, 4));
+            }
+            test.equal('250 ', lines[i].substr(0, 4));
+            test.done();
+        });
+    },
+    'EHLO fails': function(test) {
+        var cmds = ['EHLO'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'HELO after STARTTLS': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'HELO FOO'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('250', resp.toString('utf-8').trim().substr(0, 3));
+            test.done();
+        });
+    },
+    'HELO fails after STARTTLS': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'HELO'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'EHLO after STARTTLS': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'HELO FOO'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            resp = resp.toString('utf-8').trim();
+            var lines = resp.split('\r\n');
+            for (var i = 0; i < lines.length - 1; i++) {
+                test.equal('250-', lines[i].substr(0, 4));
+            }
+            test.equal('250 ', lines[i].substr(0, 4));
+            test.done();
+        });
+    },
+    'EHLO fails after STARTTLS': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH fails if not required': function(test) {
+        var cmds = ['EHLO FOO', 'AUTH LOGIN'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH fails if not required TLS': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'AUTH LOGIN'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'Custom Greeting banner': function(test) {
+        var client = netlib.connect(PORT_NUMBER, function() {
+            client.on('data', function(chunk) {
+                test.equal('SCORPIO', (chunk || '').toString().trim().split(' ').pop());
+                client.end();
+            });
+            client.on('end', function() {
+                test.done();
+            });
+        });
+    },
+    'HELO name': function(test) {
+        var cmds = ['HELO FOO'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('MYRDO', resp.toString('utf-8').trim().substr(4).split(' ').shift());
+            test.done();
+        });
+    },
+    'EHLO name': function(test) {
+        var cmds = ['EHLO FOO'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('MYRDO', resp.toString('utf-8').trim().substr(4).split(' ').shift());
+            test.done();
+        });
+    },
+    'MAIL FROM options': function(test) {
+        var cmds = ['HELO FOO', 'MAIL FROM:<test at gmail.com> BODY=8BITMIME'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.ok(resp.toString('utf-8').match(/^250/));
+            test.done();
+        });
+    },
+    'MAXSIZE': function(test) {
+        var cmds = ['EHLO FOO'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.ok(resp.toString('utf-8').trim().match(/^250[\- ]SIZE 1234$/mi));
+            test.done();
+        });
+    }
+    /*,
+
+    // test disabled due to race conditions (returned false positives if connections are created in non expected order)
+    'Max Incoming Connections': function(test) {
+        var maxClients = this.smtp.options.maxClients,
+            name = this.smtp.options.name;
+
+        for (var i = 0; i <= maxClients; i++) {
+            runClientMockup(PORT_NUMBER, 'localhost', [], (function(i) {
+                return function(resp) {
+                    if (i < maxClients) return;
+                    test.ok((new RegExp('^421\\s+' + name)).test(resp.toString('utf-8').trim()));
+                    test.done();
+                }
+            })(i));
+        }
+    },
+    */
+};
+
+exports['EHLO setting'] = {
+    setUp: function(callback) {
+
+        this.smtp = new simplesmtp.createServer({
+            disableEHLO: true
+        });
+        this.smtp.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+    },
+    tearDown: function(callback) {
+        this.smtp.end(callback);
+    },
+    'Disable EHLO': function(test) {
+        runClientMockup(PORT_NUMBER, 'localhost', ['EHLO foo'], function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            runClientMockup(PORT_NUMBER, 'localhost', ['HELO foo'], function(resp) {
+                test.equal('2', resp.toString('utf-8').trim().substr(0, 1));
+                test.done();
+            });
+        });
+
+    }
+};
+
+exports['Client disconnect'] = {
+
+    'Client disconnect': function(test) {
+
+        var smtp = new simplesmtp.createServer(),
+            clientEnvelope;
+        smtp.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            }
+
+            runClientMockup(PORT_NUMBER, 'localhost', ['EHLO foo', 'MAIL FROM:<andris at pangalink.net>', 'RCPT TO:<andris at pangalink.net>', 'DATA'], function(resp) {
+                test.equal('3', resp.toString('utf-8').trim().substr(0, 1));
+            });
+
+        });
+        smtp.on('startData', function(envelope) {
+            clientEnvelope = envelope;
+        });
+        smtp.on('close', function(envelope) {
+            test.equal(envelope, clientEnvelope);
+            smtp.end(function() {});
+            test.done();
+        });
+
+    }
+};
+
+exports['Require AUTH'] = {
+    setUp: function(callback) {
+
+        this.smtp = new simplesmtp.createServer({
+            requireAuthentication: true,
+            authMethods: ['PLAIN', 'LOGIN', 'XOAUTH2']
+        });
+        this.smtp.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+        this.smtp.on('authorizeUser', function(envelope, username, password, callback) {
+            callback(null, username == 'andris' && password == 'test');
+        });
+
+    },
+    tearDown: function(callback) {
+        this.smtp.end(callback);
+    },
+    'Fail without AUTH': function(test) {
+        var cmds = ['EHLO FOO', 'MAIL FROM:<andris at pangalink.net>'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'Unknown AUTH': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH CRAM'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH fails before STARTTLS': function(test) {
+        var cmds = ['EHLO FOO', 'AUTH LOGIN'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH LOGIN'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('3', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN Invalid login': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH LOGIN',
+            new Buffer('inv').toString('base64'),
+            new Buffer('alid').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN Invalid username': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH LOGIN',
+            new Buffer('inv').toString('base64'),
+            new Buffer('test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN Invalid password': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH LOGIN',
+            new Buffer('andris').toString('base64'),
+            new Buffer('alid').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN Login success': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH LOGIN',
+            new Buffer('andris').toString('base64'),
+            new Buffer('test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('2', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN Login with username': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO',
+            'AUTH LOGIN ' + new Buffer('andris').toString('base64'),
+            new Buffer('test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('2', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN Login with username - invalid username': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO',
+            'AUTH LOGIN ' + new Buffer('inv').toString('base64'),
+            new Buffer('test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN Login with username - invalid password': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO',
+            'AUTH LOGIN ' + new Buffer('andris').toString('base64'),
+            new Buffer('inv').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('3', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN Invalid login': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN ' +
+            new Buffer('inv\u0000inv\u0000alid').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN Invalid user': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN ' +
+            new Buffer('inv\u0000inv\u0000test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN Invalid password': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN ' +
+            new Buffer('andris\u0000andris\u0000alid').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN Login success': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN ' +
+            new Buffer('andris\u0000andris\u0000test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('2', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN Yet another login success': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN',
+            new Buffer('andris\u0000andris\u0000test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('2', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH XOAUTH2 Login success': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH XOAUTH2 ' +
+            new Buffer([
+                'user=andris',
+                'auth=Bearer test',
+                '',
+                '\n'
+            ].join('\x01')).toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('2', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH XOAUTH2 Login fail': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH XOAUTH2 ' +
+            new Buffer([
+                'user=andris',
+                'auth=Bearer test2',
+                '',
+                '\n'
+            ].join('\x01')).toString('base64'),
+            ''
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    }
+};
+
+exports['Enable AUTH'] = {
+    setUp: function(callback) {
+
+        this.smtp = new simplesmtp.createServer({
+            enableAuthentication: true
+        });
+        this.smtp.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+        this.smtp.on('authorizeUser', function(envelope, username, password, callback) {
+            callback(null, username == 'andris' && password == 'test');
+        });
+
+    },
+    tearDown: function(callback) {
+        this.smtp.end(callback);
+    },
+    'Pass without AUTH': function(test) {
+        var cmds = ['EHLO FOO', 'MAIL FROM:<andris at pangalink.net>'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('2', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'Unknown AUTH': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH CRAM'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH fails before STARTTLS': function(test) {
+        var cmds = ['EHLO FOO', 'AUTH LOGIN'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH LOGIN'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('3', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN Invalid login': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH LOGIN',
+            new Buffer('inv').toString('base64'),
+            new Buffer('alid').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN Invalid username': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH LOGIN',
+            new Buffer('inv').toString('base64'),
+            new Buffer('test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN Invalid password': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH LOGIN',
+            new Buffer('andris').toString('base64'),
+            new Buffer('alid').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH LOGIN Login success': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH LOGIN',
+            new Buffer('andris').toString('base64'),
+            new Buffer('test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('2', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('3', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN Invalid login': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN ' +
+            new Buffer('inv\u0000inv\u0000alid').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN Invalid user': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN ' +
+            new Buffer('inv\u0000inv\u0000test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN Invalid password': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN ' +
+            new Buffer('andris\u0000andris\u0000alid').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN Login success': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN ' +
+            new Buffer('andris\u0000andris\u0000test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('2', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'AUTH PLAIN Yet another login success': function(test) {
+        var cmds = ['EHLO FOO', 'STARTTLS', 'EHLO FOO', 'AUTH PLAIN',
+            new Buffer('andris\u0000andris\u0000test').toString('base64')
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('2', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    }
+};
+
+exports.ignoreTLS = {
+    setUp: function(callback) {
+
+        this.smtp = new simplesmtp.createServer({
+            requireAuthentication: true,
+            ignoreTLS: true
+        });
+        this.smtp.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+        this.smtp.on('authorizeUser', function(envelope, username, password, callback) {
+            callback(null, username == 'd3ph' && password == 'test');
+        });
+    },
+    tearDown: function(callback) {
+        this.smtp.end(callback);
+    },
+    'Fail without AUTH': function(test) {
+        var cmds = ['EHLO FOO', 'MAIL FROM:<d3ph.ru at gmail.com>'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'Fail MAIL FROM without HELO': function(test) {
+        var cmds = ['MAIL FROM:<d3ph.ru at gmail.com>'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('5', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    },
+    'Success AUTH & SEND MAIL with <CR><LF>.<CR><LF>': function(test) {
+        var cmds = ['EHLO FOO',
+            'AUTH PLAIN',
+            new Buffer('\u0000d3ph\u0000test').toString('base64'),
+            'MAIL FROM:<d3ph at github.com>',
+            'RCPT TO:<andris at pangalink.net>',
+            'DATA',
+            'Test mail\r\n.\r\n',
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            resp = resp.toString('utf-8').trim();
+            test.equal('2', resp.substr(0, 1));
+            test.ok(resp.match('queued as'));
+            test.done();
+        });
+    }
+};
+
+exports['Sending mail listen for dataReady'] = {
+    setUp: function(callback) {
+        var data = '';
+
+        this.smtp = new simplesmtp.createServer({
+            ignoreTLS: true
+        });
+        this.smtp.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+        this.smtp.on('authorizeUser', function(envelope, username, password, callback) {
+            callback(null, username == 'd3ph' && password == 'test');
+        });
+
+        this.smtp.on('data', function(envelope, chunk) {
+            data += chunk;
+        });
+
+        this.smtp.on('dataReady', function(envelope, callback) {
+            setTimeout(function() {
+                if (data.match('spam')) {
+                    callback(new Error('FAILED'));
+                } else {
+                    callback(null, '#ID');
+                }
+            }, 2000);
+        });
+    },
+    tearDown: function(callback) {
+        this.smtp.end(callback);
+    },
+    'Fail send mail if body contains "spam"': function(test) {
+        var cmds = ['EHLO FOO',
+            'MAIL FROM:<d3ph at github.com>',
+            'RCPT TO:<andris at pangalink.net>',
+            'DATA',
+            'Test mail with spam!\r\n.\r\n',
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('550 FAILED', resp.toString('utf-8').trim());
+            test.done();
+        });
+    },
+    'Create #ID for mail': function(test) {
+        var cmds = ['EHLO FOO',
+            'MAIL FROM:<d3ph at github.com>',
+            'RCPT TO:<andris at pangalink.net>',
+            'DATA',
+            'Clear mail body\r\n.\r\n',
+        ];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            resp = resp.toString('utf-8').trim();
+            test.equal('2', resp.substr(0, 1));
+            test.ok(resp.match('#ID'));
+            test.done();
+        });
+    }
+};
+
+exports['Sending mail listen for dataReady'] = {
+    setUp: function(callback) {
+        var data = '';
+
+        this.smtp = new simplesmtp.createServer({
+            ignoreTLS: true,
+            disableDNSValidation: true
+        });
+        this.smtp.listen(PORT_NUMBER, function(err) {
+            if (err) {
+                throw err;
+            } else {
+                callback();
+            }
+        });
+
+        this.smtp.on('data', function(envelope, chunk) {
+            data += chunk;
+        });
+    },
+
+    tearDown: function(callback) {
+        this.smtp.end(callback);
+    },
+
+    'Allow empty Mail from': function(test) {
+        var cmds = ['EHLO FOO', 'MAIL FROM:<>'];
+        runClientMockup(PORT_NUMBER, 'localhost', cmds, function(resp) {
+            test.equal('2', resp.toString('utf-8').trim().substr(0, 1));
+            test.done();
+        });
+    }
+};
\ No newline at end of file
diff --git a/test/testmessage.eml b/test/testmessage.eml
new file mode 100644
index 0000000..dff23e5
--- /dev/null
+++ b/test/testmessage.eml
@@ -0,0 +1,5 @@
+From: test at pangalink.net
+To: test at pangalink.net
+Subject: Test
+
+Hello world!
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-simplesmtp.git



More information about the Pkg-javascript-commits mailing list