Sendy is an application that allows you to use Amazon SES to send newsletters to your subscribers. It is also much, much cheaper than the alternatives like Campaign Monitor and MailChimp. Sendy costs $69 once off (at the time of writing) and it should save you the cost multiple times over. Get a domain (one where you could perhaps use newsletters for a lot of your different projects) and use it as a service provider. You can even charge other users to send newsletter services if you want to build a newsletter service.
Companies Mentioned
is an amazing application. It's basically software that allows you to utilize Amazon SES to send newsletters to your members. It is also much, much cheaper than the alternatives. Sendy costs $69 once off (at the time of writing), and it should save you the cost multiple times over, assuming you have some subscribers to send newsletters to!
Why Sendy
Here are the cost benefits over alternatives like Campaign Monitor and MailChimp:
I was originally using Mailchimp for my old WordPress blog. However, I knew that when I hit 2,000 subscribers, I would enter the next pricing tier, which didn't make financial sense yet. So, when I migrated my blog to ghost, I switched to SendGrid, which I used for transactional emails. I also needed to migrate/delete members who had signed up to my Ghost blog into my SendGrid email list instead.
So after some research, I found a Zapier integration that would connect my Ghost blog to SendGrid and add/update/delete members as required. The only issue is that this was for version 2 (which is now deprecated), and version 3 of the integration only had some basic API functions (bummer).
So I contacted the SendGrid support team to inquire if it was possible to get the integration between Ghost and SendGrid updated. They said they would add it to their list, which means it will happen on some unspecified day in the future. This left me with two choices: keep manually exporting my contact list from Ghost and adding it to my Newsletter email list or figure out some other solution. So that is how I stumbled onto Sendy because after scanning the Zapier integration list, I saw that, yes, Sendy was listed!
Getting Started
The normal way to install is to spin up a VPS (I recommend ) and follow the installation instructions provided on the Sendy website. This works great, but I like using Kubernetes, so I wanted to figure out a way to get it working on my clusters. Luckily, also lets you build Kubernetes Clusters!
To start, get a domain (one where you could perhaps use newsletters for a lot of your different projects). For example, something like projects.com could be cool because you could have 'newsletters.projects.com.’ You can even charge other users to send newsletters if you want to build a newsletter provider service.
You can now purchase your Sendy license or upgrade from a stable release version.
In your Sendy dashboard, set your domain as the licensed one. If you want to use staging and production clusters, it will also let you deploy on sub-domains for your licensed domain.
If you need to change the domain for your license code later, you can do so at Change Licensed Domain and insert your license key:
Now let's set up your database. I decided to use a helm chart of MariaDB. You can also use a managed database from DigitalOcean or another provider.
If you have installed the self-managed version of MariaDB (This link is the replicated version - Galera) on your cluster, you can also install a visual interface tool such as .
Once installed, you can either open it up to the outside using a NodePort configuration or via a LoadBalancer (ensure you enabled HTTPS on your database), or you can port forward the database on localhost and connect using your browser. To port forward, you can do:
Forwarding from 127.0.0.1:8888 -> 8080
Forwarding from [::1]:8888 -> 8080
Now you can go to your browser and in a new tab, type the following in the address bar:
//localhost:8888/
You should now see the login page:
When you are logged in successfully, you should see a section that lets you create a new user. Create a user with the name "sendy" and a secure password of your choosing. You will also see a checkbox to create a database table for this user. Enable this as the following image showcases:
You should store these details in a secure location as we will be using them soon.
After some research, I found that a developer called bubbajames had created a docker file so that you could deploy Sendy using the docker platform. This was great for people that used docker on their VPS, but I wanted to go one step further. Having that docker file was immensely useful for my plan. You will see why shortly.
You can download the entire project folder from my repo. Inside you will see the docker folder. Open this in your editor and navigate to the /includes folder. Inside you should see the config.php file. Here, edit the following and insert the database, user, and domain details you created for Sendy. You can enter HTTPS instead of HTTP for the domain section.
The issue with the docker image is that it was created using version 5.2 of Sendy (He now updated it to 5.2.3) and hasn't been updated yet (at the time of writing).
I like to keep my software updated to avail all the fancy features. It's free for the incremental updates until the next major version, so head over to Sendy and download the latest zip file if you haven't already (You will need to enter your license code).
Once you have the latest zip, you can go ahead and change the following files in your docker folder (That you downloaded from my repo or Sendy).
So in the artifacts folder, change the file name from 5.2 to 5.2.3
You can replace the old sendy-5.2.zip file with the one you downloaded called sendy-5.2.3.zip.
In the root of the docker folder is a version file, inside change the value from 5.2 to 5.2.3
Inside the Dockerfile you can change:
ARG SENDY_VER=5.2
ARG ARTIFACT_DIR=5.2
To:
ARG SENDY_VER=5.2.3
ARG ARTIFACT_DIR=5.2.3
Sendy Replication On Kubernetes
I am not replicating Sendy on my staging and production cluster. This is because Sendy uses cron jobs, so I would need to remove the cron job file, create a Kubernetes CronJob that would run, and send commands to my Sendy pods.
All of this is done to avoid having multiple pods duplicating actions because of the individual cron jobs running inside them. I opted for just having one instance running to avoid this. In the future, I might spend some time building a more elegant solution, or if this is something that interests you, please open a pull request at .
Project Structure
Everything from this point on you should do for both the staging and for the production environments separately.
Ensure you have a SQL database ready that works with Sendy. MariaDB is a good one. Ensure you create the credentials that are needed. Alternatively, you can use a managed database with DigitalOcean.
Our project folder structure will look like the following:
sendy
/production <- see repo for full files
/staging <- see repo for full files
/docker
cron
Dockerfile
version
/artifacts
docker-entrypoint.sh
/5.2.3
sendy-5.2.3.zip
/includes
config.php
update.php
You can see the entire file list in the .
For the secrets files, we should encrypt the details. This lets us deploy the files to our repository, to have one source of truth. So for this, you should install and configure sealed secrets for both your staging and production clusters. Of course, you could also ignore this step if you wanted to deploy normal secrets (encoded in base64, not encrypted).
If you decide to use encryption, you should install sealed secrets using the helm chart here: SealedSecrets Once installed, be sure to check the correct resources are running by using the following command:
kubectl get all -n kube-system
This should show you resources in the kube-system namespace where you installed the SealedSecrets helm chart (assuming you kept the default settings):
Now that we have confirmed that sealed secrets is installed, you need to download the cert.pem file for both your production and staging clusters. To get the public encryption key from a cluster, you can:
In some commands below, I will be using these cert.pem files to encrypt some secret files. So it will look for them in a certain location. You can also change the command to suit your own cert.pem file locations. The folder structure where I store my .pem files is :
Now we need to set some key/value pairs. These values will be used inside the cluster to access a Sendy Docker file that we will be creating. It will be stored in our GitLab image repository.For the variables, you first need to encode them in base64 format. This is the standardized encoding that Kubernetes uses. You can encode each password or value using the following command:
echo -n 'superpower' | base64
Which would result in:
c3VwZXJwb3dlcg==
If you need to decode to remember what they are, you can run:
echo 'c3VwZXJwb3dlcg==' | base64 -D
Now you need to set up the permissions for your docker registry in Gitlab. This will let us store the newly created Dockerfile there during the deployment stage.For this, we need to navigate to the following section within your repo:
Once here, you should be able to create a deploy token with the following details:
Now you need to gather all the base64 values for your user within Gitlab; it is good practice to create a new user if you are using the Administrator account (ensure the new user has been added with the relevant permissions to the Sendy repo - ensure you have also created a repo called 'sendy').
When you have collected all the base64 values, you can now add them to your docker-config.yml file for staging and production (should be unique per different cluster). Ensure the URL path matches the URL of your sendy repo in GitLab.
We will now encode this docker configuration file and add it to a secrets file so that our cluster can use it. You can do this by using the following command from the root of your project folder:
This file is also added to the .gitignore so that it will not be committed online.We will now turn this secret (which has the private details encoded) into a SealedSecret, which becomes encrypted.
So we run this command from the root of the project folder. Be sure to specify where your cert.pem file is located (ensure it is the correct one for either the staging or the production cluster)
Now that file is ready to be used in the deployment. Next, we move onto some of the other files. Be sure to replace "domain" with whatever your project name is (without the extension, i.e .com, .net, etc.). You will also replace it in the file names. These are just the production files; you need to do the same for the staging files. You can see them in the .
newsletters-domain-ingress-route.yml
This file is used to route traffic that hits our Traefik LoadBalancer to the correct application within our cluster. My cluster uses Traefik for a LoadBalancer, but you can also use nginx or others; you need to replace my Ingress files with yours.
In your domain provider, ensure to set the LoadBalancers provided IP A records for staging and production (newsletters.staging.domain.com and newsletters.domain.com). You can follow the Traefik instructions here.
This file is used to issue the SSL cert so that we can use HTTPS for our Sendy integration. I used the CertManager helm chart for this; there are other alternatives if you wish to use those (Traefik itself has a built-in letsencrypt certificate manager - I just liked the extra control from CertManager).
For the email section in the file, put in where you would like to get the email notifications from letsencrypt. For example, I have a catch-all set up in my email for my domain so that will send it to my email. You could also create another address for your domain’s email; basically, it's up to you.
This file is used to create a middleware object that can intercept traffic and basically stick something in the middle. For example, I use this one to redirect HTTP traffic to HTTPS. Some other types you can use are authentication middlewares that ask for a password.