Listmonk in Kirby 5 einbinden
Listmonk läuft bereits als Docker-Container und ist unter newsletter.bayerwald.social erreichbar. Die Verbindung zu Kirby erfolgt ausschließlich über die Listmonk-REST-API — kein direkter Datenbankzugriff, keine gemeinsamen Sessions. Kirby bleibt das Frontend, Listmonk übernimmt alles was mit E-Mail zu tun hat.
Voraussetzungen
- Kirby 5 mit installiertem Plugin
mzur/kirby-uniformfür die Formularverarbeitung - Listmonk-Instanz mit angelegter öffentlicher Liste und einem API-Key
- PHP-Funktion zum Aufruf der Listmonk-API (einfacher HTTP-Request via
curloderfile_get_contents)
Der API-Key wird in Listmonk unter Settings → API Keys angelegt. Er wird in der Kirby-Konfiguration hinterlegt:
// site/config/config.php
'listmonk.url' => 'https://newsletter.bayerwald.social',
'listmonk.username' => 'api',
'listmonk.password' => 'DEIN-API-KEY',
'listmonk.listId' => 1, // ID der Ziel-Liste in Listmonk
Anmeldeformular mit Uniform
Das Anmeldeformular wird als eigene Kirby-Seite angelegt — Template newsletter.php, Blueprint newsletter.yml. Uniform übernimmt Validierung und Verarbeitung, der Controller schickt die Daten per API an Listmonk.
Blueprint site/blueprints/pages/newsletter.yml:
title: Newsletter
Controller site/controllers/newsletter.php:
<?php
return function ($kirby, $page) {
$form = new Uniform\Form([
'email' => [
'rules' => ['required', 'email'],
'message' => 'Bitte eine gültige E-Mail-Adresse eingeben.',
],
]);
if ($kirby->request()->is('POST') && $form->validate()) {
$email = $form->data('email');
$url = option('listmonk.url') . '/api/subscribers';
$user = option('listmonk.username');
$pass = option('listmonk.password');
$listId = option('listmonk.listId');
$payload = json_encode([
'email' => $email,
'name' => '',
'status' => 'enabled',
'lists' => [$listId],
]);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_USERPWD, "$user:$pass");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
if (isset($result['data']['id'])) {
$form->success();
} else {
$form->error('Die Anmeldung ist fehlgeschlagen. Bitte später erneut versuchen.');
}
}
return compact('form');
};
Template site/templates/newsletter.php:
<?php snippet('header') ?>
<main>
<h1><?= $page->title() ?></h1>
<?php if ($form->success()): ?>
<p>Danke — du bist jetzt angemeldet.</p>
<?php else: ?>
<form method="post">
<?php csrf() ?>
<input type="email" name="email" placeholder="E-Mail-Adresse" required>
<button type="submit">Anmelden</button>
<?php foreach ($form->errors() as $error): ?>
<p class="error"><?= $error ?></p>
<?php endforeach ?>
</form>
<?php endif ?>
</main>
<?php snippet('footer') ?>
Newsletter-Templates in Listmonk gestalten
Listmonk verwendet Go-Templates mit {{ .Subscriber.Email }} und ähnlichen Variablen. Unter Campaigns → Templates wird ein HTML-Template angelegt. Ein schlichtes, gut lesbares Template reicht für den Anfang:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: Georgia, serif; max-width: 600px; margin: 0 auto; color: #222; }
h1 { font-size: 1.4em; }
a { color: #2a6496; }
.footer { font-size: 0.8em; color: #888; margin-top: 2em; }
</style>
</head>
<body>
{{ template "content" . }}
<div class="footer">
<p>Du erhältst diese Mail weil du dich auf graffiti.bayerwald.social angemeldet hast.</p>
<p><a href="{{ UnsubscribeURL }}">Abmelden</a></p>
</div>
</body>
</html>
Der Inhalt jeder Kampagne wird im Block {{ template "content" . }} eingesetzt — dort schreibt man den eigentlichen Newsletter-Text im Kampagnen-Editor.
Verfügbare Template-Variablen:
{{ .Subscriber.Email }}— E-Mail-Adresse des Empfängers{{ .Subscriber.Name }}— Name{{ UnsubscribeURL }}— Abmeldelink (Pflicht){{ MessageURL }}— Link zur Web-Version der Mail
Automatischer Versand bei neuem Artikel
Kirby bietet Hooks — PHP-Callbacks die bei bestimmten Ereignissen ausgelöst werden. Der Hook page.update:after feuert jedes Mal wenn eine Seite im Panel gespeichert wird. Damit lässt sich beim Veröffentlichen eines Artikels automatisch eine Listmonk-Kampagne erstellen und versenden.
Der Hook wird in site/plugins/listmonk-hook/index.php angelegt:
<?php
Kirby::plugin('graffiti/listmonk-hook', [
'hooks' => [
'page.update:after' => function ($newPage, $oldPage) {
// Nur bei Artikeln und nur wenn Status von draft auf listed wechselt
if ($newPage->intendedTemplate()->name() !== 'article') return;
if ($newPage->status() !== 'listed') return;
if ($oldPage->status() === 'listed') return;
$title = $newPage->title()->value();
$url = $newPage->url();
$excerpt = strip_tags($newPage->description()->value());
$apiUrl = option('listmonk.url') . '/api/campaigns';
$user = option('listmonk.username');
$pass = option('listmonk.password');
$listId = option('listmonk.listId');
$tplId = option('listmonk.templateId'); // ID des Templates in Listmonk
$body = "**{$title}**\n\n{$excerpt}\n\n[Zum Artikel lesen]({$url})";
$payload = json_encode([
'name' => 'Neuer Artikel: ' . $title,
'subject' => $title,
'lists' => [$listId],
'template_id' => $tplId,
'content_type'=> 'markdown',
'body' => $body,
'type' => 'regular',
'status' => 'scheduled',
'send_at' => date('c', strtotime('+5 minutes')),
]);
$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_USERPWD, "$user:$pass");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_exec($ch);
curl_close($ch);
},
],
]);
Die Kampagne wird 5 Minuten nach dem Veröffentlichen automatisch versendet — genug Puffer um im Listmonk-Backend noch einzugreifen falls nötig. Die Template-ID (listmonk.templateId) wird einmalig in der config.php hinterlegt nachdem das Template in Listmonk angelegt wurde.
Weitere Newsletter-Typen
Neben dem automatischen Artikel-Newsletter sind weitere Kampagnentypen sinnvoll:
Wöchentlicher Digest — eine Zusammenfassung aller Artikel der Woche, jeden Sonntag versendet. Lässt sich mit einem Cron-Job realisieren der die Kirby-API nach neuen Seiten der letzten 7 Tage abfragt und daraus eine Kampagne baut.
Willkommensmail — wird automatisch versendet wenn sich jemand neu anmeldet. In Listmonk unter Campaigns → Transactional einrichten. Der neue Abonnent bekommt eine persönliche Begrüßung mit Hinweis auf ältere empfehlenswerte Artikel.
Ankündigungen — für besondere Ereignisse außerhalb des regulären Veröffentlichungsrhythmus: neue Rubriken, Umstrukturierungen, Wartungsarbeiten. Diese Kampagnen werden manuell erstellt und versendet.
Regionale Themen-Newsletter — falls das Blog in Zukunft mehrere Themenbereiche abdeckt, können separate Listen für verschiedene Interessengebiete angelegt werden. Abonnenten wählen beim Anmelden selbst welche Liste sie abonnieren möchten.
Listmonk-Einstellungen für den Regelbetrieb
Einige Einstellungen in Listmonk sind für den Dauerbetrieb relevant:
Unter Settings → Performance sollte die Batch-Größe beim Versand an die Kapazität von SES angepasst werden — bei 40.000 Mails pro Tag und einem gleichmäßigen Versand über den Tag sind 10–20 Mails pro Sekunde ein sinnvoller Ausgangswert.
Unter Settings → Bounces kann SES-Bounce-Handling aktiviert werden. SES sendet Bounce- und Complaint-Benachrichtigungen an eine SNS-Topic — Listmonk kann diese über einen Webhook automatisch verarbeiten und betroffene Adressen deaktivieren.
Unter Lists sollte jede Liste eine aussagekräftige Beschreibung haben, die auf der öffentlichen Anmeldeseite angezeigt wird. Abonnenten sehen so sofort wofür sie sich anmelden.