Customizing Mail in a Box

Posted on 06 Jan 2021 by Steve Markham

The real reason for this blog is to experiment whether I could easily self-host Janet’s blog (blogs, actually, since she has one for her photography business, too) on the same box that’s running the mail server. In the Mail-in-a-Box forum posts, there are several people asking similar questions, and they are all told not to muck with the nginx configuration at all.

Well, I’ve been mucking around, and figured I should document what I did, so I can undo it all when things start breaking.

Clean URLs for Static Files

Self-hosting my email is nice, but GMail is pretty good so the self-hosted email isn’t really what I want. I really just want a place on the Internet that is mine. My Christmas letter is a perfect example. Before I got MiaB set up, I put our Christmas letter jpg file on the VPS I use for my consulting work, bought the domain, pointed the domain at that VPS, and then emailed out everyone a link: This was all pretty easy to set up in apache, and I didn’t expect it to be too difficult to replicate on the MiaB server. I was mistaken.

For starters, MiaB uses nginx instead of apache2. So I needed a different set of directives in the config file to handle the clean-looking URL instead of redirecting to the blog or nextcloud or anything. As it turns, I didn’t need to redirect anything, since MiaB serves the static site as is without redirecting to nextcloud unless I go to BUT, it served up that .jpg as a binary download, since the file had no extension, so I did still need nginx directives to set the default type one way or another.

At first I tried try_files $uri $uri.jpg;, and then giving the Christmas letter an extension. That worked, for a day, and then just as the forum posts indicated, the configuration reset and it was back to a binary download.

Digging into How MiaB Resets the Config Every Day

There is a cron task that runs daily. As far as I can tell it checks the SSL certs, updates them if necessary, and then re-generates the nginx config files. The cron task runs a shell script, and the shell script runs a python script, which uses a set of three templates config files. At first I edited the config files, and that mostly worked. There was unnecessary configuration for all the other domains (,, etc), but I could live with that.

Basic Authentication for the Blog

The next thing I wanted was to potentially host Janet’s blog. Janet doesn’t put her personal blog out on the web for everyone to see. Instead, you have to request access, and then she adds your email address to a list of approved blog viewers. It seems like that’s not possible with a purely static website, but I can use basic HTTP authentication to at least require people to sign in to the site. After installing apache2utils (which MiaB had removed) and creating the .htpasswd file, the nginx directive is easy:

auth_basic "Blog";
auth_basic_user_file /home/user-data/www/;

The trick is then to not require authentication for the Christmas letter. After a little fiddling around, I figured it out. First, instead of using try_files to redirect to the .jpg version of the letter, I created a location for the clean URL and set the default mime type for that location to jpeg. Then I turn off authentication for that location as well, and things behave just right.

location = /Christmas2020 {
        auth_basic off;
        default_type image/jpeg;

Updating the MiaB Template Code

It seems like putting authentication on the domain might lead to trouble, so I now needed to be more clever in how I put that custom code into the configuration in nginx. I started by modifying the python script that does the update, I copied the existing “alldomains” template to a “baredomain” template, and then used that one for only. The relevant section of python code now looks like this:

# Load the templates.
template0 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx.conf")).read()
template1 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-alldomains.conf")).read()
template2 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-primaryonly.conf")).read()
template3 = "\trewrite ^(.*) https://$REDIRECT_DOMAIN$1 permanent;\n"
template4 = open(os.path.join(os.path.dirname(__file__), "../conf/nginx-baredomain.conf")).read()

# Add the PRIMARY_HOST configuration first so it becomes nginx's default server.
nginx_conf += make_domain_config(env['PRIMARY_HOSTNAME'], [template0, template1, template2], ssl_certificates, env)

# Add configuration all other web domains.
has_root_proxy_or_redirect = get_web_domains_with_root_overrides(env)
web_domains_not_redirect = get_web_domains(env, include_www_redirects=False)
for domain in get_web_domains(env):
        if domain.count('.')==1 and domain not in has_root_proxy_or_redirect and domain in web_domains_not_redirect:
                nginx_conf += make_domain_config(domain, [template0, template4], ssl_certificates, env)
        if domain == env['PRIMARY_HOSTNAME']:
                # PRIMARY_HOSTNAME is handled above.

So now the nginx configuration for all the other domains is exactly the way MiaB wants it, but I can do whatever I want to the baredomain template, and that will show up only for For future reference, the best process for trying things out is to edit the section of the final nginx config file, then once something work, edit the baredomain template, and then rerun the daily tasks script.

vi /etc/nginx/conf.d/local.conf
# Edit the section, then :wq
nginx -s reload
# If things don't look right, repeat until they do.
vi /root/mailinabox/conf/nginx-baredomain.conf
cd /root/mailinabox
# You'll get an email that ends with "web update" if things went smoothly

It’s also worth noting that nginx error logs are in /var/log/nginx/error.log, and can help when there are problems.

Comments are closed for this post.