mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-09-07 05:35:12 +08:00
added example to push messages to INBOX
This commit is contained in:
parent
a17631b2e0
commit
d7b12edf5b
7 changed files with 253 additions and 5 deletions
12
README.md
12
README.md
|
@ -30,7 +30,7 @@ Is the server scalable? Not yet. These are some actions that must be done:
|
|||
|
||||
**Step 2.** Install dependencies
|
||||
|
||||
$ npm install --production
|
||||
$ npm install
|
||||
|
||||
**Step 3.** Modify [config file](./config/default.js)
|
||||
|
||||
|
@ -74,6 +74,16 @@ The response for successful operation should look like this:
|
|||
|
||||
After you have created an user you can use these credentials to log in to the IMAP server. Additionally the LMTP server starts accepting mail for this email address.
|
||||
|
||||
## Testing
|
||||
|
||||
Create an email account and use your IMAP client to connect to it. To send mail to this account, run the example script:
|
||||
|
||||
```
|
||||
node examples/push.mail.js username@example.com
|
||||
```
|
||||
|
||||
This should "deliver" a new message to the INBOX of *username@example.com*, if your email client is connected then you should promptly see the new message.
|
||||
|
||||
## License
|
||||
|
||||
Wild Duck Mail Agent is licensed under the [European Union Public License 1.1](http://ec.europa.eu/idabc/eupl.html).
|
||||
|
|
|
@ -13,11 +13,19 @@ module.exports = {
|
|||
},
|
||||
|
||||
lmtp: {
|
||||
enabled: true,
|
||||
port: 2424,
|
||||
host: '0.0.0.0',
|
||||
maxMB: 5
|
||||
},
|
||||
|
||||
smtp: {
|
||||
enabled: true,
|
||||
port: 2525,
|
||||
host: '0.0.0.0',
|
||||
maxMB: 5
|
||||
},
|
||||
|
||||
api: {
|
||||
port: 8080
|
||||
}
|
||||
|
|
25
examples/push-message.js
Normal file
25
examples/push-message.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
'use strict';
|
||||
|
||||
const recipient = process.argv[2];
|
||||
|
||||
if (!recipient) {
|
||||
console.error('Usage: node example.com username@exmaple.com'); // eslint-disable-line no-console
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
const config = require('config');
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: 'localhost',
|
||||
port: config.smtp.port,
|
||||
logger: true
|
||||
});
|
||||
|
||||
transporter.sendMail({
|
||||
from: 'andris@kreata.ee',
|
||||
to: recipient,
|
||||
subject: 'Test message [' + Date.now() + ']',
|
||||
text: 'Hello world! Current time is ' + new Date().toString(),
|
||||
html: '<p>Hello world! Current time is <em>' + new Date().toString() + '</em></p>'
|
||||
});
|
3
lmtp.js
3
lmtp.js
|
@ -163,6 +163,9 @@ function normalizeAddress(address, withNames) {
|
|||
}
|
||||
|
||||
module.exports = (imap, done) => {
|
||||
if (!config.lmtp.enabled) {
|
||||
return setImmediate(() => done(null, false));
|
||||
}
|
||||
MongoClient.connect(config.mongo, (err, mongo) => {
|
||||
if (err) {
|
||||
log.error('LMTP', 'Could not initialize MongoDB: %s', err.message);
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
"grunt-cli": "^1.2.0",
|
||||
"grunt-eslint": "^19.0.0",
|
||||
"grunt-mocha-test": "^0.13.2",
|
||||
"mocha": "^3.2.0"
|
||||
"mocha": "^3.2.0",
|
||||
"nodemailer": "^3.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"addressparser": "^1.0.1",
|
||||
|
|
13
server.js
13
server.js
|
@ -4,6 +4,7 @@ let config = require('config');
|
|||
let log = require('npmlog');
|
||||
let imap = require('./imap');
|
||||
let lmtp = require('./lmtp');
|
||||
let smtp = require('./smtp');
|
||||
let api = require('./api');
|
||||
|
||||
log.level = config.log.level;
|
||||
|
@ -18,12 +19,18 @@ imap((err, imap) => {
|
|||
log.error('App', 'Failed to start LMTP server');
|
||||
return process.exit(1);
|
||||
}
|
||||
api(imap, err => {
|
||||
smtp(imap, err => {
|
||||
if (err) {
|
||||
log.error('App', 'Failed to start API server');
|
||||
log.error('App', 'Failed to start SMTP server');
|
||||
return process.exit(1);
|
||||
}
|
||||
log.info('App', 'All servers started, ready to process some mail');
|
||||
api(imap, err => {
|
||||
if (err) {
|
||||
log.error('App', 'Failed to start API server');
|
||||
return process.exit(1);
|
||||
}
|
||||
log.info('App', 'All servers started, ready to process some mail');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
194
smtp.js
Normal file
194
smtp.js
Normal file
|
@ -0,0 +1,194 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const log = require('npmlog');
|
||||
const SMTPServer = require('smtp-server').SMTPServer;
|
||||
const mongodb = require('mongodb');
|
||||
const MongoClient = mongodb.MongoClient;
|
||||
const crypto = require('crypto');
|
||||
const punycode = require('punycode');
|
||||
|
||||
let imapServer;
|
||||
let database;
|
||||
|
||||
const server = new SMTPServer({
|
||||
|
||||
// log to console
|
||||
logger: {
|
||||
info(...args) {
|
||||
args.shift();
|
||||
log.info('SMTP', ...args);
|
||||
},
|
||||
debug(...args) {
|
||||
args.shift();
|
||||
log.silly('SMTP', ...args);
|
||||
},
|
||||
error(...args) {
|
||||
args.shift();
|
||||
log.error('SMTP', ...args);
|
||||
}
|
||||
},
|
||||
|
||||
name: false,
|
||||
|
||||
// not required but nice-to-have
|
||||
banner: 'Welcome to Wild Duck Mail Agent',
|
||||
|
||||
// disable STARTTLS to allow authentication in clear text mode
|
||||
disabledCommands: ['AUTH', 'STARTTLS'],
|
||||
|
||||
// Accept messages up to 10 MB
|
||||
size: config.smtp.maxMB * 1024 * 1024,
|
||||
|
||||
// Validate RCPT TO envelope address. Example allows all addresses that do not start with 'deny'
|
||||
// If this method is not set, all addresses are allowed
|
||||
onRcptTo(address, session, callback) {
|
||||
let username = normalizeAddress(address.address);
|
||||
|
||||
database.collection('users').findOne({
|
||||
username
|
||||
}, (err, user) => {
|
||||
if (err) {
|
||||
log.error('SMTP', err);
|
||||
return callback(new Error('Database error'));
|
||||
}
|
||||
if (!user) {
|
||||
return callback(new Error('Unknown recipient'));
|
||||
}
|
||||
|
||||
if (!session.users) {
|
||||
session.users = new Set();
|
||||
}
|
||||
|
||||
session.users.add(username);
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
|
||||
// Handle message stream
|
||||
onData(stream, session, callback) {
|
||||
let chunks = [];
|
||||
let chunklen = 0;
|
||||
let hash = crypto.createHash('md5');
|
||||
stream.on('readable', () => {
|
||||
let chunk;
|
||||
while ((chunk = stream.read()) !== null) {
|
||||
chunks.push(chunk);
|
||||
chunklen += chunk.length;
|
||||
hash.update(chunk);
|
||||
}
|
||||
});
|
||||
|
||||
stream.once('error', err => {
|
||||
log.error('SMTP', err);
|
||||
callback(new Error('Error reading from stream'));
|
||||
});
|
||||
|
||||
stream.once('end', () => {
|
||||
let err;
|
||||
if (stream.sizeExceeded) {
|
||||
err = new Error('Error: message exceeds fixed maximum message size ' + config.smtp.maxMB + ' MB');
|
||||
err.responseCode = 552;
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!session.users || !session.users.size) {
|
||||
return callback(new Error('Nowhere to save the mail to'));
|
||||
}
|
||||
|
||||
let users = Array.from(session.users);
|
||||
let stored = 0;
|
||||
let storeNext = () => {
|
||||
if (stored >= users.length) {
|
||||
return callback(null, 'Message queued as ' + hash.digest('hex').toUpperCase());
|
||||
}
|
||||
|
||||
let username = users[stored++];
|
||||
|
||||
// add Delivered-To
|
||||
let header = Buffer.from('Delivered-To: ' + username + '\r\n');
|
||||
chunks.unshift(header);
|
||||
chunklen += header.length;
|
||||
|
||||
imapServer.addToMailbox(username, 'INBOX', {
|
||||
source: 'SMTP',
|
||||
from: normalizeAddress(session.envelope.mailFrom && session.envelope.mailFrom.address || ''),
|
||||
to: session.envelope.rcptTo.map(item => normalizeAddress(item.address)),
|
||||
origin: session.remoteAddress,
|
||||
originhost: session.clientHostname,
|
||||
transhost: session.hostNameAppearsAs,
|
||||
transtype: session.transmissionType,
|
||||
time: Date.now()
|
||||
}, false, [], Buffer.concat(chunks, chunklen), err => {
|
||||
// remove Delivered-To
|
||||
chunks.shift();
|
||||
chunklen -= header.length;
|
||||
|
||||
if (err) {
|
||||
log.error('SMTP', err);
|
||||
}
|
||||
|
||||
storeNext();
|
||||
});
|
||||
};
|
||||
|
||||
storeNext();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function normalizeAddress(address, withNames) {
|
||||
if (typeof address === 'string') {
|
||||
address = {
|
||||
address
|
||||
};
|
||||
}
|
||||
if (!address || !address.address) {
|
||||
return '';
|
||||
}
|
||||
let user = address.address.substr(0, address.address.lastIndexOf('@'));
|
||||
let domain = address.address.substr(address.address.lastIndexOf('@') + 1);
|
||||
let addr = user.trim() + '@' + punycode.toASCII(domain.toLowerCase().trim());
|
||||
|
||||
if (withNames) {
|
||||
return {
|
||||
name: address.name || '',
|
||||
address: addr
|
||||
};
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
module.exports = (imap, done) => {
|
||||
if (!config.smtp.enabled) {
|
||||
return setImmediate(() => done(null, false));
|
||||
}
|
||||
MongoClient.connect(config.mongo, (err, mongo) => {
|
||||
if (err) {
|
||||
log.error('SMTP', 'Could not initialize MongoDB: %s', err.message);
|
||||
return;
|
||||
}
|
||||
database = mongo;
|
||||
imapServer = imap;
|
||||
|
||||
let started = false;
|
||||
|
||||
server.on('error', err => {
|
||||
if (!started) {
|
||||
started = true;
|
||||
return done(err);
|
||||
}
|
||||
log.error('SMTP', err);
|
||||
});
|
||||
|
||||
server.listen(config.smtp.port, config.smtp.host, () => {
|
||||
if (started) {
|
||||
return server.close();
|
||||
}
|
||||
started = true;
|
||||
done(null, server);
|
||||
});
|
||||
});
|
||||
};
|
Loading…
Add table
Reference in a new issue