Installing Sendy as a replacement for Mailchimp — 2023 version 6 update, the complete guide, with automated backups to S3, and https configuration

Start here to save yourself a lot of time. This is the complete record of my successful install. Includes a ton of extra detail with the goal of keeping you unstuck.

Alex Jacobs
38 min readDec 4, 2023

This install guide you are now reading is for Sendy version 6. It was tested on Sendy version 6.0.8 installed on a $6 / month Digital Ocean droplet.

If for some reason you need to work with a previous version of Sendy, my sendy version 4 install guide can be found here. There have been significant changes for installing version 6, and everything in this guide has been updated to exactly match the install experience. I verified everything by running through the instructions from start to finish.

Intro and motivation for the change

The quick version: I ran into a hard roadblock after not logging into my basic Mailchimp account for 2 years. All my mailing list data was deleted, and I had to jump through significant hoops with customer service to get it back. That got me looking for a new mailing list management option. I was convinced that it makes more sense to find an alternative to the industry heavies such as: Mailchimp, Hubspot, MailerLite, Constant Contact, ConvertKit, Sparkpost, ActiveCampaign, AWeber, or Emma.

Fortunately, I had recently read a blog post “From MailChimp to Sendy — how I saved $2,400/year on my email list”. The article convinced me to check out Sendy. It’s a self-hosted mailing list software solution. But the blog post didn’t describe how to install it.

After purchasing it and combing through the product support forums, it looked like DigitalOcean was a great choice for hosting based on both price and features. While reading many, many pages of documentation and stack overflow posts,

I took detailed notes on my installation process so I could present a complete, detailed, and verified record of the steps you can take to get a running instance of Sendy.

I will also cover how to secure your Sendy server traffic with HTTPS with a free Certbot certificate, and set up automated mysql database backups to an AWS S3 bucket.

How to — start here

Since the developer of Sendy says “if it doesn’t work out, we’ll refund you”, you can try this whole experiment without any financial risk. I set up my Sendy installation on a $6/month standard droplet. The only other costs are the Amazon SES e-mailing, which is $1.00 per 10 thousand e-mails(!), and your Sendy software purchase which is a one time fee with no additional costs until major version updates. Version 6 launched in March of 2022. This guide was written in December of 2023.

Caveats

Nowhere again in this post will I use the word ‘simple’. But I do believe that following this guide will make your installation process as easy as possible.

I recommend browsing it first, and then following the steps in the order they are presented.

I reinstalled Sendy by following and validating all of the instructions that I’ve documented here in this updated guide for installing Sendy version 6.

The only constant is change, and you may find yourself needing to jump through some unexpected hoops, just as I did during my initial install process. Hopefully everything that I have documented will be current and useful for you. If you found something that has changed, please describe it in the comments and I will update this guide accordingly.

I have made extra sure to implement a quality guide that is up to modern standards by making sure to use some instances of dry humor, at least one sci-fi movie reference, and occasional emoji insertions. As an added nerd bonus, I used the example domain www.contoso.com throughout the guide. I also documented the actual keys/secrets, and ip addresses I used while setting this up so you won’t have the extra mental baggage of trying to parse the syntax of things like <your domain>.

I’ve put in the extra effort to describe various scenarios you may encounter, and also to describe why something is done so you’ll be able to make informed choices if some step has changed since I documented my process.

This is totally doable, so read this guide, warm up your google chops, and I wish you the best on your installation process!

It may be helpful to breathe deeply, drink water, and walk around every once in a while.

Documentation conventions

Text inside a block like this represents something you should type or something visible on the browser page or terminal display.

Text inside a box like this represents the terminal display

$ This represents your local terminal (after $)

# This represents the remote shell (after #)

Note: anywhere you see "contoso.com", change that to the url
where you are hosting your sendy installation

Sail the DigitalOcean: create your droplet (cloud based virtual machine)

Log into your new account, and verify that you have the free account credit. If you are a new customer but you did not sign up through a referral link, send an email to customer service with the referral link. In my experience, they will credit you. I signed up right after finding somebody else’s referral link, and when I e-mailed it to them, they gave me the credit. If you need a referral link, here is mine: https://m.do.co/c/c29fea8989aa

First, create a new ‘project’. The link to click to make a new project may be in the sidebar navigation on the left side of the page.

A view of the sidebar showing the “new project” link.

The next window will ask you: Move resources into the new project? click Skip for now

Next, create your droplet by clicking on “Spin up a Droplet”. I will detail the settings below:

click this “card” or on the button as shown below ⬇️

Click the “Spin up a Droplet” card, or open the green “Create” drop down menu by clicking the button, and then click on the “Droplets” menu item.

Click on this button, or on the “card” as shown above ⬆️

Don’t worry when you see “$32.00 / month” at the bottom of the page. This will change to “$6 / month” after we get our settings correct. Follow along here to do so.

Now you need to select your options for your droplet.

Datacenter. I chose the “New York” Region, and “New York * Datacenter 1 * NYC1”. You may have legal obligations to host your data in a particular region depending on the type of data you are storing, so choose accordingly.

The VPC Network info below the datacenter is just informational.

The next set of options is Choose an image. Select Marketplace

Type “LAMP” into the Search Keyword box.

For ease, we’re going to install on a preinstalled LAMP stack on Ubuntu

At the time of writing, the LAMP Marketplace app is version 22.04. The number is the version of Ubuntu (a distribution flavor of the linux OS). Newer versions (higher numbers), if available, will most likely present the same, or a very similar installation experience. If you have some experience working with the command line and aren’t afraid to google things, go ahead and install with a newer version.

