# How I run this site without a CMS database or mail server

Ben Morin, 2026-08-10. https://bemorin.com/writing/how-i-run-this-site-without-a-cms-database-or-mail-server

> Git is the content database, Cloudflare serves the site, Pages CMS edits the repository, and email is split into routing and sending instead of paying for a traditional mailbox stack.

The goal of this site is not to have an interesting infrastructure. The goal is to make publishing, changing a page, and receiving an email boring enough that I do not have to think about them.

The current setup is deliberately small:

- **Astro** renders the site.
- **GitHub** stores the code and content and keeps the history.
- **Cloudflare Pages** builds and serves it.
- **Pages CMS** gives me an editor for the content stored in GitHub.
- **Cloudflare Email Routing** forwards mail sent to my domain into the inbox I already use.
- **Gmail** is the inbox I actually read.
- **Resend** handles outbound mail when the site, an automation, or Gmail needs an SMTP sender for my domain.

There is no separate content database I have to keep in sync and no mail server I have to administer.

## The publishing path

The source of truth is the GitHub repository.

```text
Pages CMS
    ↓ commit
GitHub
    ↓ deploy
Cloudflare Pages
    ↓
bemorin.com
```

Cloudflare Pages can connect directly to a Git repository and rebuild after a push. For a mostly static Astro site, requests for static assets are free and unlimited on both the Free and Paid plans; the Free plan currently allows up to 500 Pages builds per month. The domain itself is still a separate cost.

That gives me a useful separation: **Git decides what exists; Astro decides how it is rendered; Cloudflare decides how it is delivered.**

## Pages CMS is an editor, not a second source of truth

[Pages CMS](https://pagescms.org/docs/) is an open-source CMS for static sites stored in GitHub. It edits files in the repository directly rather than making the site fetch content from a separate CMS database at runtime.

The important part is the `.pages.yml` file. It defines which files and collections the editor is allowed to touch and which fields it should show.

On this site I expose:

- **Core pages** — selected editorial pages such as the home page and Challenges.
- **Writing** — articles in `src/content/writing/`.
- **Notes** — short entries in `src/content/notes/`.
- **Books** — reading notes in `src/content/books/`.
- **Projects** — project metadata and project-page content in `src/content/projects/`.

The templates, layouts, interactions, and design system remain code. Pages CMS should let me change the *content* without making it easy to accidentally redesign or break the application.

That means I can create, edit, rename, or delete entries in the collections that allow those operations. A book can be added without creating a new Astro page. A project can be updated without editing a component. A note can be published from a browser. The next Cloudflare build turns the resulting Git commit into the public site.

## The order I use when setting it up

I would not start with the CMS.

1. Put the project in **GitHub**.
2. Connect the repository to **Cloudflare Pages** and get one clean production deploy.
3. Add the `.pages.yml` configuration to the repository.
4. Connect **Pages CMS** to GitHub and authorize that repository.
5. Make one harmless content edit in Pages CMS.
6. Confirm the commit appears in GitHub and triggers a successful Cloudflare build.

That sequence isolates problems. If Cloudflare cannot build the repository, adding a CMS only gives you a second place to debug.

## Receiving email without hosting a mailbox

Cloudflare Email Routing solves a different problem.

I can create an address such as:

```text
hello@bemorin.com
```

and route incoming messages to an existing Gmail inbox.

```text
hello@bemorin.com
      ↓
Cloudflare Email Routing
      ↓
my existing Gmail inbox
```

Cloudflare describes Email Routing as a free service for creating addresses on a domain and forwarding them to an existing inbox. It requires the domain to use Cloudflare DNS.

The useful distinction is that **routing is not a mailbox**. Cloudflare receives the message at the domain edge and forwards it to the destination I configured. I do not need another inbox to check.

## Sending is a separate concern

Incoming routing does not automatically give the website an outbound mail system.

For outbound mail I use **Resend**. After verifying a domain, Resend provides the SPF and DKIM records that authorize it to send on the domain's behalf. It can be used through its API or as a normal SMTP server.

That produces two outbound paths:

```text
website / n8n / automation
        ↓
      Resend
        ↓
     recipient
```

and, for human replies:

```text
Gmail
  ↓  Send mail as + external SMTP
Resend SMTP
  ↓
recipient
```

Gmail supports sending from another address you own through **Send mail as**. Resend exposes standard SMTP credentials (`smtp.resend.com`), so the two can be connected without running an SMTP server yourself.

I still treat authentication as infrastructure, not a checkbox. SPF, DKIM, and DMARC should be checked after the DNS changes rather than assumed to be correct because a dashboard turned green.

## What is actually free?

"Free website" can be misleading because there is almost always at least a domain involved.

For this architecture, the recurring infrastructure can stay very close to zero while the site is small:

- Cloudflare Pages has a usable Free plan for static sites.
- Cloudflare Email Routing is free for forwarding incoming mail.
- GitHub can hold the public or private repository depending on the account and plan.
- Pages CMS edits the GitHub-backed content without requiring me to run a CMS backend.
- Resend has its own quotas and pricing for outbound mail, so I treat it as a separate sending service rather than pretending outbound email is part of the hosting.

The important saving is not the last few euros. It is the number of systems I am responsible for operating.

## The trade-off

This setup is excellent when the site is primarily content and the repository can remain the source of truth.

It is a worse fit if dozens of non-technical editors need complicated permissions, real-time collaborative editing, or content that must change without a deployment. At that point, a conventional database-backed CMS may be the simpler system.

For a personal Almanack, I prefer the opposite trade-off: **content stays portable, every change is versioned, the public site is mostly static, and the editing interface is replaceable.**

## Official references

- [Pages CMS: introduction](https://pagescms.org/docs/)
- [Pages CMS: collections and files](https://pagescms.org/docs/configuration/content/)
- [Cloudflare Pages limits](https://developers.cloudflare.com/pages/platform/limits/)
- [Cloudflare Pages pricing for static requests](https://developers.cloudflare.com/pages/functions/pricing/)
- [Cloudflare Email Routing](https://developers.cloudflare.com/email-service/get-started/route-emails/)
- [Gmail: send from a different address](https://support.google.com/mail/answer/22370)
- [Resend: domain verification](https://resend.com/docs/dashboard/domains/introduction)
- [Resend: SMTP](https://resend.com/docs/send-with-smtp)
