Customize campaigns-template for sunfloweracressoaps.com
Sync campaigns / sync (push) Successful in 16s

Restyle the email template to Sunflower Acres' forest green/cream/sun
yellow brand, rename the default listmonk template, replace the README
with client-specific instructions, and add a real welcome campaign.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 07:33:59 -04:00
parent 88f62c7e09
commit 678080f7c4
4 changed files with 71 additions and 67 deletions
+1 -3
View File
@@ -15,10 +15,8 @@ jobs:
- name: Install eec-campaigns
run: go install gitea.reground.org/will/eec-campaigns@v0.1.4
# TODO: rename "TEMPLATE default" below to "<this-repo-name> default"
# (or any name you like) as part of customizing this template.
- name: Sync template to listmonk
run: eec-campaigns template "TEMPLATE default" email-templates/campaign.html
run: eec-campaigns template "sunfloweracres default" email-templates/campaign.html
env:
LISTMONK_BASE_URL: ${{ secrets.CAMPAIGNS_LISTMONK_BASE_URL }}
LISTMONK_API_USER: ${{ secrets.CAMPAIGNS_LISTMONK_API_USER }}
+21 -24
View File
@@ -1,30 +1,27 @@
# campaigns-template
# sunfloweracres-campaigns
Gitea template repo for standing up a new domain/client's campaign content repo. Generate a new repo from this one (Gitea → this repo → "Generate Repo," or `POST /api/v1/repos/generate/will/campaigns-template`) rather than cloning it — generating starts the new repo with a clean history of its own.
sunfloweracressoaps.com's campaign content, synced to listmonk by [`eec-campaigns`](https://gitea.reground.org/will/eec-campaigns). This repo holds only campaign content and this domain's listmonk secrets the tool itself is installed at a pinned version in CI.
Full reference for everything below lives in the [`eec-campaigns` README](https://gitea.reground.org/will/eec-campaigns).
## To send a campaign
## After generating a new repo from this template
1. Write `campaigns/<slug>/campaign.md` (frontmatter + Markdown body — see the [eec-campaigns README](https://gitea.reground.org/will/eec-campaigns#campaignmd) for the full field reference). Set `from_email: "Sunflower Acres <hello@sunfloweracressoaps.com>"` explicitly — there's no default, and sync rejects a campaign with no `from_email` at all. Mailcow's Sender ACL must allow that exact address for the send to go through rather than bounce as spoofing (see Sending as hello@sunfloweracressoaps.com below).
2. `git push` to `master` — CI syncs it into listmonk as a **draft** and emails a preview to `CAMPAIGNS_PREVIEW_EMAIL` (or the campaign's own `preview_emails`).
3. Check the preview inbox. Keep pushing edits until it looks right — each push re-syncs and re-previews.
4. When ready to actually send: `git tag send/<slug> && git push origin send/<slug>`. This is the one action that transitions the campaign from draft to running — a plain `git push` to master can never do it.
1. **Rename the template.** In `.gitea/workflows/sync-template.yml`, replace `TEMPLATE default` with a name specific to this domain (e.g. `<client> default`).
2. **Restyle `email-templates/campaign.html`** if this client wants different branding than the stock listmonk template it starts as.
3. **Add repo secrets** (Gitea Actions → Secrets): `CAMPAIGNS_LISTMONK_BASE_URL`, `CAMPAIGNS_LISTMONK_API_USER`, `CAMPAIGNS_LISTMONK_API_TOKEN`, and optionally `CAMPAIGNS_PREVIEW_EMAIL` — scoped to this domain's own listmonk instance and its own dedicated API user (see `reground-infrastructure`'s `campaigns` Ansible role for provisioning that user).
4. **Check the tool version pin** in all four `.gitea/workflows/*.yml` files (currently `v0.1.0`) — bump it only deliberately, after confirming a newer `eec-campaigns` release works.
5. Run the `Sync default campaign template` workflow once.
6. Replace this README with a short one for this domain (see `reground-campaigns`'s README for the pattern: a numbered "to send a campaign" list for whoever operates this repo day-to-day).
7. Give the client push/PR access to *this repo only* — that's the entire isolation boundary. They never need access to `eec-campaigns`, `campaigns-template`, or any other domain's repo.
Alternatively, run the `Send campaign` workflow manually from Gitea Actions with the slug as input — same effect as the tag push.
## Layout
## Other workflows
```
campaigns/
<slug>/
campaign.md
email-templates/
campaign.html
.gitea/workflows/
sync.yml # push to master -> draft + preview
send.yml # tag push send/<slug>, or manual dispatch -> real send
test.yml # manual dispatch -> re-preview without touching status
sync-template.yml # manual dispatch -> push email template as listmonk default
```
- **`Test-send campaign`** (manual) — re-preview a campaign's current content to any address, without touching its status.
- **`Sync default campaign template`** (manual) — pushes `email-templates/campaign.html` into listmonk as the default campaign template. Run once after setup, and again whenever that file changes.
## Setup
Repo secrets (Gitea Actions → Secrets) are already set: `CAMPAIGNS_LISTMONK_BASE_URL`, `CAMPAIGNS_LISTMONK_API_USER`, `CAMPAIGNS_LISTMONK_API_TOKEN`, `CAMPAIGNS_PREVIEW_EMAIL` (`hello@sunfloweracressoaps.com`, also a confirmed subscriber on `sunfloweracres-list` so test-sends have somewhere to go) — scoped to a dedicated `campaigns-sunfloweracres` listmonk API user (see `reground-infrastructure`'s `campaigns-sunfloweracres` Ansible role).
The workflows in `.gitea/workflows/` pin a specific `eec-campaigns` version (currently `v0.1.4`). Bump that pin deliberately when a new tool version is ready, not automatically.
## Sending as hello@sunfloweracressoaps.com
Listmonk sends everything — every domain's campaigns — authenticated as one shared identity, `hello@reground.org`. For a campaign here to actually go out `From: hello@sunfloweracressoaps.com` instead of bouncing as a spoofing attempt, Mailcow's Sender ACL on `hello@reground.org` has to explicitly allow it — confirm this is in place (see `reground-infrastructure`'s `campaigns-sunfloweracres` role, which flags it as a manual step) before relying on a real send.
+18 -11
View File
@@ -1,18 +1,25 @@
---
subject: "Welcome!"
lists: ["Newsletter"]
from_email: "Your Name <hello@example.org>"
subject: "Welcome to Sunflower Acres"
lists: ["sunfloweracres-list"]
from_email: "Sunflower Acres <hello@sunfloweracressoaps.com>"
tags: ["welcome"]
---
This is a sample campaign showing the `campaign.md` format — edit it, rename
its directory, or delete it entirely once you've got the hang of things. See
the [eec-campaigns README](https://gitea.reground.org/will/eec-campaigns#campaignmd)
for the full field reference.
{{ if .Subscriber.Attribs.first_name }}Hi {{ .Subscriber.Attribs.first_name }},{{ else }}Hi there,{{ end }}
Thanks for subscribing! You'll hear from us here whenever we've got something
worth sharing.
Thanks for signing up. Here's what you can expect from us: notes on new batches, honest word when something's running low or out, and the occasional look at how a bar actually gets made.
Talk soon.
Every bar we sell is cold-processed by hand, in small batches, and cured for six weeks before it ever reaches you. We keep the ingredient lists short and recognizable — things like rolled oats, French green clay, activated charcoal, oat milk, local honey, and shea butter, not a wall of things you'd need to look up.
Right now the lineup is:
- **Lavender Oatmeal Bar** — $8, gentle enough for every day
- **Clay & Charcoal Mint Bar** — $9, running low
- **Oat Milk & Honey Bar** — $8, unscented and mild
- **Cedar & Sage Bar** — $9, currently out while we cure the next batch
If something's out, we'll tell you rather than let you guess — that's the whole point of this list.
Talk soon,
Matthew & Marisol
Sunflower Acres
+31 -29
View File
@@ -1,14 +1,14 @@
<!--
Listmonk's own stock campaign template (static/email-templates/default.tpl
in knadh/listmonk), committed here verbatim as a known-good, unsubscribe-
capable starting point rather than something invented from scratch. Synced
into listmonk via `campaigns template <name> email-templates/campaign.html`
(see internal/campaign/template.go) as the is_default campaign template, so
every campaign this tool creates gets a real unsubscribe link/footer without
per-campaign template_id wiring. Restyle freely — the two load-bearing tags
are `{{ template "content" . }}` (where the campaign body gets injected) and
`{{ UnsubscribeURL }}` (the actual unsubscribe link); everything else here is
just Listmonk's default look.
Based on Listmonk's own stock campaign template (static/email-templates/
default.tpl in knadh/listmonk), restyled to sunfloweracressoaps.com's palette
(forest green / cream / sun yellow — see will/sunfloweracres-site's
assets/css/style.css for the canonical values). Synced into listmonk via
`campaigns template <name> email-templates/campaign.html` (see internal/
campaign/template.go) as the is_default campaign template, so every campaign
this tool creates gets a real unsubscribe link/footer without per-campaign
template_id wiring. The two load-bearing tags are `{{ template "content" . }}`
(where the campaign body gets injected) and `{{ UnsubscribeURL }}` (the
actual unsubscribe link).
-->
<!doctype html>
<html>
@@ -19,56 +19,58 @@ just Listmonk's default look.
<base target="_blank">
<style>
body {
background-color: #F0F1F3;
font-family: 'Helvetica Neue', 'Segoe UI', Helvetica, sans-serif;
background-color: #2C4F2F;
font-family: Seravek, 'Gill Sans Nova', 'Gill Sans', 'Segoe UI', system-ui, sans-serif;
font-size: 15px;
line-height: 26px;
margin: 0;
color: #444;
color: #F3EEDC;
}
pre {
background: #f4f4f4f4;
background: #375E3B;
padding: 2px;
}
table {
width: 100%;
border: 1px solid #ddd;
border: 1px solid #4F7048;
}
table td {
border-color: #ddd;
border-color: #4F7048;
padding: 5px;
}
.wrap {
background-color: #fff;
background-color: #375E3B;
padding: 30px;
max-width: 525px;
margin: 0 auto;
border-radius: 5px;
border: 1px solid #4F7048;
}
.button {
background: #0055d4;
background: #E7A62E;
border-radius: 3px;
text-decoration: none !important;
color: #fff !important;
color: #2E2008 !important;
font-weight: bold;
padding: 10px 30px;
display: inline-block;
}
.button:hover {
background: #111;
background: #7A4A28;
color: #F3EEDC !important;
}
.footer {
text-align: center;
font-size: 12px;
color: #888;
color: #CBC1A2;
}
.footer a {
color: #888;
color: #CBC1A2;
margin-right: 5px;
}
@@ -82,10 +84,10 @@ just Listmonk's default look.
}
a {
color: #0055d4;
color: #A9C99B;
}
a:hover {
color: #111;
color: #C3DBB2;
}
@media screen and (max-width: 600px) {
.wrap {
@@ -97,17 +99,17 @@ just Listmonk's default look.
}
</style>
</head>
<body style="background-color: #F0F1F3;font-family: 'Helvetica Neue', 'Segoe UI', Helvetica, sans-serif;font-size: 15px;line-height: 26px;margin: 0;color: #444;">
<body style="background-color: #2C4F2F;font-family: Seravek, 'Gill Sans Nova', 'Gill Sans', 'Segoe UI', system-ui, sans-serif;font-size: 15px;line-height: 26px;margin: 0;color: #F3EEDC;">
<div class="gutter" style="padding: 30px;">&nbsp;</div>
<div class="wrap" style="background-color: #fff;padding: 30px;max-width: 525px;margin: 0 auto;border-radius: 5px;">
<div class="wrap" style="background-color: #375E3B;padding: 30px;max-width: 525px;margin: 0 auto;border-radius: 5px;border: 1px solid #4F7048;">
{{ template "content" . }}
</div>
<div class="footer" style="text-align: center;font-size: 12px;color: #888;">
<div class="footer" style="text-align: center;font-size: 12px;color: #CBC1A2;">
<p>
<a href="{{ UnsubscribeURL }}" style="color: #888;">{{ L.T "email.unsub" }}</a>
<a href="{{ UnsubscribeURL }}" style="color: #CBC1A2;">{{ L.T "email.unsub" }}</a>
&nbsp;&nbsp;
<a href="{{ MessageURL }}" style="color: #888;">{{ L.T "email.viewInBrowser" }}</a>
<a href="{{ MessageURL }}" style="color: #CBC1A2;">{{ L.T "email.viewInBrowser" }}</a>
</p>
</div>
<div class="gutter" style="padding: 30px;">&nbsp;{{ TrackView }}</div>