From b688bf354a69a45aec6f1ff42292faefe950c822 Mon Sep 17 00:00:00 2001 From: Will Estes Date: Thu, 16 Jul 2026 13:20:18 -0400 Subject: [PATCH] Initial scaffold: campaigns tooling for thistleandmoth.org Mirrors michaelferrara-campaigns/twosquirrelspress-campaigns. No campaigns written yet. Co-Authored-By: Claude Sonnet 5 --- .gitea/workflows/send.yml | 48 ++++++++++++ .gitea/workflows/sync-template.yml | 23 ++++++ .gitea/workflows/sync.yml | 25 +++++++ .gitea/workflows/test.yml | 32 ++++++++ README.md | 27 +++++++ campaigns/.gitkeep | 0 email-templates/campaign.html | 115 +++++++++++++++++++++++++++++ 7 files changed, 270 insertions(+) create mode 100644 .gitea/workflows/send.yml create mode 100644 .gitea/workflows/sync-template.yml create mode 100644 .gitea/workflows/sync.yml create mode 100644 .gitea/workflows/test.yml create mode 100644 README.md create mode 100644 campaigns/.gitkeep create mode 100644 email-templates/campaign.html diff --git a/.gitea/workflows/send.yml b/.gitea/workflows/send.yml new file mode 100644 index 0000000..7da6a8c --- /dev/null +++ b/.gitea/workflows/send.yml @@ -0,0 +1,48 @@ +name: Send campaign +on: + # Pushing a tag like send/big-announcement is the primary trigger — a + # deliberate, git-native, auditable action (git log --tags shows every + # real send that ever happened). These are "command tags," not release + # tags: fine to delete and re-push if a send needs retrying, since the + # actual safety net is listmonk's own draft/paused status check in + # `eec-campaigns send`, not tag uniqueness. + push: + tags: ["send/*"] + # workflow_dispatch is kept alongside it for when clicking a button is + # more convenient than a local `git tag && git push`. Same command either + # way. + workflow_dispatch: + inputs: + slug: + description: "Campaign slug (campaigns//) to send" + required: true + type: string + +jobs: + send: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: "1.23.1" + + - name: Install eec-campaigns + run: go install gitea.reground.org/will/eec-campaigns@v0.1.0 + + - name: Determine campaign slug + id: slug + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "value=${{ inputs.slug }}" >> "$GITHUB_OUTPUT" + else + echo "value=${GITHUB_REF#refs/tags/send/}" >> "$GITHUB_OUTPUT" + fi + + - name: Send campaign + run: eec-campaigns send "${{ steps.slug.outputs.value }}" + env: + LISTMONK_BASE_URL: ${{ secrets.CAMPAIGNS_LISTMONK_BASE_URL }} + LISTMONK_API_USER: ${{ secrets.CAMPAIGNS_LISTMONK_API_USER }} + LISTMONK_API_TOKEN: ${{ secrets.CAMPAIGNS_LISTMONK_API_TOKEN }} diff --git a/.gitea/workflows/sync-template.yml b/.gitea/workflows/sync-template.yml new file mode 100644 index 0000000..fa3d1b7 --- /dev/null +++ b/.gitea/workflows/sync-template.yml @@ -0,0 +1,23 @@ +name: Sync default campaign template +on: + workflow_dispatch: {} + +jobs: + sync-template: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: "1.23.1" + + - name: Install eec-campaigns + run: go install gitea.reground.org/will/eec-campaigns@v0.1.0 + + - name: Sync template to listmonk + run: eec-campaigns template "Thistle & Moth default" email-templates/campaign.html + env: + LISTMONK_BASE_URL: ${{ secrets.CAMPAIGNS_LISTMONK_BASE_URL }} + LISTMONK_API_USER: ${{ secrets.CAMPAIGNS_LISTMONK_API_USER }} + LISTMONK_API_TOKEN: ${{ secrets.CAMPAIGNS_LISTMONK_API_TOKEN }} diff --git a/.gitea/workflows/sync.yml b/.gitea/workflows/sync.yml new file mode 100644 index 0000000..3f26c9d --- /dev/null +++ b/.gitea/workflows/sync.yml @@ -0,0 +1,25 @@ +name: Sync campaigns +on: + push: + branches: [master] + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: "1.23.1" + + - name: Install eec-campaigns + run: go install gitea.reground.org/will/eec-campaigns@v0.1.0 + + - name: Sync campaigns to listmonk + run: eec-campaigns sync . + env: + LISTMONK_BASE_URL: ${{ secrets.CAMPAIGNS_LISTMONK_BASE_URL }} + LISTMONK_API_USER: ${{ secrets.CAMPAIGNS_LISTMONK_API_USER }} + LISTMONK_API_TOKEN: ${{ secrets.CAMPAIGNS_LISTMONK_API_TOKEN }} + CAMPAIGNS_PREVIEW_EMAIL: ${{ secrets.CAMPAIGNS_PREVIEW_EMAIL }} diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..00d1208 --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,32 @@ +name: Test-send campaign +on: + workflow_dispatch: + inputs: + slug: + description: "Campaign slug (campaigns//) to preview" + required: true + type: string + email: + description: "Address to send the preview to" + required: true + type: string + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: "1.23.1" + + - name: Install eec-campaigns + run: go install gitea.reground.org/will/eec-campaigns@v0.1.0 + + - name: Send preview + run: eec-campaigns test "${{ inputs.slug }}" "${{ inputs.email }}" + env: + LISTMONK_BASE_URL: ${{ secrets.CAMPAIGNS_LISTMONK_BASE_URL }} + LISTMONK_API_USER: ${{ secrets.CAMPAIGNS_LISTMONK_API_USER }} + LISTMONK_API_TOKEN: ${{ secrets.CAMPAIGNS_LISTMONK_API_TOKEN }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..9125966 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# thistleandmoth-campaigns + +thistleandmoth.org'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. + +## To send a campaign + +1. Write `campaigns//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: "Thistle & Moth "` explicitly — there's no default, and sync rejects a campaign with no `from_email` at all. +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/ && git push origin send/`. This is the one action that transitions the campaign from draft to running — a plain `git push` to master can never do it. + +Alternatively, run the `Send campaign` workflow manually from Gitea Actions with the slug as input — same effect as the tag push. + +## Other workflows + +- **`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` — scoped to a dedicated `campaigns-thistleandmoth` listmonk API user (see `reground-infrastructure`'s `campaigns-thistleandmoth` Ansible role). `CAMPAIGNS_PREVIEW_EMAIL` isn't set yet — add it (or rely on each campaign's own `preview_emails`) before the first real send. + +The workflows in `.gitea/workflows/` pin a specific `eec-campaigns` version (currently `v0.1.0`). Bump that pin deliberately when a new tool version is ready, not automatically. + +## Sending as hello@thistleandmoth.org + +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@thistleandmoth.org` instead of bouncing as a spoofing attempt, Mailcow's Sender ACL on `hello@reground.org` has to explicitly allow it. **This has not been done yet for thistleandmoth.org** — add `hello@thistleandmoth.org` to that Sender ACL in the Mailcow admin UI before the first real send, or Postfix will reject it. diff --git a/campaigns/.gitkeep b/campaigns/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/email-templates/campaign.html b/email-templates/campaign.html new file mode 100644 index 0000000..c83add3 --- /dev/null +++ b/email-templates/campaign.html @@ -0,0 +1,115 @@ + + + + + {{ .Campaign.Subject }} + + + + + + +
 
+
+ {{ template "content" . }} +
+ + +
 {{ TrackView }}
+ +