wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

Command line mail merge


As shown before mail merging with Notes and {{mustache}} is simple and easy to code. To be able to run merging on the command line, I modified the bean and removed the XPages dependency and added a static main function. Now you can run it off the command line easily. It requires 5 parameters:
  1. the database name
  2. the CSV file
  3. a file with a HTML message including {{mustache}} tags
  4. a file with a Text message including {{mustache}} tags
  5. The senders eMail
There isn't much error handling in that code, so be careful.
package com.notessensei;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;

import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.MIMEEntity;
import lotus.domino.MIMEHeader;
import lotus.domino.NotesException;
import lotus.domino.NotesFactory;
import lotus.domino.NotesThread;
import lotus.domino.Session;
import lotus.domino.Stream;

import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;

public class MergeManager implements Serializable {

    public static void main(String[] args) throws NotesException, IOException {
        // We need 4 parameters: nsfToUse, addresses, html message, text message
        // eMail field in addresses MUST be eMail
        if (args.length < 5) {
            System.out.println("Usage: java MergeManager maifile.nsf, addresses.csv, htmlmessage.txt, textmessage.txt sender");
            System.exit(1);
        }

        String dbName = args[0];
        String adrfilename = args[1];
        String htmlfilename = args[2];
        String txtfilename = args[3];
        String sender = args[4];

        MergeManager mergeManager = new MergeManager();

        // Load the content into the objects
        mergeManager.setRawAddresses(new Scanner(new File(adrfilename)).useDelimiter("\\Z").next());
        mergeManager.setHtmlMessage(new Scanner(new File(htmlfilename)).useDelimiter("\\Z").next());
        mergeManager.setTextMessage(new Scanner(new File(txtfilename)).useDelimiter("\\Z").next());
        mergeManager.setFrom(sender);
        
        // Spin up Notes
        NotesThread.sinitThread();
        Session s = NotesFactory.createSession();
        Database userMail = s.getDatabase("", dbName);
        // Set mailbox to null if you only want to generate the mails
        // but not send them
        Database mailbox = s.getDatabase("", "mail.box");

        mergeManager.process(s, userMail, mailbox);

        System.out.println(mergeManager.getStatus());

        // Shut down
        userMail.recycle();
        mailbox.recycle();
        NotesThread.stermThread();
    }
    private static final long                      serialVersionUID = 1L;
    private String                                 subject;
    private String                                 from;
    private String                                 rawAddresses;
    private final Map<String, Map<String, String>> rcpData          = new TreeMap<String, Map<String, String>>();
    private String                                 htmlMessage;
    private String                                 textMessage;
    private String                                 eMailField       = "eMail";
    private final ArrayList<String>                fields           = new ArrayList<String>();
    private final MustacheFactory                  factory          = new DefaultMustacheFactory();

    private final StringBuilder                    statusMessages   = new StringBuilder();

