Wednesday, October 4, 2017

AWS SES for sending emails using Java

We had been using emails for marketing some of the product offerings we have. Instead of creating our own email Server for sending emails, we had been using AWS SES for the same. In this blog, I would be posting the code (Java) and the configuration files to send emails in bulk using the AWS SES. Sending emails through SES is quite cheap, more details about the pricing here.

Here I am going with the assumption that the readers of this blog are a bit familiar with Java, AWS and Maven. They should also be having an account with AWS. If you don't have an AWS account, here are the steps.

Step 1: Login to the AWS SES Management Console and get the email address verified. This should be the same email address from which the emails will be sent. The AWS documentation for the same is here.


Step 2: Go to the AWS SQS Management Console and create a Queue. All the default setting should be good enough.


Step 3: Go to the AWS SNS Management Console and create a Topic. For this tutorial 'SESNotifications' topic has been created.


Step 4: Select the topic which has been created in the previous step and go to 'Actions -> Subscribe to topic'. The SQS Queue Endpoint (ARN) can be got from the previous step.


Step 5: Go to the SES Management Console and create a Configuration Set as shown below. More about the Configuration Sets here and here.


With the above configuration when an email is sent then any Bounce, Click, Complaint, Open events will be sent to the SNS topic and from there it will go to the SQS Queue. The above sequence completes the steps to be done in the AWS Management console. Now will look at the code and configuration files for sending the emails. The below files are required for sending the emails

File 1 : SendEmailViaSES.java - Uses the AWS Java SDK API to send emails.
package com.thecloudavenue;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.simpleemail.AmazonSimpleEmailServiceClient;
import com.amazonaws.services.simpleemail.model.Body;
import com.amazonaws.services.simpleemail.model.Content;
import com.amazonaws.services.simpleemail.model.Destination;
import com.amazonaws.services.simpleemail.model.Message;
import com.amazonaws.services.simpleemail.model.SendEmailRequest;

public class SendEmailViaSES {

 public static void main(String[] args) {

  System.out.println("Attempting to send emails through Amazon SES .....");

  try {

   if (args.length == 0) {
    System.out.println(
      "Proper Usage is: java -cp jar_path com.thecloudavenue.SendEmailViaSES config.properties_path");
    System.exit(0);
   }

   File f = new File(args[0]);
   if (!f.isFile()) {
    System.out.println(
      "Please make sure that the config.properties is specified properly in the command line");
    System.exit(0);
   }

   System.out.println("\n\nLoading the config.properties file .....");
   Properties prop = new Properties();
   InputStream input = null;
   input = new FileInputStream(args[0]);
   prop.load(input);

   String template_file_path = new String(prop.getProperty("template_file_path"));
   String template_file_name = new String(prop.getProperty("template_file_name"));
   f = new File(template_file_path + "\\\\" + template_file_name);
   if (!f.isFile()) {
    System.out.println(
      "Please make sure that the template_file_path and  template_file_name are set proper in the config.properties");
    System.exit(0);
   }

   String email_db_path = new String(prop.getProperty("email_db_path"));
   f = new File(email_db_path);
   if (!f.isFile()) {
    System.out.println("Please make sure that the email_db_path is set proper in the config.properties");
    System.exit(0);
   }

   String from_address = new String(prop.getProperty("from_address"));
   String email_subject = new String(prop.getProperty("email_subject"));
   Long sleep_in_milliseconds = new Long(prop.getProperty("sleep_in_milliseconds"));
   String ses_configuration_set = new String(prop.getProperty("ses_configuration_set"));

   System.out.println("Setting the Velocity to read the template using the absolute path .....");
   Properties p = new Properties();
   p.setProperty("file.resource.loader.path", template_file_path);
   Velocity.init(p);

   AmazonSimpleEmailServiceClient client = new AmazonSimpleEmailServiceClient();
   Region REGION = Region.getRegion(Regions.US_EAST_1);
   client.setRegion(REGION);

   System.out.println("Getting the Velocity Template file .....");
   VelocityContext context = new VelocityContext();
   Template t = Velocity.getTemplate(template_file_name);

   System.out.println("Reading the email db file .....\n\n");
   FileInputStream fstream = new FileInputStream(email_db_path);
   DataInputStream in = new DataInputStream(fstream);
   BufferedReader br = new BufferedReader(new InputStreamReader(in));
   String strLine, destination_email, name;

   int count = 0;

   while ((strLine = br.readLine()) != null) {

    count++;

    // extract the email from the line
    StringTokenizer st = new StringTokenizer(strLine, ",");
    destination_email = st.nextElement().toString();

    // Check if the email is valid or not
    Pattern ptr = Pattern.compile(
      "(?:(?:\\r\\n)?[ \\t])*(?:(?:(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*|(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)*\\<(?:(?:\\r\\n)?[ \\t])*(?:@(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*(?:,@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*)*:(?:(?:\\r\\n)?[ \\t])*)?(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*\\>(?:(?:\\r\\n)?[ \\t])*)|(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)*:(?:(?:\\r\\n)?[ \\t])*(?:(?:(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*|(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)*\\<(?:(?:\\r\\n)?[ \\t])*(?:@(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*(?:,@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*)*:(?:(?:\\r\\n)?[ \\t])*)?(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*\\>(?:(?:\\r\\n)?[ \\t])*)(?:,\\s*(?:(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*|(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)*\\<(?:(?:\\r\\n)?[ \\t])*(?:@(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*(?:,@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*)*:(?:(?:\\r\\n)?[ \\t])*)?(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\"(?:[^\\\"\\r\\\\]|\\\\.|(?:(?:\\r\\n)?[ \\t]))*\"(?:(?:\\r\\n)?[ \\t])*))*@(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*)(?:\\.(?:(?:\\r\\n)?[ \\t])*(?:[^()<>@,;:\\\\\".\\[\\] \\000-\\031]+(?:(?:(?:\\r\\n)?[ \\t])+|\\Z|(?=[\\[\"()<>@,;:\\\\\".\\[\\]]))|\\[([^\\[\\]\\r\\\\]|\\\\.)*\\](?:(?:\\r\\n)?[ \\t])*))*\\>(?:(?:\\r\\n)?[ \\t])*))*)?;\\s*)");
    if (!ptr.matcher(destination_email).matches()) {
     System.out.println("Invalid email : " + destination_email);
     continue;
    }

    // Figure out the name to be used in the email content
    if (st.hasMoreTokens()) {
     // if the email db has the name use it
     name = st.nextElement().toString();
    } else {
     // if not then use the string before @ as the name
     int index = destination_email.indexOf('@');
     name = destination_email.substring(0, index);
    }

    Destination destination = new Destination().withToAddresses(destination_email);

    // Use the velocity template to create the html
    context.put("name", name);
    StringWriter writer = new StringWriter();
    t.merge(context, writer);

    // Create the email content to be sent
    Content subject = new Content().withData(email_subject);
    Content textBody = new Content().withData(writer.toString());
    Body body = new Body().withHtml(textBody);
    Message message = new Message().withSubject(subject).withBody(body);
    SendEmailRequest request = new SendEmailRequest().withSource(from_address).withDestination(destination)
      .withMessage(message).withConfigurationSetName(ses_configuration_set);

    // Send the email using SES
    client.sendEmail(request);

    System.out
      .println(count + " -- Sent email to " + destination_email + " with name as " + name + ".....");

    // Sleep as AWS SES puts a limit on how many email can be sent per second
    Thread.sleep(sleep_in_milliseconds);

   }

   in.close();

   System.out.println("\n\nAll the emails sent!");

  } catch (Exception ex) {
   System.out.println("\n\nAll the emails have not been sent. Please send the below error.");
   ex.printStackTrace();
  }
 }
}
File 2 : GetMessagesFromSQS.java - Uses the AWS Java SDK API to get the Bounce, Click, Complaint, Open events from the SQS Queue.
package com.thecloudavenue;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

