20 Apr, 2012 von Falco Nogatz 1
Einfacher SMTP-Server zur Aufgabenverarbeitung
Häufig ärgere ich mich über Webanwendungen, die ihren Nutzern Mails schicken, wenn irgendein Ereignis eingetreten ist oder eine Aktion des Users benötigt wird, eine einfache Antwort auf die Mail aber nicht möglich ist. Ein typisches Szenario ist etwa das Bestätigen der Mailadresse, wenn man sich bei einem neuen Dienst registriert: Man gibt seine Mailadresse in ein Online-Formular ein, bekommt eine Aktivierungsmail zugeschickt und muss darin einen Link anklicken, der wiederum zum Webdienst führt und meine Mailadresse bestätigt. Wäre es nicht einfacher, die Mail so wie sie ist an den Absender zurückzuschicken um sie zu bestätigen?
Dies sei nur als ein Beispiel genannt. Beim nicht mehr gepflegten node.js-Modul smtpevent sind auch noch weitere Szenarien aufgeführt: Viele Mailinglists bieten etwa die Möglichkeit, sich über eine Mail an unsubscribe@mailinglist.de davon abzumelden. Bei Github kann man auf einen Issue direkt antworten, indem man die Benachrichtigungsmail einfach beantwortet. Weitere Szenarien sind leicht vorstellbar: Warum soll ich von unterwegs mit dem Handy eine Weboberfläche bemühen, um remote meine Heizung zu steuern, wenn ich schneller eine Mail mit dem Betreff “On” an heating@meinserver.de schicken kann?
Um eine solche Anwendung zu realisieren wird ein SMTP-Server benötigt. Node.js versteht sich sehr gut auf solche Protokolle und der eventbasierte Ansatz ist bei eingehenden Mails sicher auch nicht verkehrt. Und so gibt es bereits eine kleine Zahl an guter SMTP-Server-Implementierungen. Mir ist vor allem der estländische Programmierer Andris Reiman aufgefallen, der für node.js viele Module geschrieben hat, die sich mit dem Versand und Empfang von Mails beschäftigen. So reichen seine beiden Projekte simplesmtp und mailparser, um einen einfachen SMTP-Server aufzusetzen und die Mails und Anhänge zu parsen.
Zuerst bauen wir den SMTP-Server auf:
var simplesmtp = require('simplesmtp'); var MailParser = require('mailparser').MailParser; var server = simplesmtp.createServer({ validateSenders: false, validateRecipients: false }); server.on('startData', function(envelope) { envelope.mailparser = new MailParser(); envelope.mailparser.on('end', takeMail); }); server.on('data', function(envelope, chunk){ envelope.mailparser.write(chunk); }); server.on('dataReady', function(envelope, callback){ envelope.mailparser.end(); callback(); }); server.listen(25);
Der Code ist relativ selbsterklärend: Bei Eingang einer Mail wird das startData
-Event gefeuert, einzelne Datenchunks werden durch den data
-Listener bearbeitet, und wenn die Mail vollständig übertragen wurde, feuert dataReady
. Der Rahmen envelope
wird dabei allen Funktionen übergeben, sodass envelope.mailparser
auch in allen Funktionsaufrufen für eine Mail der gleiche ist. So werden bei zwei gleichzeitig eingehenden Mails diese nicht versehentlich vermischt.
Sobald die Mail vollständig übertragen wurde (dataReady
), lösen wir mailparser.end()
aus, was das Parsen der Mail beginnt. Fehlt noch die Definition, was mit unserer geparsten Mail denn passieren soll:
function takeMail(mail_object) { console.log('From:', mail_object.from); //[{address:'sender@example.com',name:'Sender Name'}] console.log('Subject:', mail_object.subject); // Hello world! console.log('Text body:', mail_object.text); // How are you today? // Irgendein Datenbankaufruf o.ä. }
Hier kann die Mail nun also ausgewertet und so die gewünschte Aktion ausgelöst werden. Das tolle am Mailparser ist die Tatsache, dass Dateianhänge ebenfalls soweit wie möglich geparst werden. So enthält das mail_object
auch einen Eintrag attachments
, der etwa so aussehen könnte:
attachments = [{ contentType: 'image/png', fileName: 'image.png', contentDisposition: 'attachment', contentId: '5.1321281380971@localhost', transferEncoding: 'base64', length: 126, generatedFileName: 'image.png', checksum: 'e4cef4c6e26037bcf8166905207ea09b', content: <Buffer ...> }];
Attachments-Beispiel von mailparser
So könnte man leicht einen Service wie den von Twitpic einrichten, dass man durch das Senden an eine userspezifische Mailadresse ein Bild hochladen kann.
Wem das ganze zu unsicher scheint, weil ja sonst jeder, der die Mailadresse kennt, Dummheiten anstellen könnte, der sei auf die Serverkonfiguration ganz oben hingewiesen: Werden validateSenders
und validateRecipients
auf true
gesetzt, können Sender und Empfänger in einer eigenen Funktion überprüft und ggf. zurückgewiesen werden. Die simplesmtp-Bibliothek bietet daneben auch noch andere Authorisierungsmechanismen.