# Project No. 1 | Building my own Internal Virtualized Email Infrastructure

# Introduction

In [my previous post](https://nekwokdoodle.com/how-email-works-nekwok-doodle-explained), we tackled the process of how email is forwarded and delivered from one user mailbox to another, the components needed, and the different protocols involved. We wrapped up our discussion by explaining the email flow in a relatable manner—that is, like how *“Good Ol’ Days”* mail is transported from putting it in the **postbox/pillar box** to the **mailbox** near the receiver’s house.

Learning how email works can be somewhat challenging. One time I had a discussion with a co-IT professional telling me how inconvenient it is when email is not implemented right. On this post, I’m going to share my journey on how I built an internal email environment.

# Why am i doing this?

But why internal? Isn’t email meant to be available on public network? Yes, and you’re right! But the time has come… theoretical lessons are not enough. Now, my purpose is to study by *Building*, *Breaking*, and *Fixing*.

The real story is I was bugged on how the email infrastructure works, and yes, I ended up making a [content](https://nekwokdoodle.com/how-email-works-nekwok-doodle-explained) about it. In response to that, I thought it would be better if “*I make it from scratch.*”

*First attempt*, subscribing to Microsoft 365 or Google Workspace. Yeah, I did not continue as I felt there could be something to miss. Those products are cloud-based… **I wanted full control**. Besides, I already implemented a Cloud Email solution with *Zoho*. I wanted to remove the easiness of the cloud by setting up an email server from scratch, internally, just like how it is before. So, I searched for an open-source email server to tweak with and I stumbled upon a gem, **Mailcow**. So, on my *second attempt*, I am going to use an open-source email server to do self-host.

*Mailcow* is an open-source email server that uses docker containers to work. I quickly looked for internal implementations of it, but I could not find contents online whether it is possible to set it up privately or locally. I realized the challenge because as far as I searched in Google, a *domain name* and a *public IP* is needed. And even though I have a static IP, I still need to coordinate with my Internet Service Provider (ISP) whether they are blocking the SMTP port (and allow it if they do). Even Google AI search told me that it is not possible (*what a discouragement, but I understand*). Maybe there is no published content about it currently.

Now, I am thrilled doing this *simple project* because, to be honest, I was actually tired looking for resources whether *Mailcow* can be configured *locally without having a public IP address and buying a domain name*. I’ve had enough, let’s start.

# Okay, Let’s Begin

## Network Overview

We have *3 actors*: 1.) **Client PC**, 2.) **Mail Server**, 3.) **DNS Server**. The \[1\]*Fully Qualified Domain Name* (FQDN) of the two (2) mail servers are **SMTP.OUKLOOK.COM** and **SMTP.GEEMAIL.COM**. These will run the *Mailcow* email service.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758037552751/4328bd97-f633-43d1-a46e-948aa03f5bbb.png align="center")

Our two (2) *made-up* internal domains, **DOODLER.COM** and **PAINTER.COM** can be called as **Tenant Domains**, because they’ll be the domains that will use the email service running on our two (2) mail servers. Each domain has one (1) mailbox: **BIRD@DOODLER.COM** and **CAT@PAINTER.COM**

While our two (2) mail servers will run **Mailcow**, for the DNS server, it will run **Webmin** with **BIND9** which are another open-source applications. Both the mail and DNS service are running on top of **Linux**. So, there are three (**3) *Linux* machines in total**.

## Experimenting

Most of the tutorials you’d see on the internet use a Public IP and a newly purchased—if not existing—domain name. Email service utilizes the DNS records to be able to forward email messages. *This is where I experiment*… that is, **by having full control of the DNS**. To do that, a DNS service must be installed and configured.

### DNS Setup

As mentioned earlier, I used **Webmin** to manage **BIND9** DNS service—to make things interactive. First thing we need to do here is to setup zones for the MX domains (**OUKLOOK.COM** and **GEEMAIL.COM**). Once done, *FQDN* hosts must be added to resolve MX IP addresses. We can notice below resolved IP addresses of the MX servers running the *Mailcow* mail service by directly querying our DNS server, *172.16.1.13*.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758285337287/6c698bdd-d2f5-4ec6-bcd5-979ba3a31866.png align="center")

Continuing the DNS configuration, lookup zones for **DOODLER.COM** and **PAINTER.COM** are also created. This time, instead of an *A-record*, to resolve the host machine that should process emails for such domain, we’ll add **MX record** on each of the **Tenant Domains**. Below is the resolved Mail server for **DOODLER.COM** (**SMTP.OUKLOOK.COM**), and for **PAINTER.COM** (**SMTP.GEEMAIL.COM**).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758296710521/9d6f9ba4-51ee-4861-9dca-3664737063f8.png align="center")

### Mail server setup

Going to *Mailcow*, the domains **DOODLER.COM** and **PAINTER.COM** are added. It is noticeable that DKIM is included upon the configuration. Take note that **DOODLER.COM** is added in **SMTP.OUKLOOK.COM**, while **PAINTER.COM** in **SMTP.GEEMAIL.COM**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758287895202/9c2f89cc-ab62-404f-89d2-c999bafcf1e0.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758287911880/b1210495-49ca-407d-94ff-d13765f8591f.png align="center")

Going back to the DNS—since DKIM is now created—we’ll add \[2\]*SPF* and \[3\]*DKIM* records.

Since I was having trouble adding those in **Webmin**, I manually added **SPF** and **DKIM** for both **Tenant Domains**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758289459269/bb0fefde-dfa1-4bbb-97da-be52e0f7566f.png align="center")

It took few minutes to propagate, and we can see below the *SPF* and *DKIM* of our **Tenant Domains**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758295019221/bc46c3bd-e2f3-407e-a96c-1726ff4be067.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758295025122/695e55a5-86e1-4bd2-8e86-68dbfa9bb8b6.png align="center")

I added mailboxes **ALICIA@DOODLER.COM** and **BOBBY@PAINTER.COM**. We’re now gonna send an email.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758299156057/1a74c682-0345-4b23-8b1c-627f19db6eb5.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758299301931/d21915cf-897b-4583-88a1-64c88b13b02e.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1758299333152/4a380891-a75e-4009-b7f7-6d28970033e4.png align="center")

# Conclusion

After working on this simple project, I can say that setting up an email server is challenging. The part I spent most time with is validating if the DNS records are correct and configuring *Mailcow*—since it is new to me. During the process, I observed that a simple misconfiguration in the DNS can cause inconvenience in the email flow. *Mailcow* UI is pretty straightforward and I enjoyed tweaking it. Maybe I need some more familiarization with the product because I can see a lot of options to check—in which I am excited about.

Overall, the journey was great. There are difficulties in beginning, but with the right understanding of the concept, it is much easier and ***satisfying to troubleshoot***. I am pumped checking other features such as *Encryption*, *Authentication*, and *RSpamd*.

# References

\[1\] [https://en.wikipedia.org/wiki/Fully\_qualified\_domain\_name](https://en.wikipedia.org/wiki/Fully_qualified_domain_name)

\[2\] [https://en.wikipedia.org/wiki/Sender\_Policy\_Framework](https://en.wikipedia.org/wiki/Sender_Policy_Framework)

\[3\] [https://en.wikipedia.org/wiki/DomainKeys\_Identified\_Mail](https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail)
