ORIGINAL DRAFT
If you’re doing business, you need to keep in touch with your customers. One of the primary vehicles for communication today is email. It would be nice if you could write each of your customers individually, but when you have more than a handful, the problem gets to be a little out of hand. In print, mail merge operations allow you personalize the letters you send to your customers. So why not apply the same fundamental ideas to electronic mail?
Before we move too far ahead, we have to consider the problems with electronic mail merging. Firstly, if you use it to cold-call customers, this is considered bad form. Bad enough to have forced the powers that be to draft and enact laws that prohibit this kind of mass mailing on the Internet. Spam laws are not a joke. If the people you send mail to have not indicated an interest in receiving email from you, the spam police may come knocking at your door. To avoid this, make sure the recipients have indicated interest in you or your company and make sure you have it on record before sending mass mailings.
That being said, we’re going to develop a program that lets you mail to a list of recipients pulled out of a database using JDBC, merging custom fields from the database into a message template written using XML, and sent through Internet-based email using the JavaMail API. Both the message and the SQL query used to define the mailing criteria are part of the XML message template, so you can construct as many of these as you like. Any field which is part of the JDBC result set returned by the query can be used in the email template.
Basic Design
The primary requirements of our system are that it must be able to send email, based on a template, without overwhelming the network or requiring human intervention. To make sure problems are properly dealt with, we try to make the system robust enough that a single error won’t stop a run dead in its tracks. We also need an audit trail so we can tell what happened, since the program will typically run unattended. Figure 1 shows the basic process flow.
After getting the configuration arguments, we parse the template into a Document Object Model tree and access the embedded query. We then execute the query and end up with a result set to work from. Results are pulled off the database result set one at a time and used to formulate the email, which is a composite of the template message and the merged fields in the template. Fields are replaced by matching values in the result set record. We use a Properties object to access each record as we step through the result set. After sending each email, we check to see if its the last entry in the result set and exit if it is. Otherwise, we move on to the next result and continue until we reach the last entry.
The next effect is pretty straight forward. To make the design more accessible, we provide a test option, which will construct each email, streaming it to standard output for review. These results can be captured in a file and examined to make sure the query and merged templates are consistent with expectation. In addition to this feature, we also provide the ability to throttle the process by pausing an arbitrary number of milliseconds between each email. There are more sophisticated ways of grouping by email address to reduce load on the mail server, but pausing between sends is a fairly simple and effective enough technique.
Figure 2 shows a UML diagram for the classes used in EMailMerge.
These classes are reflective of the process flow and divide the functionality into distinct objects which manage each of the template, query and message handling. You’ll find a basic description for each of these classes in Table 1.
Class | Description |
---|---|
EMailMerge | This is the master program and entry point class. It utilizes the other classes to provide a command-line interface to run EMailMerge. If this class is called without any command-line arguments, it will print a brief usage message showing expected arguments. |
EMailMergeProperties | Extends the Properties class and automatically loads the EMailMerge.properties file when an instance is created. The properties can then be passed around the program as needed. |
EMailMergeLog | This is a class which exposes a few static methods to open and close a log file, loging specified text or exception information. The log file name is automatically generated, based on the current date/time, when the open method is called. |
EMailMergeTemplate | Implements the document template-handling mechanism using XML. The constructor requires a template file name and will automatically load and parse the file into a DOM (Document Object Model) representation, whereupon values like the QUERY and SUBJECT strings can be retrieved. The generateMessage method creates a message based on the record argument. The field replacement is done using the SAX mechanism. |
EMailMergeMessageFilter | SAX filter which watches for the end of MESSAGE tag and produces output to the specified PrintWriter. |
EMailMergeFieldFilter | SAX filter which translates FIELD nodes on-the-fly, based on the record data provided. |
EMailMergeQuery | Implements JDBC access to the database. The constructor sets up a connection and statement to be used by the class. The executeQuery method takes an SQL string and applies the query to the database, sorting the ResultSet internally. The nextRecord method can be used to return a Properties object with populated record fields by name. The finalizer automatically shuts down the connection. |
EMailMergeMessage | This class implements the JavaMail interface for sending messages, based on a set of to, from, subject and text string arguments. This is essentially an abstraction that makes it easier to construct and send a message without needing to worry about the internals. |
Supporting Classes
The EMailMergeProperties class helps us manage the persistent properties of our system. There are 8 configuration elements we need to have access to in the EMailMergeProperties file. The property file can be edited by anyone with a text editor. The configuration arguments supported by the EMailMerge program are:
- The mail host server name for sending emails.
- The mail user name for logging on to the mail server.
- The mail from address for the mail message.
- The interval between emails, in milliseconds, for throttling the process.
- The JDBC driver.
- The source path to access to the database.
- The username that will be used to access the database.
- The password required to access the database.
Figure 3 shows a sample EMailMerge.properties file. By storing these elements in a properties file, we can avoid having to enter information every time the program runs, making it more user friendly. Once configured, virtually anyone can use it, so long as they know which template file they want to use.
Figure 3: An EMailMerge Configuration File
# EMailMerge configuration file
mail.smtp.host=mail.corporate.com
mail.user=marketing
mail.from=marketing@corporate.com
mail.interval=3000
jdbc.driver=sun.jdbc.odbc.JdbcOdbcDriver
jdbc.source=jdbc:odbc:GlobalDataSource
jdbc.username=marketing
jdbc.password=emailmerge
Listing 1 shows the EMailMergeProperties class. We implement a load method to read the properties from a FileInputStream, specifying the file name as the only argument. The constructor automatically calls this method with the EMailMerge.properties file name. The arguments are then accessible by calling the getProperty method. Because we’ll frequently be looking up the from email address from this object, I’ve implemented a getFrom accessor method.
The EMailMerge log file stores information about events as they take place. The file is opened when the program starts and written to as the program executes. Each line is flushed as it is written in case the program is interrupted. Log files are named with the following format: "EMailMerge <DATE> <TIME>.log". For example: "EMailMerge Jan 28, 1999 7,15 PM.log". The colon character, normally used as a time separator, is replaced by a comma. The log file shows every message recipient who was sent an email, along with an indication of whether the mail was successfully sent.
Listing 2 shows the EMailMergeLog class, which is responsible for handling the log file. This is primarily a utility class and the methods and variables are declared static. We will never need more than one log class in this context and carrying a reference to an instance of the class would complicate the rest of the code unnecessarily.
There are only 4 methods in EMailMergeLog. The open method opens the log file, using the current date, which we format with the DateFormat class. We have to replace the colon with a comma because some operating systems reserve the colon character. Having done this, we write a simple message to the console and set up a PrintWriter for subsequent use. The close method is provided to make sure the log file is closed. In our application, the open method will be called first and the close method before exiting the main clause.
Figure 4: Log File Format
[5:18 AM] FILENAME = Template.xml
[5:18 AM] QUERY = SELECT * FROM CUSTOMERS
[5:18 AM] SUBJECT = Welcome!
[5:18 AM] FROM = claude@atrieva.com
[5:18 AM] INTERVAL = 3000
[5:18 AM] Sending message to: claude@atrieva.com
[5:18 AM] Test only: Message not Sent
[5:18 AM] Sending message to: claude@atrieva.com
[5:18 AM] Test only: Message not Sent
[5:18 AM] Sending message to: claude@atrieva.com
[5:18 AM] Test only: Message not Sent
The key methods in EMailMergeLog are the log and exception methods. The log method writes a message to the console and sends the message to the log file, prefixing the text with the current time. We use the DateFormat class again to format the time properly. The syntax for the log file is relatively simple, with each entry preceded by a time stamp in square brackets. Figure 4 shows a sample log file from a simple test run. The only other method we implement is called exception and allows you to send exceptions to the log file after catching them.
Messages and Queries
The EMailMergeMessage class, in Listing 3, handles sending each email message. We use the JavaMail API to accomplish this, but our needs are very modest, so we abstract the send operation in this class for convenience. The constructor creates a JavaMail Session object, which is used in subsequent calls to the send method. For each email sent, we provide the from and to email addresses, the subject and text for the message. Because the JavaMail API does most of the work for us, this is a very simple class. Once created, we can call the send method repeatedly and as often as we like.
Listing 4 shows the EMailMergeQuery class, which handles our SQL queries. We use JDBC to access the database and retrieve the configuration from the EMailMergeProperties object before establishing a Connection and creating a Statement object in the constructor. We implement a finalize method to make sure the connection is not left open when the class is no longer needed. The executeQuery method actually executes the query string and stores the result set in a results instance variable. We’ll pop each of the results off the set with the nextRecord method.
The nextRecord method grabs the next result and places the information in a Properties object, by walking through each field in the record, defined in the result set metadata. We then lookup the value for each field and create a name/value pair in the Properties object for each of the field entries in the record. The nextRecord method then returns the Properties object and awaits subsequent calls. If the last record has been read, the method returns a null value which the program can keep an eye out for.
XML Classes
Our template is designed as an XML document. The document must include the following features:
- A <TEMPLATE> tag, which encompasses all the other elements.
- A <QUERY> tag, which identifies the SQL query to be used.
- A <SUBJECT> tag, which specifies the email subject heading.
- A <MESSAGE> tag which encompasses the body of the message and any optional fields.
- Optional <FIELD> tags in the message section, replaced by the specified database fields.
Figure 5: Typical XML Template
<?xml version="1.0">
<TEMPLATE>
<QUERY>SELECT * FROM DATABASE</QUERY>
<SUBJECT>New Version of Atrieva Available</SUBJECT>
<MESSAGE>
Dear <FIELD>FIRST_NAME</FIELD>,
Get the new Version of Atrieva NOW at "http://www.atrieva.com".
</MESSAGE>
</TEMPLATE>
Figure 5 shows a typical template document. We can make a few observations about this file.
- The "<?xml version="1.0"?>" prefix must be on the first line to identify the document as a version 1.0 XML document.
- The document must include a valid QUERY string which can operate against the database. Once properly designed and tested, this query should not be changed and should never be edited by anyone unfamiliar with SQL. The query string is identified by the <QUERY> start and </QUERY> end tags.
- The document must include a SUBJECT string which identifies the subject line to be placed in the message. This can be any valid message subject string. The subject string is identified by the <SUBJECT> start and </SUBJECT> end tags.
- The body of the message is contained between the <MESSAGE> start and </MESSAGE> end tags. All text formatting included in this section will be translated, verbatim to the final message. Keep in mind that field replacement may affect formatting due to the variable size of replacement data. For example the FIRST_NAME field may be "Claude" as easily as it might be "Areallylongnameisanoying".
- Any occurrence of the FIELD tag will be dynamically replaced by appropriate field data during processing. This is the key to customizing messages and maps directly onto database field names. A field name must be placed between the <FIELD> start and </FIELD> end tags and must be spelled exactly as it is in the database. The program will throw an exception if it finds a template field entry without a matching record entry (though empty field data is fine, as long as the field exists). FIELD tags can be placed anywhere in the MESSAGE sections. They may be repeated and placed in arbitrary order.
To manage the template, we need three classes: the EMailMergeFieldFilter implements a SAX event handler to replace fields as they are discovered. The EMailMergeMessageFilter class implements a SAX event handler which recognizes when we have reached the end of the message, having replaced all relevant fields on the way. This allows us to capture the translated message text. The EMailMergeTemplate class ties all this together and implements a number of utility methods to make processing easier.
Listing 5 shows the EMailMergeFieldFilter, which is responsible for identifying fields which we want to replace in the document. We pass the result set Properties object in the constructor and reference it in the handleElement method. For each XML <FIELD> tag we encounter, the record is checked for a matching field name. The handleElement method is called only when the parser has identified the closing </FIELD> tag element. The text between these tags must be the name of the database field to be replaced. If our check shows that the result record contains the field, the whole tag set is replaced by a single text element as it was found in the database.
If a field in the template is not found, we throw an exception immediately. This will stop the program from continuing but we don’t want emails to be sent with unreplaced fields, lest we leave a bad impression with the recipient. Throwing an exception is unlikely to cause problems because the first message will trigger it if a field is missing from the query. The only proper course of action is to repair the query string so that this does not happen and then try again (probably using the test option to diagnose problems before they become serious).
Listing 6 shows the EMailMergeMessageFilter, which captures the message itself, after the fields have been replaced. The handleElement method is called when the <MESSAGE> content is processed and the </MESSAGE> tag is encountered. This guarantees than any field replacement has already taken place. When this event happens, we stream the message out to the PrintWriter we passed into the constructor and stored as an instance variable for reference. The stream is effectively the message output we need to sent to the EMailMergeMessage class from Listing 3.
The EMailMergeTemplate class is the high-level document management class. Listing 7 shows how it implements a readDocument method to open a stream and read the document into an XML tree using the IBM XML4J library. The readDocument method returns a Document Object Model (DOM) object and is automatically called by the constructor when an EMailMergeTemplate object is created.
We implement a couple of utility methods to find a node in the DOM tree and to get a specific value. The findNode method returns a named node, returning null if the node was not found. The getValue method, searches for a named node but returns the text value in the child node. This is what you need to do to find the text between tags, and so simplifies the rest of the code. Most of the elements we need in the document object are accessible in this way but the message is handled differently because we have to replace some of its entries.
The generateMessage method returns a processed message string. We create a byte stream and a print writer to feed the output to and turn the resulting array into a string before returning it. To do the work, we create an XML parser object and a file input stream to read the template. We register our element filters before calling the parser’s readStream method. The net effect is that we stream the output from the message filter into a byte array, replacing field entries as we go, and return a string representation of the message when we’re done.
Tying it all Together
The EMailMerge class handles command line specification of the template file and fires off the emails. Listing 8 shows how simple this is. We print usage information if no arguments are used, keeping the program fairly easy to use if this is your first attempt. We expect the first argument to be the template file name. An optional "-test" argument may also be provided. Having collected the arguments, we open the log file and fetch the configuration arguments, printing them out to confirm expectations. We then create the template object and get the SQL query string, create the message and query objects and execute the query we found in the template.
There is a static pause method implemented in this class that lets us pause a given number of milliseconds using a thread sleep call. If the test option is used, we loop through the query results and output our response to the console. Otherwise, we use the message send method to fire off each email, pausing between each for the specified time. We capture any exceptions and log them if they are encountered. The only other reason to exit the loop is success, where we have printed to the console or sent the number of emails that matches the result set. If all went well, you should see the "Processing Complete" message when the emails have been processed.
To operate the EMailMerge program, you need Java version 1.1 or higher. This code was tested primarily under Java 1.2 but there are no known dependencies on the new APIs. The following libraries must be present (Under Java 1.2 these libraries can be placed in the jre/lib/ext directory and become transparently available. Under Java 1.1. they must be added to the CLASSPATH):
- The XML4J from IBM was used to implement the XML functionality. XML4J supports both the DOM and SAX models, both of which are used in this program.
- The JavaMail 1.1 API library from Sun is used to implement the mail functionality.
- An appropriate JDBC driver library class must also be present. The JDBC classes are identified in the properties file and are not coupled to any particular database.
Summary
Together, these classes provide mail merge functionality for email, entirely consistent with the kinds of features most of us would recognize from modern word-processing applications, though applied to electronic mail rather than print or publishing mediums. Because we use an architecture based on open standards - with XML providing the template format, queries formulated with standard SQL and accessed through JDBC, and email sent through the JavaMail API
- we are ensured an easy maintenance path and have a solid foundation for easy upgrade development. As new versions of these technologies come along, we can simply plug them in, typically with little or no development effort.
Listing 1
import java.io.*;
import java.util.*;
public class EMailMergeProperties extends Properties
{
protected String filename = "EMailMerge.properties";
public EMailMergeProperties()
throws IOException
{
load(filename);
}
public void load(String filename)
throws IOException
{
FileInputStream file = new FileInputStream(filename);
clear();
load(file);
file.close();
}
public String getFrom()
{
return getProperty("mail.from");
}
}
Listing 2
import java.io.*;
import java.util.*;
import java.text.*;
public class EMailMergeLog
{
protected static String filename;
protected static PrintWriter writer;
protected static DateFormat dateFormat =
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
protected static DateFormat timeFormat =
DateFormat.getTimeInstance(DateFormat.SHORT);
public static void open() throws IOException
{
filename = "EMailMerge " +
dateFormat.format(new Date()) + ".log";
String head = filename.substring(0, filename.indexOf(':'));
String tail = filename.substring(filename.indexOf(':') + 1);
filename = head + "," + tail;
System.out.println("Logging to: " + filename);
writer = new PrintWriter(new FileWriter(filename));
}
public static void close()
{
writer.close();
}
public static void log(String text)
{
System.out.println(">>> " + text.toUpperCase());
writer.print("[" + timeFormat.format(new Date()) + "] ");
writer.println(text);
writer.flush();
}
public static void exception(Throwable exception)
{
System.out.println("ERROR: " + exception.getMessage());
System.out.println("See log file: " + filename);
writer.print("[" + timeFormat.format(new Date()) + "] ");
exception.printStackTrace(writer);
writer.flush();
}
}
Listing 3
import java.io.*;
import java.net.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
public class EMailMergeMessage
{
protected Session session;
public EMailMergeMessage(Properties config)
{
session = Session.getDefaultInstance(config, null);
//session.setDebug(true);
}
public void send(String from, String to, String subject,
String message) throws MessagingException
{
InternetAddress fromAddress = new InternetAddress(from);
InternetAddress[] toAddress = {new InternetAddress(to)};
Message msg = new MimeMessage(session);
msg.setFrom(fromAddress);
msg.setRecipients(Message.RecipientType.TO, toAddress);
msg.setSubject(subject);
msg.setContent(message, "text/plain");
Transport.send(msg);
}
public static void main(String[] args)
throws AddressException, MessagingException, IOException
{
Properties props = new EMailMergeProperties();
EMailMergeMessage message = new EMailMergeMessage(props);
message.send("claude@atrieva.com", "claude@atrieva.com",
"New Test", "This is a new test email");
}
}
Listing 4
import java.io.*;
import java.sql.*;
import java.util.*;
import javax.mail.*;
public class EMailMergeQuery
{
protected Connection connection;
protected Statement statement;
protected ResultSet results;
public EMailMergeQuery(Properties props)
throws ClassNotFoundException, SQLException
{
Class.forName(props.getProperty("jdbc.driver"));
String url = props.getProperty("jdbc.source");
connection = DriverManager.getConnection(url, "", "");
statement = connection.createStatement();
}
public void finalize()
{
try
{
connection.close();
}
catch (SQLException e) {}
}
public void executeQuery(String query)
throws SQLException
{
results = statement.executeQuery(query);
}
public Properties nextRecord()
throws SQLException
{
boolean more = results.next();
if (!more) return null;
Properties record = new Properties();
ResultSetMetaData meta = results.getMetaData();
for (int i = 1; i <= meta.getColumnCount(); i++)
{
String name = meta.getColumnName(i);
String value = results.getString(i);
record.put(name, value);
}
return record;
}
public static void main(String[] args)
throws ClassNotFoundException, SQLException,
IOException, MessagingException
{
EMailMergeTemplate template =
new EMailMergeTemplate("Template.xml");
String sql = template.getValue("QUERY");
String subject = template.getValue("SUBJECT");
EMailMergeProperties props = new EMailMergeProperties();
String from = props.getFrom();
EMailMergeQuery query = new EMailMergeQuery(props);
query.executeQuery(sql);
Properties record;
while ((record = query.nextRecord()) != null)
{
String to = record.getProperty("EMAIL_ADDRESS");
String message = template.generateMessage(record);
System.out.println("FROM: " + from);
System.out.println("TO: " + to);
System.out.println("SUBJECT: " + subject);
System.out.println(message);
}
}
}
Listing 5
import java.util.*;
import org.w3c.dom.*;
import com.ibm.xml.parser.*;
public class EMailMergeFieldFilter implements ElementHandler
{
protected Properties record;
public EMailMergeFieldFilter(Properties record)
{
this.record = record;
}
public TXElement handleElement(TXElement element)
{
String name = element.getNodeName();
if (!name.equalsIgnoreCase("FIELD"))
return element;
Node child = element.getFirstChild();
String value = child.getNodeValue();
if (record.containsKey(value))
{
child.setNodeValue(record.getProperty(value));
element.getParentNode().appendChild(child);
return null;
}
else throw new IllegalArgumentException(
"MISSING DATA FIELD: '" + value + "'");
}
}
Listing 6
import java.io.*;
import java.util.*;
import org.w3c.dom.*;
import com.ibm.xml.parser.*;
public class EMailMergeMessageFilter implements ElementHandler
{
protected PrintWriter writer;
public EMailMergeMessageFilter(PrintWriter writer)
{
this.writer = writer;
}
public TXElement handleElement(TXElement element)
{
String name = element.getNodeName();
if (name.equalsIgnoreCase("MESSAGE"))
{
writer.println(element.getText());
writer.flush();
}
return element;
}
}
Listing 7
import java.io.*;
import java.util.*;
import org.w3c.dom.*;
import com.ibm.xml.parser.*;
public class EMailMergeTemplate
{
protected String filename;
protected Document doc;
public EMailMergeTemplate(String filename)
throws IOException
{
this.filename = filename;
doc = readDocument();
}
public Document readDocument()
throws IOException
{
Parser parser = new Parser(filename);
InputStream input = new FileInputStream(filename);
parser.setPreserveSpace(true);
Document doc = parser.readStream(input);
input.close();
return doc;
}
public String getValue(String name)
{
return findNode(doc, name).getFirstChild().getNodeValue();
}
public Node findNode(Node node, String name)
{
if (node.getNodeName().equals(name))
return node;
if (node.hasChildNodes())
{
NodeList list = node.getChildNodes();
int size = list.getLength();
for (int i = 0; i < size; i++)
{
Node found = findNode(list.item(i), name);
if (found != null) return found;
}
}
return null;
}
public String generateMessage(Properties record)
throws IOException
{
ByteArrayOutputStream array = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(array);
Parser parser = new Parser(filename);
InputStream input = new FileInputStream(filename);
parser.setPreserveSpace(true);
parser.addElementHandler(new EMailMergeFieldFilter(record));
parser.addElementHandler(new EMailMergeMessageFilter(writer));
Document doc = parser.readStream(input);
input.close();
array.close();
return array.toString();
}
public static void main(String[] args)
throws IOException
{
Properties record = new Properties();
record.put("EMAIL_ADDRESS", "claude@atrieva.com");
record.put("FIRST_NAME", "Claude");
record.put("PRODUCT", "Globular Twinkies");
EMailMergeTemplate processor = new EMailMergeTemplate("Template.xml");
System.out.println("QUERY=" + processor.getValue("QUERY"));
System.out.println("SUBJECT=" + processor.getValue("SUBJECT"));
System.out.println("MESSAGE=" +
processor.generateMessage(record));
}
}
Listing 8
import java.io.*;
import java.sql.*;
import java.util.*;
import javax.mail.*;
public class EMailMerge
{
public static void pause(int milliseconds)
{
try
{
Thread.sleep(milliseconds);
}
catch (InterruptedException e) {}
}
public static void main(String[] args)
throws ClassNotFoundException, SQLException,
IOException, MessagingException
{
String filename = "Template.xml";
boolean test = false;
if (args.length == 0)
{
System.out.println();
System.out.println(" EMailMerge - Automated Query/Template EMail System");
System.out.println(" Created by Claude Duguay, Copyright (c) 1999, Atrieva Corp.");
System.out.println();
System.out.println(" USAGE: java EMailMerge <templatefile> [-test]");
System.out.println();
System.out.println(" Using the -test option generates output to the console");
System.out.println(" without sending the email.");
System.out.println();
System.exit(0);
}
else
{
filename = args[0];
if (args.length > 1 && args[1].equalsIgnoreCase("-test"))
{
test = true;
}
}
EMailMergeLog.open();
try
{
EMailMergeLog.log("FILENAME = " + filename);
EMailMergeTemplate template = new EMailMergeTemplate(filename);
String sql = template.getValue("QUERY");
EMailMergeLog.log("QUERY = " + sql);
String subject = template.getValue("SUBJECT");
EMailMergeLog.log("SUBJECT = " + subject);
EMailMergeProperties props = new EMailMergeProperties();
String from = props.getFrom();
EMailMergeLog.log("FROM = " + from);
int interval = Integer.parseInt(props.getProperty("mail.interval"));
EMailMergeLog.log("INTERVAL = " + interval);
EMailMergeMessage message = new EMailMergeMessage(props);
EMailMergeQuery query = new EMailMergeQuery(props);
query.executeQuery(sql);
Properties record;
while ((record = query.nextRecord()) != null)
{
String to = record.getProperty("EMAIL_ADDRESS");
String text = template.generateMessage(record);
EMailMergeLog.log("Sending message to: " + to);
if (test)
{
System.out.println("FROM: " + from);
System.out.println("TO: " + to);
System.out.println("SUBJECT: " + subject);
System.out.println(text);
EMailMergeLog.log("Test only: Message not Sent");
}
else
{
message.send(from, to, subject, text);
EMailMergeLog.log("Message Sent");
EMailMergeLog.log("Pause " + interval + " milliseconds");
pause(interval);
}
}
}
catch (Throwable exception)
{
EMailMergeLog.exception(exception);
}
EMailMergeLog.close();
System.out.println("Processing Complete!");
}
}