22.04 — Latest release at time of writing.

** Don’t click on “Add a worry-free Managed Database”. We’re going to use the MySQL DB inside the droplet.

** Don’t click on any of the “Recommended for you” items.

Choose size. For my currently puny mailing list, I’m confident with the smallest size virtual machine. I selected:

  • Droplet Type “Shared CPU — Basic”
  • CPU options “Regular Disk Type: SSD”
  • $6/mo

Note: in the future, you can scale cpu/ram up and down as you wish. You can scale disk size up, but not down again.

You may notice that there is a greyed out $4/month option at some datacenters. We can not use that with the LAMP image we are using, since it requires more computing resources than the $4/month virtual machine provides.

Additional storageAdd Volume” not required for this set up.

Choose Authentication Method — choose “Password”. For this demo, I will use a password, not a SSH key, when I sign in to the droplet via SSH. In case you’re wondering what SSH keys allow, they let you to log into the remote server with a private “key” that is automatically sent along with your login request, without needing to authenticate with a password.

Create and keep track of this root password. You won’t have access to it again if you lose it.

We recommend these options

  • Add improved metrics and alerting (free) . Select that.
  • Enable Backups — I chose not to, since you can make and store manual snapshots. However, automating backups on a $6/month container will only cost you $1.20/month, so you may opt for this in order to save having to remember to make snapshots.

Advanced Options — no need to select these options

Finalize and create. Make sure that you are only deploying 1 droplet, and rename it something sane (such as sendy-droplet in my example below), and make sure it is in your newly created ‘project’ (which it will be, unless you already have multiple ‘projects’). Adding a “tag” is not required.

Click the “Create Droplet” button. You will then be redirected to your ‘project’ page, and see a status bar of your droplet creation progress. It takes a couple minutes.

Followed by its successful creation, you will see your droplet IP address, and the green “active” dot to the left of the droplet name. You will need your login password for an upcoming step, so make sure you wrote it down somewhere safe in the step above.

Congratulations, your newly spawned creation is running in the cloud. Now perform a sanity check by entering the ip address into your browser and you should see something like the following:

The “Quick”start Guide button points to this linked document on the Digital Ocean website, which details what software your One-Click installation contains, as well as some configuration information.

Let’s log into the new droplet and set it up!

You’re going to need the root password for your droplet. Head to your command line. We will login with ssh (secure shell).

$ ssh root@72.123.76.188

Which will be greeted by the following appropriately paranoid message

The authenticity of host '72.123.76.188 (72.123.76.188)' can't be established.
ED25519 key fingerprint is SHA256:LFmKw3zSvZZB0/2wjVUyqRGC6xjJZeEarCULwOvBA3M.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?

You do want to continue connecting, so type yes and you should then see the following:

Warning: Permanently added '72.123.76.188' (ED25519) to the list of known hosts.

root@72.123.76.188's password:

The terminal will not show the characters you enter when you type or paste the password. Enter the password that you saved from a previous step, and then press return and you should be logged in and see some boilerplate text describing some of the default configurations of your server.

You are now logged into your droplet, a Linux Ubuntu virtual machine. The normal pwd ls cd mkdir, etc terminal commands work as you would expect.

Update the list of available software

# apt-get update

will update the list of available packages and their versions, but not install or upgrade any packages. It will create output similar to what is shown next:

Get:1 http://security.ubuntu.com/ubuntu jammy-security InRelease [110 kB]
Hit:2 http://mirrors.digitalocean.com/ubuntu jammy InRelease

***[ more loading stuff on the screen…]***

Fetched 110 kB in 4s (29.0 kB/s)
Reading package lists... Done

Upgrade available packages

# apt-get upgrade

Accept the confirmation message and you will see a wall of scrolling text.

This will take a few minutes.

Then you will likely see this strange screen pop up on your terminal:

Now (and any time you see this screen pop up again during the SSH terminal session) Press your keyboard TAB key so that <Ok> is highlighted, and then press ENTER. If you press too many times, just press TAB again until <Ok> is highlighted.

Some messages about restarting services will print to the terminal.

When you get your command line prompt back, your droplet will be updated to the latest version of all currently installed software packages.

Install some new software needed by Sendy

Next we need to install a couple software packages that are not part of the default 1-click installation, but will be needed by Sendy. The first one is php-curl

first, figure out which major.minor version of curl you need

# php --version

This will return a bunch of output including the (major.minor.patch) version that is installed on your server. In my case it’s 8.2.13 which we can use in the next command. The version is made up of [major.minor.patch] but we only need the [major.minor] numbers.

# apt-get install php8.2-curl

Fill in the number that matches the [major.minor] version of php you have installed on your system.

Here’s the next one to install:

# apt-get install php-xml

accept the confirmation dialogue that may pop up and then restart the droplet’s web server with:

# systemctl restart apache2

Next, Create New Virtual Host Files

Virtual host files are the files that specify the actual configuration of our virtual hosts and dictate how the Apache web server will respond to various domain requests.

In English: we need to set up the server to respond to the domain name that you are going to assign to it when you create a DNS record with your domain registrar. Example: if you run the website www.contoso.com, and you plan to install Sendy at sendy.contoso.com, you need to configure the apache web server on the droplet to serve the appropriate files when you visit the sendy domain. These files are hosted in the /var/www/ directory. Read on below to see how to edit these files.

Create The Directory Structure for the files to be served

# mkdir -p /var/www/contoso.com/public_html

Create New Virtual Host Files

