Self-Hosted Apps: Programmatic Email

I host a few small web projects using CapRover, a simple web-based container orchestration framework built upon Docker Services. For non-critical hobby apps, it is perfect. I simply run CapRover on a virtual private server (VPS) with DigitalOcean and containerize all my running applications; forgoing all systemd setup and dependency installation.

One feature that I needed was programmatic email. This was necessary for welcome emails, password resets and notifications. CapRover, being a simple docker service orchestrator couldn’t help me. So I needed to find my own solution.

Before organizing this self-hosted system, I was using MailGun and it was working well for me, but I didn’t want to ‘hardcode’ reliance on external platforms within my apps. I wanted to be as platform-agnostic as possible. At the same time, I knew I couldn’t directly send emails from my VPS (Virtual Private Server), as that would be complex to set up and prone to be marked as spam.

I decided that what I needed was an internal service that would be queried via an agnostic interface (JSON over HTTP) which would then send the mail via SMTP, another agnostic interface. This would allow all applications to send emails without needing any ‘emailing code dependencies’ and also allow me to change the actual SMTP server as desired for flexibility in just one place.

I implemented this mailservice in less than 150 lines of go and used the following schema for the JSON request:

type AttachmentInfo struct {
    Name           string `json:"name"`
    Base64Contents string `json:"base64contents"`
}

type MailSendRequest struct {
    FromAddress string           `json:"from_address"`
    ToAddress   string           `json:"to_address"`
    Subject     string           `json:"subject"`
    Body        string           `json:"body"`
    ContentType string           `json:"content_type"`
    Attachments []AttachmentInfo `json:"attachments"`
}

As far as the SMTP server, I used Amazon SES for it’s low cost and reliability. Whenever I wanted to send email from a new domain, I had to verify I owned it via the Amazon web panel.

Now it is easy for me to add low-cost emailing capabilities to any new project via this unexposed service running within Docker; it would be as easy as an HTTP request. No libraries needed. Moving my whole deployment to a different orchestration platform would be painless as well as it is all containerized.