Email filters often pile up quietly in Gmail, making it tricky to recall why each one exists or how they interact. I use Terraform to bring order by putting Gmail labels and filters in version control, letting you tweak your inbox setup with confidence. The yamamoto-febc/gmailfilter provider works with both labels and filters, so you can handle everything in one place.
Get the Code
I open-sourced MailPilot on GitHub: 👉 https://github.com/eddyvlad/mailpilot
Clone or fork the repository to start with a working skeleton that already includes common modules. From there you can:
- Remove modules you don’t need
- Add more criterias to each module’s filters
- Add or remove labels and filters in
filters.tf
orlabels.md
This way you can get going quickly without writing everything from scratch.
I called this project MailPilot.
Why Terraform for Gmail?
Terraform is usually for cloud infrastructure, yet it shines whenever you want reproducible, declarative configuration. Treating Gmail settings as code gives you:
- Clarity: quickly find (using 'find in files') and edit instead of hunting through settings pages
- Version history: review past changes and roll back when needed
- Automation: reuse modules to apply the same rules across categories or accounts

Step-by-Step Setup
1. Install the Tools
- Terraform CLI
- or
brew tap hashicorp/tap
(Hashicorp's official repository) andbrew install hashicorp/tap/terraform
on macOS - Google Cloud SDK (gcloud)
- or
brew install gcloud-cli
on macOS - (Optional) jq for checking JSON responses
- or
brew install jq
on macOS
2. Enable the Gmail API and Create OAuth Credentials
- Visit the Google Cloud Console.
- Create or pick a project.
- Enable Gmail API.
Official documentation: Enabling the Gmail API.

- Under APIs & Services → Credentials, create an OAuth client of type Desktop app.
- Download the
client_secret_*.json
file, store it at~/.config/mailpilot/client_secret_mailpilot.json
, and runchmod 600
to restrict access.

3. Authenticate with Application Default Credentials
gcloud auth application-default login \
--client-id-file ~/.config/mailpilot/client_secret_mailpilot.json \
--scopes https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/gmail.labels,https://www.googleapis.com/auth/gmail.settings.basic

This opens a browser for consent and saves your tokens locally. ⚠️ Tokens usually expire after seven days. Re-run this command whenever you want to apply new changes after that.
Quick sanity check:
TOKEN="$(gcloud auth application-default print-access-token)"
curl -H "Authorization: Bearer $TOKEN" \
https://gmail.googleapis.com/gmail/v1/users/me/labels
You should see JSON listing existing labels.
4. Scaffold the Terraform Project
mkdir mailpilot
cd mailpilot
nano main.tf # or use your preferred editor
Paste in a minimal configuration:
# terraform.tf
terraform {
required_providers {
gmailfilter = {
source = "yamamoto-febc/gmailfilter"
version = "1.1.0"
}
}
}
provider "gmailfilter" {}
# main.tf
resource "gmailfilter_label" "newsletters" {
name = "Newsletters"
}
resource "gmailfilter_filter" "nl_archive" {
criteria {
from = "news@newsletter.example"
exclude_chats = false
has_attachment = false
}
action {
add_label_ids = [gmailfilter_label.newsletters.id]
remove_label_ids = ["INBOX", "SPAM"]
}
}
This file declares the Gmail provider, a label named Newsletters, and a filter that tags and archives emails from news@newsletter.example
.
Terraform stores state locally in terraform.tfstate
.

5. Initialize and Apply
terraform init
terraform plan
terraform apply
terraform init
downloads the providerterraform plan
previews changesterraform apply
enforces the configuration after you confirm withyes
Check Gmail afterward to see the new label and filter in action.


Going Further with Modules and Loops
Once you are comfortable with the basics, you can modularize recurring rules. For example, MailPilot’s receipts module uses a for_each
loop over a map of senders:
# modules/receipts/filters.tf
locals {
criterias = {
shopee = { query = "from:shopee subject:\"Your payment has been confirmed\"" }
github = { query = "from:github subject:\"Payment Receipt\"" }
# more entries...
}
}
resource "gmailfilter_filter" "receipts" {
for_each = local.criterias
action {
add_label_ids = [var.label_id]
remove_label_ids = ["INBOX", "SPAM"]
}
criteria {
exclude_chats = "false"
has_attachment = "false"
query = lookup(each.value, "query", null)
}
}
# modules/receipts/variables.tf
variable "label_id" {
type = string
description = "ID of the Receipts label"
}
# modules/receipts/versions.tf
terraform {
required_providers {
gmailfilter = {
source = "yamamoto-febc/gmailfilter"
version = "1.1.0"
}
}
}
# main.tf
module "receipts_filter" {
source = "./modules/receipts-filter"
label_id = gmailfilter_label.receipts.id
}
Adding a new sender is as simple as appending another entry to criterias
. Terraform generates one filter per entry, keeping the configuration concise and consistent.
The same provider also manages labels, so you can create nested structures like Receipts/Transport
without leaving your editor.

Final Thoughts
MailPilot shows that even a personal inbox can benefit from infrastructure as code. With a bit of Terraform and the Gmail provider, you can:
- Track Gmail labels and filters in version control
- Reapply your setup on a new account or after major changes
- Scale gracefully using modules and loops for patterns like receipts or bills
If your Gmail filters feel out of control, try codifying them. Start with a single label and filter; once you see how approachable it is, you may find yourself managing email as if it were any other part of your infrastructure. With MailPilot, your Gmail filters become readable, shareable, and maintainable.