Build a Bulk Emailer for Salesforce with App Engine

Sometimes you just want to send a crapload of email from Salesforce.com. However, like every PaaS platform there are limits baked into the multi-tenant environment so you don't stomp on other tenants' resources. Salesforce.com limits you to 2000 emails per day for each Salesforce license. So if you don't have a lot of Salesforce licenses or a different kind of license, you may be out of luck if you want to send out large volumes. There are a few AppExchange products but they seem more targeted towards marketing purposes.

Google App Engine may be a good solution in this case. With Google App Engine quotas you get 7,000 Mail API calls per day free and can bump that up as high as 1.7M with a paid account.

Here's how to roll your own basic bulk emailer using Google App Engine. Take a look at the video below, but it essentially queries Force.com for records, uses the Google Mail Java API to send out individual emails and then send an administrator a notification via Jabber (Google Talk). You can schedule your application (essentially a Servlet) to run on a timed basis using the cron service.

All of this code is at this GitHub repo.

Most of the important code is in the MailServlet class.

package com.jeffdouglas.emailer;

[removed imports]

/**
 * MailServlet.java - a simple, schedulable servlet for sending mail
 * with salesforce.com
 * @author Jeff Douglas
 * @version 1.0
 * @see http://code.google.com/appengine/docs/java/mail/overview.html
 * for more details on using Mail with App Engine
 */

@SuppressWarnings("serial")
public class MailServlet extends HttpServlet {

    private static final Logger logger = Logger.getLogger(ConnectionManager.class.getName());
    private String jabberRecipient = "jeffdonthemic@gmail.com";

    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {

        resp.setContentType("text/html");
        String mailerMsg = "No contact found to email!!";
        QueryResult result = null;

        // get a reference to the salesforce connection
        PartnerConnection connection = ConnectionManager.getConnectionManager().getConnection();

        try {
            // query for contacts based upon some criteria -- emailNotSent boolean
            result = connection.query("Select Id, FirstName, LastName, Email " +
                "FROM Contact Where Email = 'jeff@jeffdouglas.com' Limit 1");
        } catch (ConnectionException e) {
            e.printStackTrace();
            logger.severe(e.getCause().toString());
        } catch (NullPointerException npe) {
            npe.printStackTrace();
            logger.severe(npe.getCause().toString());
        }

        // if records were returned then send out email
        if (result != null) {

          for (SObject contact : result.getRecords()) {

            // construct the 'name' for the email recipient
            String contactName = contact.getField("FirstName").toString() + " " + 
              contact.getField("LastName").toString();

            logger.info("Sending emil to " + contactName + " at " + 
              contact.getField("Email").toString());

            /// send the email
            mailerMsg = sendMail(contact.getField("Email").toString(), contactName);

            // send a jabber notification of the status
            sendJabberNotification(jabberRecipient, mailerMsg);

          }

          // TODO - make a call back into salesforce and update these records
          // as having their emails sent. Implementation is up to you.

        } else {
          logger.warning("No results returned from salesforce");
        }

        resp.getOutputStream().println(mailerMsg);

    }  

    /**  
     * Sends an email
     * @param toAddress the email address of the recipient
     * @param toName the name that appears for the recipeint in their email client  
     * @return A String representing the status of the email sent  
     */  
    private String sendMail(String toAddress, String toName) {

      String msg = "Email sent successfully to " + toAddress;
      Properties props = new Properties();
      Session session = Session.getDefaultInstance(props, null); 

      String messageBody = "This is the body of my email";

      try {

          Message emailMessage = new MimeMessage(session);
          //  must be the email address of an administrator for the application. see docs
          emailMessage.setFrom(new InternetAddress("jeffdonthemic@gmail.com","Jeff Douglas"));
          emailMessage.addRecipient(Message.RecipientType.TO, 
            new InternetAddress(toAddress, toName));
          emailMessage.setSubject("My Email Subject");
          emailMessage.setText(messageBody);
          Transport.send(emailMessage);

      } catch (AddressException e) {
          msg = e.toString();
      } catch (MessagingException e) {
          msg = e.toString();
      } catch (UnsupportedEncodingException e) {
          msg = e.toString();
      }

      return msg;
    }

    /**  
     * Sends a message to any XMPP-compatible chat messaging service (google talk). 
     * See http://code.google.com/appengine/docs/java/xmpp/overview.html
     * for more detils
     * @param recipient the jid of the jabber recipient of the notification
     * @param msgBody the body of the message to be sent  
     */  
    private void sendJabberNotification(String recipient, String msgBody) {

      JID jid = new JID(recipient);

      com.google.appengine.api.xmpp.Message msg = new MessageBuilder()
          .withRecipientJids(jid)
          .withBody(msgBody)
          .build();

      boolean messageSent = false;
      XMPPService xmpp = XMPPServiceFactory.getXMPPService();

      if (xmpp.getPresence(jid).isAvailable()) {
          SendResponse status = xmpp.sendMessage(msg);
          messageSent = (status.getStatusMap().get(jid) == SendResponse.Status.SUCCESS);
      }

      logger.info("Jabber notifiation sent: " + messageSent);

    }

}