Terraform email SNS topic subscription (Bonus: Email List)

Preface

Terraform does not support email as one of its protocols for SNS email subscriptions.

You can read more about it here in their docs.

These are unsupported because the endpoint needs to be authorized and does not generate an ARN until the target email address has been validated. This breaks the Terraform model and as a result are not currently supported.

In this tutorial we will be covering how to bring this functionality by calling CloudFormation. We will create a Terraform module for reusability. While this satisfies our need it will only support a set number of email address.

The second part of the tutorial will cover how to add emails dynamically by accepting a list of email addresses and by using terraforms built in interpolation functions.

Requirements

  • Terraform
  • AWS CLI
  • Editor – (I’ll be using Visual Studio Code)

Tutorial

For a quick overview of what we will be making. We will have a Terraform Module with 4 inputs and 1 output listed below. Our module will run a CloudFormation template that will provide us with the ability to bring email support.

Input

NameDescriptionTypeDefaultRequired
display_nameName shown in confirmation emailsstringyes
email_addressesEmail address to send notifications tolistyes
protocolSNS Protocol to use, for email use – email or email-jsonstringemailno
stack_nameUnique Cloudformation stack name that wraps the SNS topic.stringyes

Output

NameDescription
arnEmail SNS topic ARN

1 Create your Variables.tf

In your editor of choice create a new directory for your Terraform Module.

Create a file called variables.tf, this is the default name used by Terraform for variables. 

In this file we will put our Terraform module variables that we initially went over above. Notice how email_address is defaulted to email.

variable "display_name" {
  type        = "string"
  description = "Name shown in confirmation emails"
}

variable "email_addresses" {
  type        = "list"
  description = "Email address to send notifications to"
}

variable "protocol" {
  default     = "email"
  description = "SNS Protocol to use. email or email-json"
  type        = "string"
}

variable "stack_name" {
  type        = "string"
  description = "Unique Cloudformation stack name that wraps the SNS topic."
}

2 Create your Main.tf

Your main.tf will have two items. 

  • template_file
  • aws_cloudformation_stack

The template_file will reference the CloudFormation file we will be creating in the next section. It will take in two variables

  • display_name
  • subscriptions

The display_name will specifically be the SNS Topic DisplayName used in emails. 

The subscriptions will be the a combination of email_address and protocol, the protocol in our case will default to email.

We are using the interpolation function of Terraform called formatlist. This takes a variable that is a list and formats to a specific structure. In our case "{ \"Endpoint\": \"%s\", \"Protocol\": \"%s\" }" which is what gives us the ability to provide multiple email address instead of one. 

You can read more about formatlist here:

https://www.terraform.io/docs/configuration/interpolation.html#formatlist-format-args-

This Terraform resource does not run anything but create the final form of the template that will be used in the aws_cloudformation_stack

data "template_file" "cloudformation_sns_stack" {
  template = "${file("${path.module}/templates/email-sns-stack.json.tpl")}"

  vars {
    display_name  = "${var.display_name}"
    subscriptions = "${join("," , formatlist("{ \"Endpoint\": \"%s\", \"Protocol\": \"%s\"  }", var.email_addresses, var.protocol))}"
  }
}

The aws_cloudformation_stack will tell AWS to run a CloudFormation template creating our final resource. We use the output of the template_file called rendered which provides us with the final rendered template.

resource "aws_cloudformation_stack" "sns_topic" {
  name          = "${var.stack_name}"
  template_body = "${data.template_file.cloudformation_sns_stack.rendered}"

  tags = "${merge(
    map("Name", "${var.stack_name}")
  )}"
}

How your main.tf should look like:

data "template_file" "cloudformation_sns_stack" {
  template = "${file("${path.module}/templates/email-sns-stack.json.tpl")}"

  vars {
    display_name  = "${var.display_name}"
    subscriptions = "${join("," , formatlist("{ \"Endpoint\": \"%s\", \"Protocol\": \"%s\"  }", var.email_addresses, var.protocol))}"
  }
}

resource "aws_cloudformation_stack" "sns_topic" {
  name          = "${var.stack_name}"
  template_body = "${data.template_file.cloudformation_sns_stack.rendered}"

  tags = "${merge(
    map("Name", "${var.stack_name}")
  )}"
}

3 Create your CloudFormation Template

Create a new folder to store your CloudFormation file. This is not necessary but good for organization. I created a folder called “templates”.

Inside of the templates folder create a new template file. I called mine “email-sns-stack.json.tpl”. 

Inside of this file we will create our SNS Topic EmailSNSTopic. It will contain Terraform variables such as ${display_name} and  ${subscriptions}. Which are the two variables we passed in for the  template_file in the previous section. 

It will output the Email SNS Topic ARN which we will use later to output via Terraform. This will be covered in the next section.

Your final CloudFormation template should look like the following. 

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources" : {
    "EmailSNSTopic": {
      "Type" : "AWS::SNS::Topic",
      "Properties" : {
        "DisplayName" : "${display_name}",
        "Subscription": [
          ${subscriptions}
        ]
      }
    }
  },

  "Outputs" : {
    "ARN" : {
      "Description" : "Email SNS Topic ARN",
      "Value" : { "Ref" : "EmailSNSTopic" }
    }
  }
}

3 Create your Outputs.tf

Everything should now work, but you wont have the Email SNS Topic ARN to pass to your other Terraform resources. 

For that we need to create another file called outputs.tf in the same directory as main.tf

We will pull the CloudFormation output using the following – ${aws_cloudformation_stack.sns_topic.outputs["ARN"]}.

Your final output should look like the following.

output "arn" {
  value       = "${aws_cloudformation_stack.sns_topic.outputs["ARN"]}"
  description = "Email SNS topic ARN"
}

Thats it! Now test it out by running a terraform plan followed by a terraform apply. You can also check the output value with this command terraform output -module=sns-email-topic arn.

The GitHub repository can be found here.
https://github.com/zghafari/tf-sns-email-list

6 comments
24 likes
Prev post: Nuke / Delete ALL your untracked AWS resourcesNext post: Becoming a Cloudiator, attaining all 5 AWS Certifications

Related posts

Comments

  • XDP5D843D www.google.com

    November 8, 2022 at 1:47 am
    Reply

    XDP5D843D www.google.com

  • Shrilesh

    January 7, 2021 at 6:53 am
    Reply

    Thanks for Sharing, this is really helpful to mitigate the TF limitations for SNS email integration. I have enhanced this further to include the Policy

  • jffhjdjjrrf www.yandex.ru

    May 20, 2020 at 8:10 pm
    Reply

    jffhjdjjrrf www.yandex.ru

  • Felipa

    March 6, 2019 at 9:10 am
    Reply

    Have you ever before had troubles with your web host? I'm open for recommendations as my webhost is dreadful at the moment.

  • Akram BLOUZA

    December 14, 2018 at 10:42 am
    Reply

    Very interesting ! And everything works wonderfully.

Leave a Reply

Your email address will not be published. Required fields are marked *