Using Amazon SES to send mails from a Ghost blog

Photo by Campaign Creators on Unsplash

Why do I want to use Amazon Simple Email Service?

In the beginning I hosted my blog on DigitalOcean and used Mailgun to send emails to subscribers, as you can see in my previous post. But after some time I moved the blog to AWS and now it is running inside an EC2 instance. Unfortunately Amazon is blocking all outgoing SMTP traffic on EC2 instances, which means sending mails via Mailgun won’t work. When I requested this restriction to be removed, Amazon denied it with the following message:

Set up Amazon SES

When getting started with Amazon Simple Email Service your account will be in a test environment called sandbox. This means you can only send emails to addresses you verified before. So the first step is to get out of this sandbox. To achieve that you need to contact Amazon by creating a new support case. A detailed description about how to get out of the sandbox is available here.

Use Amazon SES for administrative mails

Sending administrative mails (e.g. signup mails) with Amazon SES is very easy. The only thing you have to do is to edit your Ghost config, which is located in /var/www/ghost/ and is named config.production.json by default. There you need to add the following settings:

"mail": { 
"transport": "SMTP",
"options": {
"host": "<SES-HOST>",
"port": 465,
"service": "SES",
"auth": {
"user": "<SES-ACCESS-KEY-ID>",
"pass": "<SES-ACCESS-KEY>"

Use Amazon SES to inform users about new posts

Ghost has a very nice integration to send mails about new posts via Mailgun. When using this, you can automatically send a mail to all your subscribers when creating a new post. Unfortunately this works only when using Mailgun, as one of their staff members stated in a forum post:

Install the AWS Command Line Interface

To make the script working the AWS Command Line Interface (CLI) needs to be installed on your local machine. You can read more about how to install it here.

Create a mail template

First I created a template for the mails that should be sent. As all administrative mails are created by Ghost automatically I just had to create the template for the mail that informs subscribers about new posts. A mail template basically looks like this (copied from the tutorial provided by AWS):

"Template": {
"TemplateName": "MyTemplate",
"SubjectPart": "Greetings, {{name}}!",
"HtmlPart": "<h1>Hello {{name}},</h1><p>Your favorite animal is {{favoriteanimal}}.</p>",
"TextPart": "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}."
  • TemplateName: the name of the template
  • SubjectPart: the subject of the mail
  • HtmlPart: the HTML body of the mail
  • TextPart: the text body of the mail if a mail client can't or doesn't display HTML
"Template": {
"TemplateName": "NewPostTemplate",
"SubjectPart": "New Post on {{postTitle}}",
"HtmlPart": "[html I described above - too long to display here]",
"TextPart": "Hey there\r\n\r\n, A new post is available on\r\n{{postTitle}}\r\n{{postUrl}}\r\n\r\nAll the best,\r\nKathi from "
$ aws ses create-template --cli-input-json file://newPostTemplate.json

Create a SES configuration set to get informed about errors

For this one I didn’t make anything special and simply followed Part 1 of this tutorial. Therefore, I won’t describe the steps I made here. I named the configuration set NewPostConfig.

Write a script to send mails

As I wrote in beginning of this section, my goal is to semi-automate mail sending. This means every time a new post is created, I have to manually trigger a script that automatically fetches the latest post and sends a mail to all subscribers. The full script can be found on GitHub. In the following I’ll explain how it works.

aws ses send-bulk-templated-email --cli-input-json file://mybulkemail.json
"Source":"Mary Major <>",
"ConfigurationSetName": "ConfigSet",
"Destination": {
"ToAddresses": [
"ReplacementTemplateData":"{ \"name\":\"Anaya\", \"favoriteanimal\":\"angelfish\" }"
"DefaultTemplateData":"{ \"name\":\"friend\", \"favoriteanimal\":\"unknown\" }"
  • Source: mail address (and if wanted name) of the sender
  • Template: the name of the template that should be used to create the mail (the one we created above)
  • ConfigurationSetName: the name of the configuration set to use (created in the previous section)
  • Destinations: an array of Destination objects
    -Destination: the recipients
    *ToAddresses: the addresses the mail should be sent to
    *CcAddresses: the addresses the mail should be sent in cc
    *BccAddresses: the addresses the mail should be sent in bcc
    - ReplacementTemplateData: In the template it was possible to define replacement tags (e.g. {{name}}). You can define their replacements for this special destination here.
  • DefaultTemplateData: In the template it was possible to define replacement tags (e.g. {{name}}). You can define their replacements for all destinations here.

Get the DefaultTemplateData

When taking a look at the template file that was created earlier, it becomes clear that the following variables need to be set: {{postTitle}}, {{postExcerpt}}, {{postUrl}} and {{unsubscribeUrl}}. The first three are the same for all people the mail is sent to, so we can put them into the DefaultTemplateData. The unsubscribeUrl is different for each subscriber, so it needs to be put into the ReplacementTemplateData inside Destination.

  • key: an API key you can generate in Ghost's backend. More information can be found here
  • fields: the fields to query, in this case title, url and html for the content
  • limit: how many posts to query, as we only need the latest post, 1 is the value to set
post_data=$(curl -X GET "${ghost_api_key}&fields=${title_key},${url_key},${content_key}&limit=1")

Get all Destinations

Unfortunately the Ghost Content API doesn’t include an endpoint to get all subscribers. Therefore, we need to find another way to get the subscribers. In the Ghost admin panel it is possible to export a JSON file containing lots of information about a blog. You can read more about that here. As it’s not possible to fetch the subscribers via REST, this will be the way to go. So this file needs to be downloaded and passed as an argument to the script. Inside the script jq is used to extract the subscribers (all members that have the subscribed flag set) and store them in a list of Destination objects.

Generate the full JSON template

Now the most tricky parts (getting the information about subscribers and the latest post) are done, and the last step is to generate the template file. Generating the content of this file can of course be achieved with jq again. After that the result just has to be written to a file.

Finally send the mail

Now the full template is created and the very last step to complete the task is to send the mail via Amazon SES. This requires the following command.

aws ses send-bulk-templated-email --cli-input-json file://$template_file

Full workflow to send a mail to all subscribers

To send a mail to all subscribers after publishing a post you now need to complete the following steps:

  1. Go to the Ghost admin panel and head to Labs -> Export Content, click Export and download the export file.
  2. Invoke the just created script like this:
./inform_users_about_new_post GHOST_API_KEY PATH_TO_EXPORT_FILE


Within this blog post I described how I semi-automated the process of informing all Ghost blog subscribers about a new post via Amazon SES. This required some additional work, as Ghost only supports Mailgun out of the box. I used the AWS CLI to send a mail via Amazon SES and automated the template creation with a Shell script. The full source code can be found on GitHub.




Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Katharina Sick

Katharina Sick

Creative and detail-oriented software developer. Advanced from mobile to backend development and now getting into full stack solutions.