import com.amazonaws.regions.Regions;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.sqs.model.DeleteMessageRequest;
import com.amazonaws.services.sqs.model.GetQueueAttributesRequest;
import com.amazonaws.services.sqs.model.GetQueueAttributesResult;
import com.amazonaws.services.sqs.model.Message;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;

public class GetMessagesFromSQS {

 public static void main(String[] args) throws Exception {

  System.out.println("Attempting to get messages from Amazon SQS .....");

  try {

   if (args.length == 0) {
    System.out.println(
      "Proper Usage is: java -cp jar_path com.thecloudavenue.GetMessagesFromSQS config.properties_path");
    System.exit(0);
   }

   File f = new File(args[0]);
   if (!f.isFile()) {
    System.out.println(
      "Please make sure that the config.properties is specified properly in the command line");
    System.exit(0);
   }

   System.out.println("\n\nLoading the config.properties file .....");
   Properties prop = new Properties();
   InputStream input = null;
   input = new FileInputStream(args[0]);
   prop.load(input);

   String message_output_file_path = new String(prop.getProperty("message_output_file_path"));
   String sqs_queue_name = new String(prop.getProperty("sqs_queue_name"));

   AmazonSQS sqs = AmazonSQSClientBuilder.standard().withRegion(Regions.US_EAST_1).build();
   String myQueueUrl = sqs.getQueueUrl(sqs_queue_name).getQueueUrl();

   int approximateNumberOfMessages = 0;

   GetQueueAttributesResult gqaResult = sqs.getQueueAttributes(
     new GetQueueAttributesRequest(myQueueUrl).withAttributeNames("ApproximateNumberOfMessages"));
   if (gqaResult.getAttributes().size() == 0) {
    System.out.println("Queue " + sqs_queue_name + " has no attributes");
   } else {
    for (String key : gqaResult.getAttributes().keySet()) {
     System.out.println(String.format("\n%s = %s", key, gqaResult.getAttributes().get(key)));
     approximateNumberOfMessages = Integer.parseInt(gqaResult.getAttributes().get(key));

    }
   }

   FileWriter fstream = new FileWriter(message_output_file_path, true);
   BufferedWriter out = new BufferedWriter(fstream);

   ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest(myQueueUrl);
   receiveMessageRequest.setMaxNumberOfMessages(10);

   int pendingNumberOfMessages = approximateNumberOfMessages;

   for (int i = 1; i <= approximateNumberOfMessages; i++) {

    List messages = sqs.receiveMessage(receiveMessageRequest).getMessages();
    int count = messages.size();
    System.out.println("\ncount == " + count);

    for (Message message : messages) {

     System.out.println("Writing the message to the file .....");
     out.write(message.getBody());

     System.out.println("Deleting the message from the queue .....");
     String messageRecieptHandle = message.getReceiptHandle();
     sqs.deleteMessage(new DeleteMessageRequest(myQueueUrl, messageRecieptHandle));

    }

    pendingNumberOfMessages = pendingNumberOfMessages - count;
    System.out.println("pendingNumberOfMessages = " + pendingNumberOfMessages);

    if (pendingNumberOfMessages <= 0) {
     break;
    }
   }

   out.close();

   System.out.println("\n\nGot all the messages into the file");

  } catch (Exception ex) {
   System.out.println("\n\nAll the messages have not been got from the queue. Please send the below error.");
   ex.printStackTrace();
  }
 }
}
File 3 : emaildb.txt - List of emails. It can also take the name of the person after the comma to send a customized email. The last two emails are used to test bounce and complaints, more here.
praveen4cloud@gmail.com,praveen
praveensripati@gmail.com
bounce@simulator.amazonses.com
complaint@simulator.amazonses.com
File 4 : email.vm - The email template which has to be sent. The $name in the below email template will be replaced with the name from the above file. If the name is not there then the email id will be used in place of the $name.
<html>
 <body>
  Dear $name,<br/><br/>
   Welcome to thecloudavenue.com <a href="http://www.thecloudavenue.com/">here</a>.<br/><br/>
  Thanks,
  Praveen
 </body>