These are the files that tell the Apache web server how to respond to web requests. We are going to copy and configure the default host file (named 000-default.conf). The following shell command is one line. (if you copy and paste this, don’t include the “#” in what you paste or else the terminal will treat the command as a comment, and do nothing.

# cp /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/contoso.com.conf 

Now are going to edit this file using nano or vim. If you’ve never used vim, I would not start now. Nano is fairly user-friendly and responds to arrow key movements to move the cursor around the screen just like a familiar text editor. After editing, press control and x together to save the file and return to the terminal.

# nano /etc/apache2/sites-available/contoso.com.conf

The file that appears should begin with

<VirtualHost *:80>

and end with

</VirtualHost>

Don’t worry too much about what’s in the middle. It may vary a bit from what you see below.

Leave everything else as it is. We are going to add one directive (line of configuration code) right after the <VirtualHost *:80> tag:

ServerName sendy.contoso.com

and modify two directives (lines of configuration code). First, change theDocument Root directive to:

DocumentRoot /var/www/contoso.com/public_html

and then remove the word “Indexes” from the line that looks like this:

Options Indexes FollowSymLinks

so that after you edit it looks like this:

Options FollowSymLinks

And then also modify the text inside the <Directory /var/www/html/> tag to:

<Directory /var/www/contoso.com/public_html/>

The final result should be something similar to the following. Don’t worry about all the other lines matching exactly:

<VirtualHost *:80>
ServerName sendy.contoso.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/contoso.com/public_html

<Directory /var/www/contoso.com/public_html/>
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

<IfModule mod_dir.c>
DirectoryIndex index.php index.pl index.cgi index.html index.xhtml index.htm
</IfModule>

</VirtualHost>

This is what mine looks like, but I’m installing Sendy as a subdomain of my personal website domain. If you plan to run Sendy as a standalone server (something like www.contoso.com), then your final result should probably be: (notice the extra directive of ServerAlias)

################# WHEN NOT INSTALLING AS A SUBDOMAIN

<VirtualHost *:80>
ServerName contoso.com
ServerAlias www.contoso.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/contoso.com/public_html

<Directory /var/www/contoso.com/public_html/>
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

<IfModule mod_dir.c>
DirectoryIndex index.php index.pl index.cgi index.html index.xhtml index.htm
</IfModule>

</VirtualHost>

If you edited using nano, when you use control + x to write and exit, you will confirm Save modified buffer? with Y and then enter again to accept the File Name to Write.

Activate The Virtual Host File (and deactivate the default host file)

# a2ensite contoso.com.conf

this will show some confirmation message including a notification about restarting apache. We will do that in a moment.

First you need to deactivate the default virtual host file with:

# a2dissite 000-default.conf

this will also show some confirmation message.

And as the confirmation messages stated, it is now time to reload the Apache server to enable the new Apache configurations.

Reload the Apache server

# systemctl reload apache2

This reloads the Apache web server only , and you should remain logged into the shell.

If you visit the ip address now, you should see something like the following. (You might need to open the IP address again in a new browser tab):

Which is what we want. This is the result of removing Indexes from the Options directive inside of the <Directory...> tag.

**If you see something like this:**

…then the Options directive wasn’t properly updated. Check the syntax above. After you correct it, restart Apache with # systemctl reload apache2

When you reload the browser (you may need to open the address in a new tab), it should show the correct Forbidden message. This is going to keep the public from peering into your /uploads folder after you install Sendy.

Regarding HTTPS traffic to the droplet

Since the 1-click droplet is preconfigured with Certbot, securing your servers web traffic with SSL can be done with just a few commands. However, before we generate a certificate for the server, we need to set up DNS routing to the server so that we make sure we’re generating the certificate for the right domain. Detailed HTTPS instructions will follow, at first we will cover how to get the proper DNS records configured for your droplet.

DNS Routing

Log into your domain name registrar so that we can create some records that will point to your new server.

In my case, for this example server, I would be adding a new record for the DNS records of my domain contoso.com. I would create an “A” record. The A name record would be: sendy, and the value: 72.123.76.188

If you need the IP address again, back on theDigital Ocean dashboard, you can click on sendy-droplet to get the IPv4 address.

I just tested this out with my registrar, and within a couple minutes my subdomain resolved to the server and I saw the same Forbidden message originally seen when accessing the ip address right after changing the <VirtualHost> settings and restarting the Apache server.

If you plan to install a standalone server without a subdomain, then the name of your “A” record would be www, and you would create a second “A” record for the bare root domain (contoso.com in this example). The syntax for this differs by registrar, and may be “@”, or sometimes just a blank field. The value of both of these “A” records would be the ip address of your droplet.

Ideally it will only take a few minutes for your DNS changes to propagate. Feeling impatient or wondering if it worked? Check this out (and update it with your own domain name):

https://www.whatsmydns.net/#A/sendy.contoso.com

This is a pretty cool resource that shows you the status of the DNS propagation. My changes made it around the world in just a few minutes.

Enable HTTPS with SSP certificate installation with Certbot

As part of your 1-click installation, there is an automated SSL certificate generator already installed called Certbot. Now that we have the virtual host files set up correctly, and DNS records correctly configured we can run it.

For my example server, I only need to generate a certificate for the sendy subdomain, and this is what I would run:

# certbot --apache -d sendy.contoso.com

If you are running it for a standalone Sendy installation, you will run it as follows (for the www subdomain, and root domain):

# certbot --apache -d contoso.com -d www.contoso.com

After you run the command, you will be asked

Enter email address (used for urgent renewal and security notices) (Enter 'c' to cancel):

I personally consider entering an email address that you actually check a very good idea so that you can receive notifications in case the automated SSL certificate renewal process doesn’t work. If your certificate expires, browsing to your formerly secure site will not show your site, but instead a “Your Connection Is Not Private” warning page.

If you choose to generate your certificate without the email address you can do so with the command that’s given after you press c at that prompt.

After this step, you will be given the terms of service link and asked to agree. If you agree, you will be asked whether or not you want to share your email address with EFF.

After you answer that, you will see a bunch of output related to the installer trying to access your domain. If your DNS propagation has not completed, or there was an error with how you entered your DNS records, this step will fail.

The output so far should be something like:

root@sendy-droplet:~# certbot --apache -d sendy.contoso.com
Account registered.
Requesting a certificate for sendy.contoso.com

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/sendy.contoso.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/sendy.contoso.com/privkey.pem
This certificate expires on 2024-02-24.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Deploying certificate
Successfully deployed certificate for sendy.contoso.com to /etc/apache2/sites-available/contoso.com-le-ssl.conf
Congratulations! You have successfully enabled HTTPS on https://sendy.contoso.com

Now refresh or navigate to your server url, with your browser (go to your site, such as https://www.contoso.com, NOT the ip address), and you should see the:

💥

Your secure server is up and rolling!

(At this point I still see the “Forbidden. You don’t have permission to access this resource.” message) but now it resolves at the https endpoint.

Let’s take a look at the ssl site configuration .conf file and make sure it properly copied over the directives from the original non-ssl version.

# nano /etc/apache2/sites-available/contoso.com-le-ssl.conf

Should bring up a page that looks pretty much like this. Look to see that the ServerName / DocumentRoot and Directory directives look similar. And also that the Options directive doesn’t have the Indexes keyword. If this .conf file wasn’t copied properly from the http version, you are currently in the nano editor, and can make the necessary changes now. Exit out of the editor with ctrl + x and you will return to the ssh terminal.

<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName sendy.contoso.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/contoso.com/public_html

<Directory /var/www/contoso.com/public_html/>
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

<IfModule mod_dir.c>
DirectoryIndex index.php index.pl index.cgi index.html index.xhtml index.htm
</IfModule>


SSLCertificateFile /etc/letsencrypt/live/sendy.contoso.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/sendy.contoso.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

Next is the initialization of the mysql database and then the upload of Sendy to your server.

Installing Your Mysql Database

Surprise…it’s already installed. Welcome again to the joy of the 1-click droplet installation. We just need to create the Sendy database, a database user/password, and grant the new database user privileges to use the Sendy database.

Login into mysql with # mysql and you should see something like:

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.35-0ubuntu0.22.04.1 (Ubuntu)

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

and create your Sendy database (don’t forget the ; at the end)

mysql> create database sendy;

and you should see:

Query OK, 1 row affected (0.00 sec)

You can confirm it worked with the show databases; command:

mysql> show databases;

+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sendy | <--- you'll see it in the list now
| sys |
+--------------------+
5 rows in set (0.01 sec)

Now we need to create a database user with a password, and grant them permission to use the Sendy database (remember the ;):

Come up with a username and strong password and record it somewhere secure. You’re going to need it again soon.

Type the following command, putting the username after “CREATE USER” and the password after “IDENTIFIED BY”. Don’t forget the quotes around the values, as shown below. (remember the ; at the end):

mysql> CREATE USER 'sendy_db_admin'@'localhost' IDENTIFIED BY '2zrQYe5NC8z2sw87TpC4SWDK';

which should respond with:

Query OK, 0 rows affected (0.00 sec)

Now we grant them privileges (remember the ; at the end):

mysql> GRANT ALL PRIVILEGES ON *.* TO 'sendy_db_admin'@'localhost';

again which should respond with:

Query OK, 0 rows affected (0.00 sec)

Our work is done here. Exit the mysql command line interface with

mysql> exit;

and the terminal will say Bye. Now you are back at the prompt for the root user inside the droplet.

You now have your database name, authorized user and password, and you can set up your Sendy configuration file.

Setting Up Your Sendy Configuration File

Assuming you have already purchased and downloaded Sendy, unzip the file and edit /includes/config.php in your code editor.

This is the part that we’re going to modify:

/*  Set the URL to your Sendy installation (without the trailing slash) */
define('APP_PATH', 'https://your_sendy_installation_url');

/* MySQL database connection credentials (please place values between the apostrophes) */
$dbHost = ''; //MySQL Hostname
$dbUser = ''; //MySQL Username
$dbPass = ''; //MySQL Password
$dbName = ''; //MySQL Database Name

The relevant values for the install in this tutorial would be:

/*  Set the URL to your Sendy installation (without the trailing slash) */
define('APP_PATH', 'https://sendy.contoso.com');

/* MySQL database connection credentials (please place values between the apostrophes) */

$dbHost = 'localhost'; //MySQL Hostname
$dbUser = 'sendy_db_admin'; //MySQL Username
$dbPass = '2zrQYe5NC8z2sw87TpC4SWDK'; //MySQL Password
$dbName = 'sendy'; //MySQL Database Name

'localhost'will be the Hostname regardless of the other values. Enter the username/password for the database user we just created in the last step. And enter the database name you entered in the previous step, which in the demo was'sendy'.

Notice: since we installed the SSL certificate, the ‘APP_PATH’ value has been set to https, and there is no trailing /after the .com

Did you enter a different domain when you purchased Sendy than what you are going to end up using? No problem, just update your domain at the “Changing your licensed domain” link sent to you in your purchase confirmation email. I’ve done this a couple times and the update was immediate. You can find the domain change form again at: https://sendy.co/#forms . That link should open up a popup. If it doesn’t, scroll to the bottom of the page and click on “Contact & Support”

It’s now time to upload Sendy to your server.

Uploading Sendy To Your Server

You can use sftp or rsync. Sftp is probably more intuitive with relatively simple documentation, so I will show the command for rsync. The following command will be issued from a new terminal window, not the ssh shell we’ve been using. The command should be entered as one line. The -ahrvz flags mean “archive/human-readable/recursive/verbose/compressed”. The first folder path is for your local Sendy folder (remember to include the trailing /) and the second location is the DocumentRoot web content folder inside your droplet, which we specified in the virtual host file we edited previously. You will be prompted to supply your droplet root password after issuing this command:

IMPORTANT: the following command must be entered in a new terminal window, NOT in the SSH shell that we have been issuing commands to the server with, (as specified by the $ at the beginning of the command)

Also… don’t forget the trailing slash at the end of the path to your sendy folder:

$ rsync -ahrvz -e ssh /path/to/your/sendy/folder/  root@157.230.133.78:/var/www/contoso.com/public_html/

This will be followed by a confirmation such as:

root@157.230.133.78's password:

After you successfully enter the root password to your DigitalOcean droplet, you will see a massive file list of everything that will be transferred to the server. The listed order of the files may be different, but it should start with something like:

root@72.123.76.188's password:
building file list ... done
./
.htaccess
_compatibility.php
_install.php
app.php
autoresponders-create.php
autoresponders-edit.php
autoresponders-emails.php

and end with something like:

locale/en_US/LC_MESSAGES/default.mo
locale/en_US/LC_MESSAGES/default.po
uploads/

sent 10.98M bytes received 21.23K bytes 709.91K bytes/sec
total size is 21.72M speedup is 1.97

IMPORTANT: now we’re going to issue commands in a SSH shell connected to the Digital Ocean droplet again. NOT in the terminal window you just used to upload your files.

After uploading, you need to make sure that the permissions of the uploads folder is correctly set to 0777 . This can be achieved with

# chmod 0777 /var/www/contoso.com/public_html/uploads/

. And you can check if this worked with

# ls -al /var/www/contoso.com/public_html/uploads/

You should see something like

total 8
drwxrwxrwx 2 501 staff 4096 Jan 12 00:25 .
drwxr-xr-x 10 501 staff 4096 Jan 12 04:31 ..

The important permission bits are the drwxrwxrwx on the same line as the .

The other line relates to the permissions of the parent folder.

If everything has gone according to plan so far, when you reload your browser that is pointing to your new server, you should be redirected to /_install.php.

If you see the following, you’re walking the happy path:

💥

If you don’t, don’t worry. It’s also possible that you might see something like the following:

Not to worry, no matter what is missing, it’s easy to correct. Following the instructions, we navigate to: /_compatibility.php?i=1 and see what needs to be installed. Your results may vary slightly, but likely not too much if you have followed along with this this guide from the beginning.

🤔

Whatever is missing, search the support forum at: sendy.co/forum/ by entering the error into the search box to figure out how to remedy it.

After hunting down any possible remaining packages, your server compatibility checklist should all be green, score 10/10 and navigating to your Sendy url should show you the login page…

…or possibly this:

😶

If this is what you see, you will need to make sure that the domain that you entered as your APP_PATH variable in /includes/config.php matches the domain that you are currently pointed to with your browser, as well as being the domain that you entered when you purchased the product. Domain changes can be accomplished easily. The link for that form is in the email you receive when purchasing the software. You can also find it by clicking “Contact & Support” at the bottom of most sendy web site pages, such as https://sendy.co/troubleshooting. In the form, you enter your license key and in theChange domain to field, enter the updated domain. You do not need to preface it with http(s) when you enter the domain (see below). However you do need the http(s):// preface when defining the APP_PATHvariable in the Sendy config file.

What I would need to enter in that form to enable Sendy for the examples here would be:

For the rest of the setup of Sendy, I leave you to the capable hands of the official getting started documentation. You can pick up at step 5. There is very important information regarding getting your Amazon Web Services IAM credentials set up properly, as well as information on increasing your SES Sending Limits. This usually takes at least 2 days for a new account, so if you are browsing through this documentation and planning on installing this software in the near future:

I recommend you actually handle the AWS part described just above right now so that when you are ready to install you don’t have to deal with the speed bump of waiting for AWS to approve your increased sending limits.

One related tip that I will share is that the first time you log into your Sendy dashboard, you might see this, even if you already had your SES limit raised:

If you know that you are no longer in “Sandbox mode”, the fix is to go into /settings and make sure that this is set properly:

After you have your AWS access keys sorted, install sendy on your Digital Ocean droplet back at /_install.php

At this point, sendy is up and running on your Digital Ocean droplet. You can start using it now. However I recommend you continue through the final steps to set up database backups to AWS because . . .

Things Go Wrong. Protect Your Valuable Mailing List Data By Setting Up Automatic DB Backups And Automating Cloud Storage Of Those Backups.

Let’s set up automatic backups of your database, and automatic transfers of the backups to an AWS S3 container!

Setting Up Automatic Mysql Dumps

Let’s schedule automatic backups of our database. Before we set up the scheduler to handle that, let’s create a storage directory for the backups and backup script. Enser this command in your SSH shell session. Don’t forget the leading “/” in /srv/...

# mkdir /srv/db_bak/

That will create the empty folder where we will have our database backups dumped into.

Let’s create our actual backup script with the nano editor again. Again, don’t forget the leading “/” in /srv/...

# nano /srv/dump_script.sh

Paste the following into the editor and press Esc and then $ to get the lines to word-wrap so you can see everything on the screen at once.

# clear out current local backups
rm -rf /srv/db_bak/*

# backup sendy database
/usr/bin/mysqldump -u'sendy_db_admin' -p'2zrQYe5NC8z2sw87TpC4SWDK' sendy > /srv/db_bak/$(
date +%F)_sendy_backup.sql

# copy sendy "uploads" directory to /srv/db_bak to be backed up to s3 later
cp -r /var/www/contoso.com/public_html/uploads/ /srv/db_bak/

The first chunk of code (rm -rf …) clears out the existing backups on your droplet. Since you will be sending these daily to a remote AWS S3 bucket, you don’t need to keep the previous backup on your droplet. In fact, if you have a large database, it’s fair to say that you don’t want to, as that will continue to write more and more data to your droplet, with the potential of filling up the virtual disk of the droplet over time.

For the second chunk of the code below (/usr/bin/mysqldump -u…), you will substitute the database username and password you created previously. It is a script that will save a backup of your sendy mysql database data to the backup directory you just created.

The third chunk of code (cp -r /var/…) is a script used to copy the uploads folder from your sendy files to the backup directory you just created. It’s good to have a backup of that, as any attachments or images that you upload via Sendy when creating campaigns will be stored in this folder, and the e-mails you send out will be served from your droplet. In case you are sending out massive email campaigns, you will probably want to link to images and uploads served via a CDN, or S3 bucket, and not put undue stress on your server.

now control + x to exit, and confirm you want to save the changes, and confirm the file name. Now to configure the scheduler.

Cron…not Tron

There is a fantastic utility that lives in the Unix-like world, including your droplet, called Cron. We’re going to use it to set up two jobs. One will take periodic snapshots of your database. The other will periodically transfer those snapshots to a secure cloud storage bucket. I’m going to show you how to transfer those backups to an AWS S3 bucket.

Initially there is no user configuration file, known as a crontab, for the cron scheduling utility on your computer. Here’s how to create one:

# crontab -e

which will probably be followed by:

no crontab for root - using an empty one

Select an editor. To change later, run 'select-editor'.
1. /bin/nano <---- easiest
2. /usr/bin/vim.basic
3. /usr/bin/vim.tiny
4. /bin/ed

Choose 1-4 [1]:

BTW, you can make a new default editor choice later by typing select-editor in the droplet SSH terminal.

Unless you already know basic vim commands, go ahead and select the default nano editor by pressing 1 and then enter. You will be greeted with this screen which shows the default file which is entirely commented out with “helpful” boilerplate

# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h dom mon dow command

Let’s erase that boilerplate and replace it with this:

# backing up to /srv/db_bak/
# every day at 8:12 utc, dump database
12 8 * * * . /srv/dump_script.sh > /dev/null 2>&1

Crontable scheduling syntax gets no awards for readability. Check out this site if you want to see more details about what those obscure symbols mean. https://crontab.guru/#12_8_*_*_*

click on the underlined “next” to expand the display

This will run our database backup script every day at 08:12 UTC, which is currently just past midnight here on the West Coast of the US. I picked that time because it’s unlikely that there will be many people interacting with my site in a way that will access the database at that time, and I wanted it to not be exactly on a 5-minute interval since that’s when other scripts will run. Perhaps not necessary to schedule like that, but that’s how I was thinking about it.

You can feel free to set that interval to be more frequent than every day, since the way it’s written, it will overwrite the previous file as long as the current day of the system date is the same.

When the backup script is run by the cron scheduler, the filenames being saved will be formatted as…

db_bak/2023–12–01_sendy_backup.sql

…auto-updating the file prefix as the date changes.

I’m guessing you might want to see this actually work and not have to wait a day. If you want to update the cron scheduling interval temporarily to see if it works, change the first part of the third line to */1 * * * * and it will run every minute. Also don’t forget the . after the last *. Without that, the dump script won’t run. When you save the file, it will ask you to confirm the save, and then confirm the file name. Confirm the temporary file name that it shows you, and after you accept, it will install a new crontab that you can access again for edits and updates with crontab -e. You can also inspect your crontab at any time with crontab -l. You can also change your preferred crontab editor with select-editor.

If you remove the > /dev/null 2>&1 part from the end of the cron script, the output of the job will generate a system mail message each time it runs. You can check it with cat /var/mail/root and delete the mail messages with rm /var/mail/root.

You can confirm that your db is being backed up with the following command:

# ls /srv/db_bak/

Which should output something like:

# ls /srv/db_bak/
2023-12-02_sendy_backup.sql

Now make sure to edit your crontab back with crontab -e to set the timing of the crontab back to daily value which was:

12 8 * * * . /srv/dump_script.sh > /dev/null 2>&1

Since you’ll be back in your Crontab file soon after you set up Sendy, I will save you some time and give you the code for all the cron jobs that you are going to be setting up for the official sendy cron jobs, and this tutorial’s automation scripts. The sendy cron jobs are to to schedule campaigns, activate auto responders, import subscription oriented csv files, and performing mailing list segmentation . Feel free to edit your crontab now, or wait until you get into configuring your Sendy dashboard.

Note: the first three lines are for our database dump script. The other 4 cron tasks are copied from the UI of the sendy installation.

# backing up to /srv/db_bak/
# every day at 8:12 utc, dump database
12 8 * * * . /srv/dump_script.sh > /dev/null 2>&1

#every day at 9:12 utc, synchronize .sql dumps with s3 bucket
# commenting out for now. this step will be described later in the tutorial
# 12 9 * * * s3cmd sync /srv/db_bak/ s3://sendy-sql-dumps/sql-dumps/ > /dev/null 2>&1

# Five minute cron job to schedule emails and send campaigns in the background with auto-resume when server times out
*/5 * * * * php /var/www/contoso.com/public_html/scheduled.php > /dev/null 2>&1

# One minute interval job for importing csv in the background
*/1 * * * * php /var/www/contoso.com/public_html/import-csv.php > /dev/null 2>&1

# One minute interval job for activating auto responders
*/1 * * * * php /var/www/contoso.com/public_html/autoresponders.php > /dev/null 2>&1

# For automatic updating of segments periodically
*/15 * * * * php /var/www/contoso.com/public_html/update-segments.php > /dev/null 2>&1

You can always check if you have the current and correct sendy cron tasks set up on your crontab file by:

  1. first, navigate to: http(s)://your_sendy_installation_url/reset-cron
    In my case, I would navigate to: https://sendy.contoso.com/reset-cron .
  2. Then accept the UI prompt, and you will see a page that looks like this:
cron job reset status, and explicit commands to enter on crontab to set up automated sendy background tasks.

Part 1 of the automation tasks complete! Next is getting your daily database dump files transferred automatically to a S3 bucket on AWS.

Setting Up Automatic Transfers Of The Mysql Backups To The S3 Bucket

Head to AWS S3. First we need to create the AWS S3 bucket that we’re going to send the transfers into. From the S3 console, create a new bucket. The Region is arbitrary, unless you have a specific need to host your data in a particular region. If you’re new to AWS, consider picking one region and sticking to it. It’s often confusing to log into an AWS and not see resources you are sure you set up. The reason is usually because you aren’t in the right AWS region in the UI. I picked Oregon (us-west-2) because many services are offered at relatively low cost in the Oregon region, and it doesn’t matter where in the world you are sending emails, there is no need for low latency when using Sendy.

I didn’t change anything of the default settings for the bucket. Then I clicked “Create bucket”.

Now in the “General Purpose Buckets” list, click on the name of your new bucket. In the “Objects” tab, create a new folder.

Keep the bucket and folder name handy, you will need them soon.

And that’s it for setting up the bucket. Now to create a user and give them appropriate permissions to store things in your bucket.

Setting Up An AWS User In IAM And Granting Them Permission To Access Your Buckets

Head over to the AWS IAM console and click on the Users link over on the left.

Click on Create user and give them a sensible name.

Select Attach policies directly

and in the search box enter: s3full and give the user AmazonS3FullAccess

And then click on Next. I did not add any optional Tags. Then “Create User”.

Back in the list of users, click on your new user’s name. Then click on the “Create access key” link. If there’s no link there, click on “security credentials” then scroll down to “access keys” and click “create access key”.

Select “Application running outside AWS” and “Next”

Optionally add a note in the “Description tag value” field:

And “Create access key”

Make note of the Access Keyand Secret access key values. You will need those soon, and this is the only time that you can access the Secret access key so if you need that value later, you will have to create a new key for this user.

Then click done.

Heading into the home stretch!

Installing AWS S3 Utilities On Your Droplet To Handle The Transfer Of Your Database Backups To The S3 Bucket

Back in your root SSH terminal connection, install the S3 utilities with:

# apt-get install -y s3cmd

You’ll get the strange terminal confirmation screen. Click “tab” then enter.

Now you need to enter your credentials.

# s3cmd --configure

will bring up a dialogue that looks something like:

Enter new values or accept defaults in brackets with Enter.
Refer to user manual for detailed description of all options.

Access key and Secret key are your identifiers for Amazon S3. Leave them empty for using the env variables.
Access Key:

You will enter your Access Key, and Secret Key and for the rest of the values, just click enter to accept the default values unless there is something you want to explicitly set. At the end of the process, make sure that when it tests your credentials that it passes, and then agree to Save settings.

Things will probably have looked something like this:

# s3cmd --configure

Enter new values or accept defaults in brackets with Enter.
Refer to user manual for detailed description of all options.

Access key and Secret key are your identifiers for Amazon S3. Leave them empty for using the env variables.
Access Key: AKIAQNPPI3UUXXIIWWNN
Secret Key: fdjTc7w9NWovRyNxL9WKdN2meTq
Default Region [US]:

Use "s3.amazonaws.com" for S3 Endpoint and not modify it to the target Amazon S3.
S3 Endpoint [s3.amazonaws.com]:

Use "%(bucket)s.s3.amazonaws.com" to the target Amazon S3. "%(bucket)s" and "%(location)s" vars can be used
if the target S3 system supports dns based buckets.
DNS-style bucket+hostname:port template for accessing a bucket [%(bucket)s.s3.amazonaws.com]:

Encryption password is used to protect your files from reading
by unauthorized persons while in transfer to S3
Encryption password:
Path to GPG program [/usr/bin/gpg]:

When using secure HTTPS protocol all communication with Amazon S3
servers is protected from 3rd party eavesdropping. This method is
slower than plain HTTP, and can only be proxied with Python 2.7 or newer
Use HTTPS protocol [Yes]:

On some networks all internet access must go through a HTTP proxy.
Try setting it here if you can't connect to S3 directly
HTTP Proxy server name:

New settings:
Access Key: AKIAQNPPI3UUXXIIWWNN
Secret Key: fdjTc7w9NWovRyNxL9WKdN2meTq
Default Region: US
S3 Endpoint: s3.amazonaws.com
DNS-style bucket+hostname:port template for accessing a bucket: %(bucket)s.s3.amazonaws.com
Encryption password:
Path to GPG program: /usr/bin/gpg
Use HTTPS protocol: True
HTTP Proxy server name:
HTTP Proxy server port: 0

Test access with supplied credentials? [Y/n] y
Please wait, attempting to list all buckets...
Success. Your access key and secret key worked fine :-)

Now verifying that encryption works...
Not configured. Never mind.

Save settings? [y/N]

Save the settings and you are finished with this.

You can test it out now with the following command, assuming you have a db backup from previous test run before in the /srv/db_bak folder. If you don’t already have a backup file, do this first:
# echo 'just a test file' > /srv/db_bak/testFile.txt and then:

s3cmd sync /srv/db_bak/  s3://contoso-sendy-backups/sql-dumps/

which should result in some output like this:

# s3cmd sync /srv/db_bak/  s3://contoso-sendy-backups/sql-dumps/
WARNING: Empty object name on S3 found, ignoring.
upload: '/srv/db_bak/testFile.txt' -> 's3://contoso-sendy-backups/sql-dumps/testFile.txt' [1 of 1]
17 of 17 100% in 0s 176.97 B/s done
Done. Uploaded 17 bytes in 1.0 seconds, 17.00 B/s.

Now checking my S3 bucket, I see that the contents of that folder transferred successfully

Okay…let’s make this a job for Cron!

Add this to your crontab with crontab -e

#every day at 9:12 utc, synchronize .sql dumps with s3 bucket
12 9 * * * s3cmd sync /srv/db_bak/ s3://contoso-sendy-backups/sql-dumps/ > /dev/null 2>&1

This will send the db dump to S3 daily at 9:12 UTC. Again you might want to test this by setting the interval to something much lower (detailed in the first Cron section, above). As a reminder, you can have a cron task run every minute with */1 * * * * (don’t forget to set it back). I think a daily external cloud backup is a sane default. Your needs may differ.

Restoring Database From a Saved Backup

So what do you do if a database disaster happens? If you set up the S3 automated backups as described above, you have a backup!

Restoring your database can be achieved as follows:

  • sign into your S3 console
  • select the backup that you want to restore
  • When you download the file, if it is downloaded as a .txt file. Just rename the file extension to .sql
  • Send the backup to your droplet from the command line with (this is all one line). NOTE: this next command is entered in your computer’s terminal, not in the digital ocean droplet ssh shell. You will need to enter your droplet password when prompted.

$ rsync -ahrvz -e ssh /path/to/your/sql/backup/2023–12–03_sendy_backup.sql root@72.123.76.188:/srv/db_bak/backup_to_restore.sql

You should see:


$ rsync -ahrvz -e ssh ./2023-12-03_sendy_backup.sql root@72.123.76.188:/srv/db_bak/backup_to_restore.sql
root@72.123.76.188's password:
building file list ... done
2023-12-03_sendy_backup.sql

sent 3.77K bytes received 42 bytes 77.03 bytes/sec
total size is 25.67K speedup is 6.73

Now over in your droplet SSH terminal session login to mysql to drop the existing corrupt database. **AN UPCOMING COMMAND WILL IRREVERSIBLY DESTROY THE CURRENT DATABASE** So make sure you are only doing this if you really need to restored the db from one of your backups.

# mysql

then (this is the destructive part):

mysql> drop database sendy;

then re-create the sendy database

mysql> create database sendy;

then exit out of my sql with:

mysql> exit;

If you refresh sendy running in your browser now, you will see the following. DO NOT click “install now”!

We’re going to perform the database restoration in the SSH remote shell with the following command. Note: make sure you have exited out from the mysql> shell prompt by typing “exit” already. This command is entered in the droplet SSH remote shell:

#  mysql sendy < /srv/db_bak/backup_to_restore.sql

Now refresh Sendy in your browser and you should be back in the same state as when the newly restored database backup was created.

Back in business after a DB restore

Let’s Create A Snapshot Of The Droplet

It’s nice to have an “undo” point. Digital Ocean gives you the ability to create snapshots of your virtual servers at any point in time. This is the equivalent of a full “save game” that you can recreate a droplet from in the future. It saves the entirety of your droplet state.

This is a very good time to create a snapshot of your droplet, in case you ever want to recreate your droplet server from this point of having everything fully set up. If you did recreate your droplet server, you would only need to restore your database to the latest saved version in your S3 bucket, as above, and you’d be back in business.

To do this, first we need to shut down the droplet from the droplet SSH shell with:

# poweroff

this should give you some output similar to:

root@sendy-droplet:/srv# Connection to 72.123.76.188 closed by remote host.
Connection to 72.123.76.188 closed.

Now navigate to https://cloud.digitalocean.com/droplets and select your Sendy droplet. You should see that it is currently off.

Click on the Snapshots tab and give your snapshot a sane name. Then click on Take snapshot and it will take a couple minutes.

For the costs quoted at the time of this article, you’re looking at about $0.18 per month to store a snapshot. Cheap insurance.

After the snapshot is complete, head back to the Power menu and turn your droplet back on. Give it a couple minutes to fully reboot before you try to reload, or you may see errors related to connecting to the server or database.

You can take snapshots at any time like this, but of course be mindful of whether or not there are running jobs when you shut it down.

Congratulations, That’s it!

Please comment on how it went! I would love to hear how your process went, and if this guide helped you.

I hope this guide saves you a lot of the headaches that I went through while setting this up. I’m happy knowing that it may have helped you, and if you want to say thank you, please leave a comment, and / or feel free to buy me a beer! 🍻 Cheers!

--

--

Alex Jacobs

Technical mentor. Facilitator of music circles with seniors with Alzheimer’s and dementia. Writing about code and music.