The $0 Custom Domain Email Setup I Wish I Knew Earlier
Receive mail in Gmail, send from your domain, and skip the extra mailbox subscription.
You can also read this on Medium.
A polished product can still feel unfinished when the first email your user receives comes from a Gmail address.
You've spent weeks polishing the product. The landing page is tight. The onboarding flow doesn't have a single rough edge. And then someone signs up, and the welcome email lands in their inbox from:
yourproduct@gmail.com
It works. Nobody's going to churn over it. But it was one of those small details that didn't match the rest of the experience.
I wanted contact@mydomain.com. My assumption was that this meant Google Workspace or another paid, per-user mailbox subscription for an inbox I'd barely use.
Turns out that's not true, and the reason it's not true is worth understanding, because it'll save you from overpaying for the wrong problem.
The one idea that makes all of this click
An email address isn't one thing. It's three separate jobs wearing the same trench coat:
- Receiving mail: someone emails
contact@mydomain.com, and you need to actually see it. - Sending application mail: your app fires off
contact@mydomain.comfor signups, invoices, password resets. - Hosting a mailbox: a place where you log in as
contact@mydomain.comto compose and reply.
Every "just pay for Google Workspace" recommendation bundles all three into one purchase. But if most of your outgoing mail is transactional and you already have an inbox you're happy with, you may only need #1 and #2. Once I stopped treating this as "buy an email service" and started treating it as three separate problems, the free path became obvious.
Here's the setup I ended up with:
- Incoming:
contact@mydomain.com→ Cloudflare Email Routing → existing Gmail inbox - Outgoing: my app → Brevo →
contact@mydomain.com→ users - Extra cost: $0/month
One thing to flag before we start: this setup covers receiving mail and sending application mail. It doesn't turn Gmail into a permanent mailbox for manually replying as your custom domain long-term. There's a real limitation there, and I'll walk through exactly where it bites later on.
Let's build it.
Part 1: Receiving mail for free
Cloudflare has a feature called Email Routing that forwards anything sent to contact@mydomain.com straight into an inbox you already own, like your existing Gmail.
someone emails → `contact@mydomain.com` → Cloudflare → existing Gmail inbox
You keep checking Gmail exactly like you do today. Your custom address just becomes the public-facing front door to an inbox nobody else needs to know about.
To set it up:
- Your domain needs to be on Cloudflare's DNS.
- Go to Email → Email Routing in your Cloudflare dashboard and enable it.
- Cloudflare will configure the DNS records Email Routing needs. That includes its MX records for receiving mail, and it typically also adds an SPF record and a DKIM record used by the routing service itself. In my setup, that looked roughly like this:
MX route1.mx.cloudflare.net
MX route2.mx.cloudflare.net
MX route3.mx.cloudflare.net
TXT v=spf1 include:_spf.mx.cloudflare.net ~all
TXT cf2024-1._domainkey (Cloudflare's DKIM record)
Stop here if you already have MX records. MX records are what tell the internet "if someone emails this domain, send it here." If you're already using Google Workspace, Zoho, Fastmail, Proton, or anything else for email, don't remove those records without checking what they belong to first; you'll break an inbox that's currently working. In my case the old MX records belonged to a forwarding service I'd abandoned, so replacing them was safe. Check yours before you touch anything.
Also check your root TXT records. In my case, the previous forwarding provider had left behind its own SPF record, something like v=spf1 include:spf.efwd.registrar-servers.com ~all, which conflicted with the SPF record Cloudflare Email Routing needed. Since I was intentionally replacing that forwarding service, I removed the old SPF and let Cloudflare's routing SPF take over.
Don't copy that decision blindly. If your existing SPF record authorizes something you still use, like a different sending service, it may need to be merged into one record rather than deleted outright.
Once Email Routing is live, create a forwarding rule:
contact@mydomain.com → existing Gmail inbox
Cloudflare will verify the destination inbox. After that, send yourself a test email from another account. If it lands in Gmail, receiving is done.
What this does not give you: a real mailbox you can log into as contact@mydomain.com and send from directly. It's forwarding, not hosting. That limitation is exactly why Part 2 exists.
Part 2: Sending app emails from your domain
If you're running a SaaS, you're probably already using a transactional email provider: Brevo, Resend, Postmark, SES, Mailgun. I was already on Brevo, so I kept it. The architecture is the same with Resend, but the exact DNS records differ by provider.
Old flow:
app → Brevo → yourproduct@gmail.com → user
What I wanted:
app → Brevo → contact@mydomain.com → user
To get there, you authenticate your domain with your provider.
Step 1: Add and verify your domain
In Brevo, I added my domain and it gave me a domain-verification TXT record, two DKIM CNAME records (Brevo uses two selectors), and a DMARC TXT record. Other transactional providers use the same general authentication concepts, domain verification, DKIM, DMARC, but the exact record types and values can differ, so use whatever your provider's dashboard gives you rather than assuming it'll match mine.
SPF, DKIM, DMARC, without the jargon
You don't need to become an email security engineer, but you should know roughly what these do before you start pasting records into DNS:
- SPF: a list of servers allowed to send mail on your domain's behalf.
- DKIM: a cryptographic signature proving a message actually came from your domain and wasn't tampered with.
- DMARC: checks whether authenticated mail aligns with the domain in the From address, and tells receiving servers what to do when that authentication fails. It can also send you reports about who's sending mail as your domain.
The one DNS mistake that'll bite you
If another provider gives you an SPF record and one already exists, don't create a second SPF TXT record at the same hostname. In my setup, Cloudflare Email Routing required the root SPF record, and Brevo did not require another root SPF; it only needed domain verification, DKIM, and DMARC. Publishing multiple SPF records at the same hostname causes SPF evaluation errors and can break SPF-based authentication, so check what's already there before adding anything new.
DMARC works the same way: one policy at _dmarc.mydomain.com, not several. DKIM is the exception. Multiple DKIM selectors are fine and expected. My setup has Cloudflare's Email Routing DKIM selector and Brevo's two sending DKIM selectors coexisting without conflict. They serve different parts of the email flow and each uses its own selector (provider1._domainkey, provider2._domainkey, and so on), not one shared key signing everything.
The rule that kept me out of trouble: don't touch an email DNS record until you understand why the service is asking for it.
Step 2: Create the sender and test before you flip the switch
Set up your new sender, My Product <contact@mydomain.com>, and send yourself a test email before touching production. Open it in Gmail, click ⋮ → Show original, and check:
DKIM: PASS
DMARC: PASS
Don't just check for a pass. Confirm the message is actually signed by your own domain and not by a default or shared domain your provider falls back to. Don't panic if Gmail's "mailed-by" field still shows your transactional provider, something like mailed-by: sender-sib.com, while "signed-by" shows mydomain.com. That's normal: the mailed-by field just reflects the sending infrastructure. What you want to see is your own domain being used for the DKIM signature, with the overall authentication checks passing.
Only once that's confirmed do you flip the sender in your app:
// before
sender: { name: "My Product", email: "yourproduct@gmail.com" }
// after
sender: { name: "My Product", email: "contact@mydomain.com" }
Your API keys and SMTP credentials usually don't change; you're just swapping the sender identity on an already-authenticated domain.
The full picture
mydomain.com
│
contact@mydomain.com
│
┌───────┴───────┐
│ │
INCOMING APP EMAIL
│ │
Cloudflare Brevo / Resend
│ │
▼ ▼
Existing Gmail Users
Cloudflare handles inbound. Your transactional provider handles outbound app mail. This lets the existing inbox keep doing its job without requiring a separate hosted mailbox just for the custom address.
Bonus: you can stack addresses for free
contact@, support@, hello@, me@: these are just addresses on the same domain, and Cloudflare will happily forward all of them into one Gmail inbox:
contact@mydomain.com ──┐
support@mydomain.com ──┼──→ existing Gmail inbox
me@mydomain.com ───────┘
You don't need a separate paid seat per address if all they're doing is forwarding. For a solo setup, I'd probably keep it to two: one general address and, if useful, one more personal address.
Where this setup actually has a limit
Be honest with yourself about this part, because it's the one people skip and then get surprised by later.
Cloudflare gets you receiving. Brevo/Resend gets you app sending. But Cloudflare is only forwarding incoming mail into Gmail; it isn't turning Gmail into a hosted mailbox for your custom domain. Historically you could use Gmail's "Send mail as" with an external SMTP server to reply as contact@mydomain.com right from Gmail's compose window.
One important 2026 caveat: Google says third-party "Send as" support in Gmail will be fully removed in January 2027, and new configurations may already be restricted during the transition period in late 2026. So I wouldn't build a setup that depends on manually sending as the custom address from Gmail's compose window long-term. Cloudflare forwarding and application sending through Brevo or Resend are separate from that change and continue to work regardless.
That doesn't break anything described above. Receiving and app-sending both still work fine. It just means: if you're personally replying to customers a lot as contact@mydomain.com, this free setup starts becoming less convenient than a hosted mailbox, and that's your actual signal to upgrade, not the Gmail avatar issue below, and not vanity.
The cosmetic thing nobody warns you about
After switching senders, Gmail stopped pulling the old profile photo and started showing a plain fallback letter next to contact@mydomain.com. It's not a delivery problem. The custom-domain address is a different sender identity, so Gmail isn't automatically using the profile image from the underlying Gmail account.
The standards-based route for a branded sender logo is BIMI, which lets supported inboxes show your logo, but BIMI requires a stricter DMARC policy (p=quarantine or p=reject instead of p=none), and even a correctly configured BIMI record doesn't guarantee every provider will actually display the logo.
That's the approach I took: I left DMARC at p=none and turned on DMARC reporting first, with reporting addresses for both Cloudflare and Brevo. That gives me visibility into who's actually sending mail as the domain before I consider moving to a stricter enforcement policy. I wouldn't tighten DMARC just to fix an avatar. Get authentication solid and monitored first, and treat BIMI as a later optimization, not a launch-day fix for a cosmetic complaint.
Is it actually free?
For the part that matters, not paying for an extra mailbox, yes.
- Cloudflare Email Routing — receive custom-domain mail. Extra cost: $0
- Existing Gmail — inbox. Extra cost: $0
- Brevo / Resend — application sending. Extra cost: depends on provider and sending volume
- Hosted mailbox — not used in this setup. Extra cost: $0
In my case, this added $0/month because I was already using Gmail and Brevo. Your transactional provider may have its own cost depending on your sending volume, and you're still paying for the domain itself, but a full business mailbox was never actually a requirement for having an authenticated custom-domain sender.
When a hosted mailbox makes more sense
A hosted mailbox becomes the better option once manual email is a significant part of your workflow. Move to Google Workspace, Zoho, or Fastmail once:
- multiple teammates need their own individual inboxes
- support volume gets heavy enough that forwarding into one inbox is chaos
- you're sending a lot of manual, one-off email as the business
- you actually want Calendar/Drive tied to the business identity
None of that makes this architecture wrong. It just means manual email has become important enough that a hosted mailbox is now the simpler tool for that part of the workflow.
The mental model to keep
Receiving mail, sending app mail, and hosting a mailbox are three different problems that got sold to you as one bundled purchase. Once you split them apart, you can solve each one with the cheapest tool that actually fits, and upgrade the one piece that needs it later, without rebuilding anything else.
- Sender:
contact@mydomain.com - Incoming: Cloudflare → existing Gmail
- Outgoing: Brevo / Resend → users
- Extra cost: $0/month
No additional mailbox subscription, no change to the inbox I already used, and no need to rebuild the application email setup if the mailbox layer changes later.
