7ce8201082
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").
124 lines
4.1 KiB
Markdown
124 lines
4.1 KiB
Markdown
# 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.
|