    private void addOneDataLine(Map<String, Map<String, String>> allRCP, ArrayList<String> fields, String raw) {
        Map<String, String> oneLine = new HashMap<String, String>();
        String key = raw; // Backup pla
        try {
            String[] dataItems = raw.split(",");
            for (int i = 0; i < dataItems.length; i++) {
                oneLine.put(fields.get(i), dataItems[i]);
                if (fields.get(i).equalsIgnoreCase(this.eMailField)) {
                    key = dataItems[i];
                }
            }
            allRCP.put(key, oneLine);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void getFields(ArrayList<String> list, String raw) {
        String[] fieldNames = raw.split(",");
        list.clear();
        for (int i = 0; i < fieldNames.length; i++) {
            list.add(fieldNames[i]);
        }
        return;
    }

    public String getStatus() {
        return this.statusMessages.toString();
    }

    private void populateMail(Session s, Document mail, String htmlBody, String textBody, String receipient) throws NotesException {
        s.setConvertMime(false);

        final MIMEEntity emailRoot = mail.createMIMEEntity("Body");

        // Primary Header
        String fromSender = this.from;
        MIMEHeader emailHeader = emailRoot.createHeader("Return-Path");
        emailHeader.setHeaderVal(fromSender);
        emailHeader = emailRoot.createHeader("From");
        emailHeader.setHeaderVal(fromSender);
        emailHeader = emailRoot.createHeader("Sender");
        emailHeader.setHeaderVal(fromSender);

        emailHeader = emailRoot.createHeader("Recipients");
        emailHeader.setHeaderVal(receipient);

        emailHeader = emailRoot.createHeader("To");
        emailHeader.setHeaderVal(receipient);

        emailHeader = emailRoot.createHeader("Subject");
        emailHeader.setHeaderVal(this.subject);

        // Text and HTML Body
        MIMEEntity emailRootChild = emailRoot.createChildEntity();
        final String boundary = System.currentTimeMillis() + "-" + String.valueOf(this.hashCode());
        emailHeader = emailRootChild.createHeader("Content-Type");
        emailHeader.setHeaderVal("multipart/alternative; boundary=\"" + boundary + "\"");

        MIMEEntity emailChild = emailRootChild.createChildEntity();

        Stream stream = s.createStream();
        stream.writeText(textBody);
        emailChild.setContentFromText(stream, "text/plain; charset=\"UTF-8\"", MIMEEntity.ENC_NONE);
        stream.close();

        emailChild = emailRootChild.createChildEntity();
        stream = s.createStream();
        stream.writeText(htmlBody);
        emailChild.setContentFromText(stream, "text/html; charset=\"UTF-8\"", MIMEEntity.ENC_NONE);
        stream.close();
        stream.recycle();
        stream = null;
        s.setConvertMime(true);
    }

    public void process(Session s, Database database, Database mailbox) {
        Mustache mHTML = this.factory.compile(new StringReader(this.htmlMessage), "eMailHTML");
        Mustache mText = this.factory.compile(new StringReader(this.textMessage), "eMailPlain");
        for (Map<String, String> oneRecord : this.rcpData.values()) {
            this.sendOneMessage(s, database, mailbox, mHTML, mText, oneRecord);
        }
    }

    private void processRawAdresses(String raw) {
        ByteArrayInputStream in = new ByteArrayInputStream(raw.getBytes());
        boolean firstLine = true;
        Scanner scanner = new Scanner(in);

        while (scanner.hasNextLine()) {
            if (firstLine) {
                this.rcpData.clear();
                this.getFields(this.fields, scanner.nextLine());
                firstLine = false;
            } else {
                this.addOneDataLine(this.rcpData, this.fields, scanner.nextLine());
            }
        }
        this.statusMessages.append(String.format("%x Records loaded and ready\n", this.rcpData.size()));
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void sendOneMessage(Session s, Database database, Database mailbox, Mustache mHTML, Mustache mText,
                Map<String, String> oneRecord) {

        // Text as HTML and as plain Text
        StringWriter htmlBody = new StringWriter();
        StringWriter textBody = new StringWriter();
        mHTML.execute(htmlBody, oneRecord);
        mText.execute(textBody, oneRecord);
        try {
            Document mail = database.createDocument();
            String rcpt = oneRecord.get(this.eMailField);
            mail.replaceItemValue("Form", "Memo");
            this.populateMail(s, mail, htmlBody.toString(), textBody.toString(), rcpt);
            mail.save();
            if (mailbox != null) {
                mail.copyToDatabase(mailbox);
            }
            mail.recycle();
            this.statusMessages.append("Mail send to ");
            this.statusMessages.append(rcpt);
            this.statusMessages.append("\n");
        } catch (Exception <e) {
            this.statusMessages.append(e.getMessage());
            this.statusMessages.append("\n");
        }
    }

    public void setEmailField(String mailField) {
        this.eMailField = mailField;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public void setHtmlMessage(String htmlMessage) {
        this.htmlMessage = htmlMessage;
    }

    public void setRawAddresses(String rawAddresses) {
        if (rawAddresses != null && !rawAddresses.equals(this.rawAddresses)) {
            this.processRawAdresses(rawAddresses);
            this.rawAddresses = rawAddresses;
        }
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public void setTextMessage(String textMessage) {
        this.textMessage = textMessage;
    }
}

As usual YMMV

Posted by on 13 December 2014 | Comments (1) | categories: IBM Notes

Comments

  1. posted by Stephan H. Wissel on Tuesday 30 December 2014 AD:

    Of course nobody would manually call the command line (**we wouldn't**). However in a batch file that might come handy.