</html>
File 5 : config.properties - The properties to configure the Java program. Note that the path may be need to be modified as per where the files have been placed.
email_db_path=E:\\WorkSpace\\SES\\sendemail\\resources\\emaildb.txt
message_output_file_path=E:\\WorkSpace\\SES\\sendemail\\resources\\out.txt

from_address="Praveen Sripati" <praveensripati@gmail.com>
email_subject=Exciting oppurtunities in Big Data
template_file_path=E:\\WorkSpace\\SES\\sendemail\\resources
template_file_name=email.vm
sleep_in_milliseconds=100

ses_configuration_set=email-project
sqs_queue_name=SESQueue
File 6 : pom.xml - The maven file with all the dependencies.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.thecloudavenue</groupId>
 <artifactId>sendemail</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <name>sendemail</name>
 <dependencies>
  <dependency>
   <groupId>org.apache.velocity</groupId>
   <artifactId>velocity</artifactId>
   <version>1.7</version>
   <scope>compile</scope>
  </dependency>
  <dependency>
   <groupId>com.amazonaws</groupId>
   <artifactId>aws-java-sdk</artifactId>
   <version>1.11.179</version>
   <scope>compile</scope>
  </dependency>
  <dependency>
   <groupId>com.amazonaws</groupId>
   <artifactId>amazon-kinesis-client</artifactId>
   <version>1.2.1</version>
   <scope>compile</scope>
  </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.0.0</version>
    <executions>
     <execution>
      <phase>package</phase>
      <goals>
       <goal>shade</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
  </plugins>
 </build>
</project>
File 7 : credentials file in the profile folder. I have kept mine in the C:\Users\psripati\.aws folder under windows. Create the access keys as mentioned here. The format of the credentials file is mentioned here. Note that it's better to create an IAM user with the permissions to send SES emails and to read messages from the SQS queue. The access keys have to be created for this user.

Compile the java files and package them as a jar file. Now lets see how to run the program.

For sending the emails, open the command prompt and run the below command. Note to replace the correct sendemail-0.0.1-SNAPSHOT.jar and the config.properties path.

java -cp E:\WorkSpace\SES\sendemail\target\sendemail-0.0.1-SNAPSHOT.jar com.thecloudavenue.SendEmailViaSES E:\WorkSpace\SES\sendemail\resources\config.properties

For getting the list of complaints, bounces, opened and clicked emails, open the command prompt and run the below command. Note to replace the correct sendemail-0.0.1-SNAPSHOT.jar and the config.properties path.

java -cp E:\WorkSpace\SES\sendemail\target\sendemail-0.0.1-SNAPSHOT.jar com.thecloudavenue`.GetMessagesFromSQS E:\WorkSpace\SES\sendemail\resources\config.properties

I know that  there are a sequence of steps to get started for using SES, but once the whole setup has been done it should be piece of cake to use SES for sending emails. On top of every thing, it's a lot cheaper sending emails via SES than setting up a email server.

Note : This article has used Apache Velocity which is a Java based template engine for sending customized emails to the email recipients. AWS SES has included this functionality in the SDK itself, so there no need to use Apache Velocity. Here is a blog on the same.

No comments:

Post a Comment