# 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 (formatted as ordinal age — *"45th birthday"* / *"45. Geburtstag"*). - **Upcoming marriage anniversaries** of intact couples (admin toggle, per tree). Marriages with a divorce or annulment fact are excluded automatically. - **Historical events** — births and deaths of deceased individuals whose anniversary falls in the upcoming window. The look-ahead window and the send cadence are the same number: one "every N days" setting (default 14) drives both the cron interval and how far each issue looks ahead. Issues with nothing to report are silently skipped. Each recipient gets a per-recipient render — language, relationship labels, detail filter, cadence, and personalisation tokens are all resolved against *their* webtrees account. ## Highlights - **Editorial layout** with embedded circular avatars, a left-side timeline rail, and event-type icons (birth / death / marriage). - **BockenTheme light-mode skin** — Open Sans, cream background, Nord accent palette. The newsletter and the website read as one product. - **Per-recipient localisation** — German for users whose webtrees language starts with `de`, English otherwise. Subject line, body, date strings, and (optionally) a custom subject prefix are all localised. Subject dates use `IntlDateFormatter` for the recipient's locale. - **Per-recipient relationship labels** — *"your mother"*, *"4th great- grandfather"*, *"first cousin twice removed"*. Uses webtrees' own `RelationshipService` so the labels match the site. - **Kin-distance detail filter** — close family get the full card (avatar + timeline + icon); distant kin appear as a single-line bullet at the foot of each section. The "distance" radius is an admin setting (default 3); spouses inherit their partner's distance; recipients with no linked tree record always see the full detailed view. - **Per-recipient cadence** — each subscriber can pick weekly, biweekly, monthly, every-two-months, quarterly, or "use site default" on their `/my-account/{tree}` page. - **One-shot intro paragraph** — admins can attach a Markdown intro (bilingual, EN/DE) to the next issue. Supports `{{first_name}}`, `{{last_name}}`, `{{username}}`, `{{email}}` personalisation tokens. Rendered alongside the tree-contact user's avatar as an editorial column. Cleared automatically after a successful send. - **Cron-only dispatch** — the "is it due?" decision is made server- side against stored timestamps. Calling the trigger more often than the cadence is harmless and idempotent. ## Requirements - webtrees ≥ 2.2.0 - PHP ≥ 8.2 with `ext-intl` (for locale-aware subject dates) and either `ext-imagick` or `ext-gd` (for avatar resizing — falls back to original-size embeds if neither is present). - A working SMTP / sendmail configuration in *Control panel → Sending email*. This module reuses webtrees' standard mailer and signs with the site's DKIM keys if configured. - An external scheduler on the host (system `cron`, `systemd` timer, Kubernetes `CronJob`, …) that can fire an HTTP request at a fixed interval. **Newsletter dispatch never runs on visitor page loads.** ## Installation 1. Copy this directory into the webtrees `modules_v4/` folder, renaming it to `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, for each tree: - Tick *Enable newsletter for this tree*. - Set the send-cadence (default 14 days). This same number is the look-ahead window for the next issue. - Toggle *Include marriage anniversaries* if desired. - Set *Detailed view distance* (default 3). Lower values produce a terser email focused tightly on close kin. - Optional: set per-locale *Subject prefix* and a *Generic* fallback (e.g. `[Bocken family] `). - Optional: tick existing webtrees users in *Subscribed users* to subscribe them. Users can still adjust their own subscription and cadence on `/my-account/{tree}`. - Optional: add external (non-user) addresses in *Extra recipient email addresses*. 4. Copy the **Cron URL** at the bottom and wire it into your scheduler (see below). ## 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 forced send (bypassing the per-recipient "is it due?" check), append `&force=1` to the cron URL. ## Subscribers Three sources, combined and de-duplicated by email: 1. **Webtrees users** who opt in themselves via the per-tree *Newsletter subscription* menu entry on `/my-account/{tree}` — visible only to logged-in users on trees where the module is enabled. 2. **Webtrees users** an admin subscribes from the preferences page. 3. **External addresses** the admin lists in *Extra recipient email addresses*. Only **approved and email-verified** webtrees accounts will receive the newsletter. External addresses always receive on every run (they have no per-user cadence timer). ## Sender identity To match webtrees' own convention for system-generated email (registration, password resets, "new version available"), the `From:` header is **SiteUser** — *Control panel → Sending email → Sender name / Sender email* (`SMTP_FROM_NAME` / `SMTP_DISP_NAME`). The tree's contact user becomes the `Reply-To:`, so replies still reach a human admin. If `SMTP_FROM_NAME` isn't set the dispatcher falls back to the tree contact for `From:` as well, so the message always has a valid sender envelope. ## 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.