Consuming the SNS message and sending emails

As the last step, let's create a Lambda function that will subscribe to our topic and send a welcome mail. As usual, let's create our module and the package:

    $ mkdir -p lambda-userregistration-welcomemail/src/main/
java/com/serverlessbook/lambda/userregistration/welcomemail

Then, let's add the package into the settings.gradle file:

echo "include 'lambda-userregistration-welcomemail'" >>
settings.gradle

Let's first create the build.gradle file in our new module and add the required dependencies:

dependencies { 
  compile group: 'com.amazonaws', name: 'aws-lambda-java-events',
version: '1.3.0' compile group: 'com.amazonaws', name: 'aws-java-sdk-ses',
version: '1.11.+'compile group: 'com.google.inject',
name: 'guice', version: guiceVersion }

Then, let's create our Handler class:

public class Handler implements RequestHandler<SNSEvent, Void> { 
  private static final Injector INJECTOR = Guice.createInjector(); 
  private static final Logger LOGGER = Logger.getLogger(Handler.class); 
  private AmazonSimpleEmailServiceClient simpleEmailServiceClient; 
 
  @Inject 
  public Handler setSimpleEmailServiceClient( 
      AmazonSimpleEmailServiceClient simpleEmailServiceClient) { 
    this.simpleEmailServiceClient = simpleEmailServiceClient; 
    return this; 
  } 
 
  public Handler() { 
    INJECTOR.injectMembers(this); 
    Objects.nonNull(simpleEmailServiceClient); 
  } 
 
  private void sendEmail(final String emailAddress) { 
    Destination destination = new Destination().
withToAddresses(emailAddress); Message message = new Message() .withBody(new Body().withText(new Content("Welcome to our forum!"))) .withSubject(new Content("Welcome!")); try { LOGGER.debug("Sending welcome mail to " + emailAddress); simpleEmailServiceClient.sendEmail(new SendEmailRequest() .withDestination(destination) .withSource(System.getenv("SenderEmail")) .withMessage(message) ); LOGGER.debug("Sending welcome mail to " + emailAddress +
" succeeded"); } catch (Exception anyException) { LOGGER.error("Sending welcome mail to " + emailAddress + " failed: ",
anyException); } } @Override public Void handleRequest(SNSEvent input, Context context) { input.getRecords().forEach(snsMessage ->
sendEmail(snsMessage.getSNS().getMessage())); return null; } }

Here, you should pay attention to a couple of things. First of all, we did not want our custom Lambda handler but the standard one because we do not need any custom JSON deserialization. Here, we used the standard AWS library, aws-lambda-java-events, which includes some POJOs for AWS service events. SNSEvent is one of them, which is created in accordance with SNS' event structure. AWS does not provide this type of library in other platforms, but as we are in Java, we are lucky. So, we do not have to worry about parsing the incoming request, and we can directly consume the incoming Java object.

The second thing to pay attention to is how we iterate over the getRecords() method of the SNSEvent object. In most cases, this method will return a list of one element, because in theory for each SNS event, only one Lambda invocation is created. However, there are some people reporting that they received more than one message in the same Lambda invocation. It means, it is not sure nor documented that SNS will send only one SNS message per Lambda invocation. Because of this just in case we iterate over the array, instead of just picking the first element of the array.

The last thing to look at is how we build the SES API request. There are a lot of different options here that you can configure. You can check the SES documentation for deeper knowledge, but our configuration just does its job now and sends a raw text mail.

Before we go any further, let's add the required permission to our Lambda's IAM permission:

{ 
  "Effect": "Allow", 
  "Action": [ 
    "ses:*" 
  ], 
  "Resource": "*" 
} 

Then, let's create the Lambda function with the SenderEmail environment variable. You have to change the variable for your configuration, so you can add any email address belonging to the domain you verified on the SES panel:

"UserRegistrationWelcomeMailLambda": { 
  "Type": "AWS::Lambda::Function", 
  "Properties": { 
    "Handler": "com.serverlessbook.lambda.
userregistration.welcomemail.Handler", "Runtime": "java8", "Timeout": "300", "MemorySize": "1024", "Description": "User registration welcome mail Lambda", "Role": { "Fn::GetAtt": [ "LambdaExecutionRole", "Arn" ] }, "Code": { "S3Bucket": { "Ref": "DeploymentBucket" }, "S3Key": { "Fn::Sub": "artifacts/lambda-userregistration-welcomemail/
${ProjectVersion}/${DeploymentTime}.jar" } }, "Environment": { "Variables": { "SenderEmail": "info@example.com" } }
} }, "UserRegistrationWelcomeMailLambdaPermission": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { "Ref": "UserRegistrationWelcomeMailLambda" }, "Principal": "sns.amazonaws.com", "SourceArn": { "Fn::Sub": "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:*" } } }

The second part of this block was the Lambda permission to let SNS invoke our function. As you remember from the API Gateway part, we should let AWS services invoke our Lambda functions to achieve execution of them on behalf of us. As we did for the API Gateway, here we allow sns.amazonaws.com to identify to execute our Lambda function.

As the last step, we must add a new subscription to UserRegistrationSnsTopic. Let's add this block to the Subscriptions part of UserRegistrationSnsTopic:

{ 
  "Endpoint": { 
    "Fn::GetAtt": [ 
      "UserRegistrationWelcomeMailLambda", 
      "Arn" 
     ] 
   }, 
   "Protocol": "lambda" 
} 

Everything is ready to be run now.

Just try to register a new user with the following command, changing the domain name to yours:

    $ curl  -X POST  -H "Content-Type: application/json" -d
'{"username": "tester3",
email: "success@simulator.amazonses.com"}'
-v https://serverlessbook.example.com/users

You can check the logs of your Lambda function to see that they are triggered via SNS and send the email to the user!