Stephen A. Fuqua (saf)

a Bahá'í, software engineer, and nature lover in Austin, Texas, USA

Mini E-mail Campaign With Node.js

Over the weekend I had what at first appeared to be a small challenge: send out a few hundreds e-mails for a non-profit’s outreach campaign. MailChimp and other mailings lists were not a good fit, as these messages were of the cold-call variety, and a formal mailing list felt too spammy. Initially I wrote a utility in .NET, but ended up solving with Node.js instead due to timeouts experienced with SmtpClient.

Requirements: parse a CSV file and then send HTML-formatted e-mail through an SMTP server. The body of the e-mail has a place holder for the recipient’s name. Log the results.

Really, this should be trivial. It does not take a team lead software engineer to write this. But somehow my .NET client just kept timing out. I tried several servers and had the same response. I disabled firewalls, but with no luck. Finally, I decided to just try another language. As I’ve not written anything in Perl or PHP in many years (both well suited to the task), I chose Node.js. And I was able to send e-mail on the first try.

It seems there is something wrong with the System.Net.Mail.SmtpClient. I tried both synchronous and asynchronous, by the way. And I’ve used the SmtpClient with 2.0 and 3.5 applications, though never with 4.0+. There are a few people who have experienced similar problems. Although disabling the firewalls did not help, I still wonder there is something about the Windows 8 security settings.

What I realized is that I was just wasting my time trying to get this working .NET. Switching to Node.js also gave me a good opportunity to get some practice in with this elegant, highly-componetized framework. The keys to success were using the built-in filesystem API and NPM installing the csv and emailjs modules.

Though quick-and-dirty, I give this to the Internet for my own future reference if nothing else.

var campaign = {
    user: "arthur@the.roundtable.uk", 
    password: "12345678909876", 
    host: "mailserver.the.roundtable.com",
    port: 465, 
    templateFile: "c:/temp/invitationToGrailSearch.txt",
    logFile: "c:/temp/invitationToGrailSearch.log",
    addressFile: "c:/temp/completeListOfTheKnightsOfAlbion.txt",
    from: "King Arthur <arthur@the.roundtable.uk>;",
    subject: "Open Invitation to Join in the Grail Hunt"
};
 
 var csv = require('csv');
var fs = require('fs');
 
var logToFile = function (message) {
    fs.appendFileSync(campaign.logFile, message + "\r\n");
};

// Setup mail server connection
var email = require("emailjs/email");
var server = email.server.connect( {
    user: campaign.user,
    password: campaign.password,
    host: campaign.host,
    port: campaign.port,
    ssl: true
});

// Read in the template file
var template = fs.readFileSync(campaign.templateFile).toString();

// Now read in the address file, containing lines with "Name, email"
fs.readFile(campaign.addressFile, function (err, data) {
    if (err) {
        logToFile(err);
        throw err;
    }
    
    // now that we've read the file, need to parse it
    csv.parse(data.toString(), function (parseError, output) {
        if (parseError) {
            logToFile(parseError);
            throw parseError;
        }
        
        // for each line from the file
        var lineNumber = 0;
        output.forEach(function (record) {
            lineNumber++;

            if (record.length != 2) {
                logToFile("Invalid entry on line " + lineNumber.toString() + ", which has " + record.length.toString() + " columns");
                return;
            }

            var name = record[0].trim();
            var email = record[1].trim();
            
            var message = {
                from: campaign.from, 
                to: '"' + name + '" &lt;' + email + '&gt;',
                subject: campaign.subject,
                attachment: 
                [
                    // my template only has a single placeholder - "[person]"
                    { data: template.replace('[person]', name), alternative: true }
                ]
            };
            
            server.send(message, function (err, message) {
                logToFile(message.header.to.toString() + ", " + (err || 'success').toString() );
            });
        });
    });
});

Posted with : Tech, General Programming, JavaScript