Mail Merge with XPages
Being able to have individualized letters based on a template was one of the drivers to make Word processors popular. In the age of mass-communication of one. This tasks falls no longer to the printer, but your eMail processor. For a complete solution, check out Chris Toohey's excellent Mailer application, that is yours for USD 5.00 only.
I was wondering what it would take to build something similar, minus the address management, in XPages. I defined a few constraints:
As usual YMMV
I was wondering what it would take to build something similar, minus the address management, in XPages. I defined a few constraints:
- I don't want to store the address list or the raw message
- I need to be able to send images
- I need to be able to fine tune the HTML
- No sending of attachments required
- Message needs to be individual using parameters (in the Body only), list of parameters will be flexible supplied together with the eMails as CSV data
MergeManager
Java bean. Along the way I made a few experiences:
- When bound to a bean the XPages RichText control wants a data type of
com.ibm.xsp.http.MimeMultipart
- The IBM Image upload doesn't work with a bean bound RichText control, but dragging and dropping works. I got rid of the button with a single line of code:
config.removeButtons = 'IbmImage';
- When adding the Dropdown for the variables, a simple change allowed me to show the View source button:
{"name" : "mustache", "items" : ["mustache","Source"] });
- I reused parts of Toni's eMail bean, which I stripped of the attachment function and added direct deposition into the
mail.box
. Works like a charm - There are lots of loose ends to consider: using the OpenNTF Domino API to gain easy access to theads to send the messages in the background would be top on the list. Allow template storage, different messages for HTML and Text (and EE), eMail preview some of the others. But that's a story for another time
package com.notessensei;
import java.io.ByteArrayInputStream;
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.Session;
import lotus.domino.Stream;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import com.ibm.xsp.http.MimeMultipart;
public class MergeManager implements Serializable {
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 MimeMultipart message;
private String eMailField = "eMail";
private final ArrayList<String> fields = new ArrayList<String>();
private final MustacheFactory factory = new DefaultMustacheFactory();
private final StringBuilder statusMessages = new StringBuilder();
public MergeManager() {
// Default Constructor to
// be able to work as a bean
}
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();
}
}
public void clear() {
this.message = null;
this.subject = null;
this.rawAddresses = null;
this.statusMessages.delete(0, this.statusMessages.length() - 1);
}
public String getCount() {
return (this.rcpData.isEmpty()) ? "0" : String.valueOf(this.rcpData.size());
}
public String getEmailField() {
return this.eMailField;
}
public ArrayList<String> getFields() {
return this.fields;
}
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 getFrom() {
return this.from;
}
public MimeMultipart getMessage() {
return this.message;
}
public String getRawAddresses() {
return (this.rawAddresses == null) ? ("eMail,salutation,greeting\n"+
"peter.parker@noreply.com,Hi spidy,Your arachno fan\n"+
"st.wissel@sg.ibm.com,Dear NotesSensei,Keep on coding"
: this.rawAddresses;
}
public String getStatus() {
return this.statusMessages.toString();
}
public String getSubject() {
return this.subject;
}
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.getFrom();
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.getSubject());
// 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) {
System.out.println(this.message.getHTML());
System.out.println(this.message.getContentAsText());
Mustache mHTML = this.factory.compile(new StringReader(this.message.getHTML()), "eMailHTML");
Mustache mText = this.factory.compile(new StringReader(this.message.getContentAsText()), "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 setMessage(MimeMultipart message) {
this.message = message;
}
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;
}
}
As usual YMMV
Posted by Stephan H Wissel on 13 December 2014 | Comments (0) | categories: XPages