Files
eec-campaigns/main.go
T
will 4006dca9f5
CI / test (push) Successful in 12s
Split tool from content: eec-campaigns becomes a standalone CLI
Campaign content and listmonk credentials now live in separate
per-domain repos that install this tool at a pinned version, so a
client with access to one domain's content repo has no path to
another domain's secrets. Renames the module to the actual Gitea
host path so `go install` works without vanity-import redirects,
drops the four workflows that assumed campaigns/ content lived here,
adds real go-test CI, and rewrites the README for the tool's new
audience.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 20:00:52 -04:00

125 lines
3.3 KiB
Go

// Command campaigns drives listmonk's Campaign API from git-authored
// Markdown: sync creates/updates draft campaigns from campaigns/<slug>/
// directories, send transitions one to running (the one real send trigger
// in this tool), and test previews a campaign's current content without
// touching its status.
package main
import (
"fmt"
"log"
"os"
"strings"
"gitea.reground.org/will/eec-campaigns/internal/campaign"
"gitea.reground.org/will/eec-campaigns/internal/listmonk"
)
func main() {
if len(os.Args) < 2 {
usage()
}
lm := listmonk.New(
mustEnv("LISTMONK_BASE_URL"),
mustEnv("LISTMONK_API_USER"),
mustEnv("LISTMONK_API_TOKEN"),
)
switch os.Args[1] {
case "sync":
if len(os.Args) < 3 {
fmt.Fprintln(os.Stderr, "usage: campaigns sync PATH")
os.Exit(1)
}
runSync(lm, os.Args[2])
case "send":
if len(os.Args) < 3 {
fmt.Fprintln(os.Stderr, "usage: campaigns send SLUG")
os.Exit(1)
}
runSend(lm, os.Args[2])
case "test":
if len(os.Args) < 4 {
fmt.Fprintln(os.Stderr, "usage: campaigns test SLUG EMAIL...")
os.Exit(1)
}
runTest(lm, os.Args[2], os.Args[3:])
case "template":
if len(os.Args) < 4 {
fmt.Fprintln(os.Stderr, "usage: campaigns template NAME PATH")
os.Exit(1)
}
runTemplate(lm, os.Args[2], os.Args[3])
default:
usage()
}
}
func usage() {
fmt.Fprintln(os.Stderr, "usage: campaigns <sync PATH|send SLUG|test SLUG EMAIL...|template NAME PATH>")
os.Exit(1)
}
// runSync's default preview address(es) come from CAMPAIGNS_PREVIEW_EMAIL
// (comma-separated), used for any campaign that doesn't set its own
// preview_emails in frontmatter. Optional — sync still works with none set,
// it just won't auto-preview campaigns that don't specify their own.
func runSync(lm *listmonk.Client, path string) {
var defaultPreview []string
if raw := os.Getenv("CAMPAIGNS_PREVIEW_EMAIL"); raw != "" {
for _, addr := range strings.Split(raw, ",") {
if addr = strings.TrimSpace(addr); addr != "" {
defaultPreview = append(defaultPreview, addr)
}
}
}
result, err := campaign.SyncDir(lm, path, defaultPreview)
if err != nil {
log.Fatalf("sync: %v", err)
}
log.Printf("synced: %v", result.Synced)
log.Printf("unchanged: %v", result.Unchanged)
if len(result.Rejected) > 0 {
log.Printf("rejected: %v", result.Rejected)
}
if len(result.PreviewFailed) > 0 {
log.Printf("preview send failed: %v", result.PreviewFailed)
}
if len(result.Rejected) > 0 || len(result.PreviewFailed) > 0 {
os.Exit(1)
}
}
func runSend(lm *listmonk.Client, slug string) {
camp, err := campaign.Send(lm, slug)
if err != nil {
log.Fatalf("send: %v", err)
}
log.Printf("sent: campaign %q (id=%d) is now running", slug, camp.ID)
}
func runTest(lm *listmonk.Client, slug string, emails []string) {
if err := campaign.Test(lm, slug, emails); err != nil {
log.Fatalf("test: %v", err)
}
log.Printf("test: sent preview of %q to %v", slug, emails)
}
func runTemplate(lm *listmonk.Client, name, path string) {
tpl, err := campaign.SyncTemplate(lm, name, path)
if err != nil {
log.Fatalf("template: %v", err)
}
log.Printf("template: %q (id=%d) is now the default campaign template", tpl.Name, tpl.ID)
}
func mustEnv(key string) string {
v := os.Getenv(key)
if v == "" {
log.Fatalf("%s environment variable is required", key)
}
return v
}