Initial commit: webtrees Email Newsletter module
Recurring email newsletter for webtrees 2.2+. Each enabled tree
sends upcoming birthdays of living individuals, optional marriage
anniversaries of intact couples, and a once-per-calendar-month
historical section of births and deaths of deceased individuals.
Triggered exclusively by an external scheduler (system cron,
systemd timer, etc.) hitting a token-gated HTTP endpoint — never
on visitor page loads. The "is it due?" decision is idempotent
within the configured frequency window.
Per-user subscription is integrated into the built-in
/my-account/{tree} page via a custom view + a decorated
AccountUpdate handler. Admins can add external addresses and
trigger an immediate send for testing. Email body renders in
German for German-language users; English otherwise. Birthdays
and anniversaries are formatted with the upcoming-event ordinal
age (e.g. "45th birthday" / "45. Geburtstag").
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
# webtrees Email Newsletter
|
||||
|
||||
A [webtrees](https://www.webtrees.net/) 2.2+ custom module that sends recurring
|
||||
email newsletters with:
|
||||
|
||||
- **Upcoming birthdays** of still-living individuals.
|
||||
- **Upcoming marriage anniversaries** of intact couples (optional — admin
|
||||
toggle, per tree). Marriages with a divorce or annulment fact are excluded
|
||||
automatically.
|
||||
- **Once-per-month historical section**: births and deaths of deceased
|
||||
individuals whose anniversary falls in the upcoming window.
|
||||
|
||||
The decision to actually send is made by comparing a stored "last sent"
|
||||
timestamp to the configured frequency, so the dispatch run is idempotent —
|
||||
calling the trigger more often than the frequency simply does nothing
|
||||
extra.
|
||||
|
||||
## Requirements
|
||||
|
||||
- webtrees ≥ 2.2.0
|
||||
- PHP ≥ 8.2
|
||||
- A working SMTP / sendmail configuration in *webtrees → Control panel →
|
||||
Sending email* (this module reuses webtrees' standard mailer).
|
||||
- An external scheduler on the host: system `cron`, a `systemd` timer,
|
||||
a Kubernetes `CronJob`, or anything else that can fire an HTTP request
|
||||
at a fixed interval. **Newsletter dispatch never runs on visitor page
|
||||
loads — it only runs when the scheduler triggers it.**
|
||||
|
||||
## Installation
|
||||
|
||||
1. Copy this directory into the webtrees `modules_v4/` folder, renaming
|
||||
it to `email_newsletter` (the folder name determines the internal
|
||||
module identifier — the registered name will be
|
||||
`_email_newsletter_`).
|
||||
|
||||
```sh
|
||||
cp -r webtrees_email_newsletter /var/www/webtrees/modules_v4/email_newsletter
|
||||
```
|
||||
|
||||
2. In the webtrees control panel, go to *Modules → All modules* and
|
||||
enable **Email Newsletter**.
|
||||
|
||||
3. Open *Control panel → Modules → Email Newsletter → Preferences* and:
|
||||
- Enable newsletter dispatch per tree.
|
||||
- Pick a frequency (default: 14 days).
|
||||
- Optionally toggle marriage anniversaries and add any extra
|
||||
external email addresses.
|
||||
- Copy the **Cron URL** at the bottom — this is the secret-token
|
||||
URL your scheduler must hit.
|
||||
|
||||
## Setting up the scheduler
|
||||
|
||||
> **Why no built-in scheduler?** PHP has no daemon, and frameworks like
|
||||
> Laravel rely on a once-per-minute system cron to fire their internal
|
||||
> scheduler. This module follows the same convention: the host OS owns
|
||||
> the timer, the module owns the "is it actually due?" decision.
|
||||
|
||||
### System cron
|
||||
|
||||
```cron
|
||||
# Run every 15 minutes. The module itself decides whether sending is due.
|
||||
*/15 * * * * curl -fsS --max-time 60 'https://example.com/module/_email_newsletter_/Cron?token=YOUR_TOKEN' > /dev/null
|
||||
```
|
||||
|
||||
### systemd timer
|
||||
|
||||
`/etc/systemd/system/webtrees-newsletter.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=webtrees newsletter trigger
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/curl -fsS --max-time 60 "https://example.com/module/_email_newsletter_/Cron?token=YOUR_TOKEN"
|
||||
```
|
||||
|
||||
`/etc/systemd/system/webtrees-newsletter.timer`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Trigger webtrees newsletter dispatch
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* *:00/15
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
Then `systemctl enable --now webtrees-newsletter.timer`.
|
||||
|
||||
### Forcing a one-off send
|
||||
|
||||
The admin **Preferences** page has a *Send now* button for testing.
|
||||
For an unattended one-off send, append `&force=1` to the cron URL —
|
||||
that bypasses the "is it due?" check.
|
||||
|
||||
## Subscribers
|
||||
|
||||
Two sources, combined:
|
||||
|
||||
1. **Logged-in webtrees users** who opt in via the per-tree
|
||||
*Newsletter subscription* menu entry (visible only to logged-in
|
||||
users on trees where the module is enabled). Only **approved and
|
||||
email-verified** accounts will receive the newsletter.
|
||||
2. **External addresses** the tree administrator lists in the
|
||||
*Extra recipient email addresses* textarea (one per line).
|
||||
|
||||
## Privacy
|
||||
|
||||
The dispatch service does not impersonate a webtrees user, so it sees
|
||||
the tree from the **visitor** access level. Records and facts that
|
||||
your tree settings hide from visitors will be omitted from the
|
||||
newsletter even if a recipient has higher in-app access. This is the
|
||||
safest default for an outbound email — if you need to expose more
|
||||
information, relax the tree's visitor-access settings or hand-curate
|
||||
the *Extra recipient* list.
|
||||
|
||||
## License
|
||||
|
||||
AGPL-3.0-or-later. See `LICENSE` for the full text.
|
||||
Reference in New Issue
Block a user