- New "Upcoming family events" block for the tree home page,
rendering the same card + timeline visualisation as the
newsletter email but adapted for web context: avatars resolve
to media-file URLs (no CID), the silhouette placeholder reuses
BockenTheme's .person-card .photo-placeholder rules so the
Nord-mixed shades and dark-mode handling stay in sync with the
full-diagram plugin, and per-viewer relationship labels surface
when the signed-in user is linked to an Individual on the tree.
- Default window 30 days, configurable via the standard block
config UI. Wide-screen wrapper caps at 760 px with a small
right-side breathing margin.
- Block renders via AJAX and caches its HTML for 5 minutes per
(tree, window, viewer, locale), so the tree home page paints
instantly and repeat visits skip the heavy event/query +
relationship-BFS work.
- Living-kin section is now a single date-sorted timeline that
mixes birthdays and intact-couple anniversaries. Each row's
icon + label key off the fact's tag, so a mixed run shares
one rail. Applies to both block and email.
- Newsletter subscription menu entry removed from the header;
the form is still reachable on the standard /my-account page
via the registerCustomView override.
Two-level gate: resolve and embed the tree-contact portrait only
when (a) at least one locale on the tree has a non-empty intro on
file, and (b) the specific recipient is still pending delivery
for the current intro version. Recipients who have already seen
this intro, or whose locale has no intro, no longer carry the
extra image bytes.
Replaces "clear intro after first send", which dropped the message
for any subscriber still queued on a slower cadence.
- Each non-empty admin save bumps a per-locale version counter on
the tree. The dispatcher includes the intro only for recipients
whose last-seen version is behind, then advances their watermark
after a successful send. Webtrees users get a per-user watermark;
external addresses share one tree-level watermark. Re-saving the
same text is a no-op.
- The preferences page now shows delivery progress per locale:
"Delivered to X of Y subscriber(s)" plus a collapsible Pending
list with name + email of each subscriber who hasn't received
the current intro yet, and a single "External recipients (N)"
row when the external watermark is behind.
- README rewritten to reflect every feature shipped since the
initial commit (BockenTheme skin, embedded avatars, relationship
labels, kin-distance filter, per-user cadence, bilingual subject
prefix, locale-aware subject date, SiteUser as From, three
subscriber sources, Markdown intro with personalisation tokens).
- Admin can set a per-locale intro paragraph for the next issue on
the preferences page; cleared automatically after a successful
send. Stored in module_setting (longText) so multi-paragraph
notes fit.
- Intro is rendered via webtrees' CommonMark factory (same flavour
as notes) with raw HTML escaped, supports {{first_name}},
{{last_name}}, {{username}}, {{email}} substitution per recipient.
- Two-column intro layout: tree contact user's linked Individual
becomes the editorial portrait on the left. Their avatar is
added to the per-recipient embed set so the inline image always
resolves rather than falling through to a tree-page login link.
- Masthead now shows the tree URL under the title.
- Avatar source dimensions bumped 96→192 px and JPEG quality 75→88
so portraits stay crisp at retina display ratios.
- Admin preferences page can now subscribe existing webtrees users
per tree, not just external addresses.
- Subject prefix is now configurable per locale (en/de), and the
date in the subject is formatted via IntlDateFormatter in the
recipient's locale.
- "From:" header now uses SiteUser (SMTP_FROM_NAME/SMTP_DISP_NAME)
to match webtrees' own system-mail convention; the tree contact
becomes the Reply-To.
Admin-facing simplification:
- Dropped separate \"lookahead\" and \"historical lookahead\" tree
prefs (and the once-per-month historical gate). A single
\"send every N days\" number now drives both the cron cadence
and the window each issue looks ahead for living + deceased
events.
- Default 14, range 1–90, applies uniformly.
User-facing addition:
- The /my-account/{tree} subscription card gained an \"Email
frequency\" select with options: use site default, weekly,
every 2 weeks, monthly, every 2 months, quarterly. Stored as
a per-tree-per-user preference.
- Dispatch now checks each recipient's own cadence against
their own last-sent timestamp. Admin-added external addresses
with no webtrees account always receive every run (no
per-user state).
- Newsletter footer now reads \"You can change how often you
receive this email, or unsubscribe entirely, in the Newsletter
subscription section on your My account page\" — true now
that the control exists.
German translations updated for the new strings; stale ones
removed.
Replaces the previous "depth in generations along the strict
lineal chain" definition (which excluded siblings, aunts, cousins
entirely) with the metric the user actually wants: the number of
descent-steps separating the target from the recipient's closest
direct ancestor or descendant.
Examples relative to the recipient:
- sibling: 1 (parent → sibling)
- great-aunt: 1 (great-grandparent → great-aunt)
- nephew: 2 (parent → sibling → nephew)
- first cousin: 2 (grandparent → aunt → cousin)
- second cousin: 3
- ego, parents, grandparents, ..., children, ..., great-greats: 0
- own spouse, step-parents, brothers-in-law: inherit partner's
distance (so spouse-of-distance-1 is also distance 1)
Implementation:
- Anchor set seeded with R's direct ancestors + R + direct
descendants (capped at 25 generations to bound runaway data).
- Multi-source BFS expanding by descent only.
- Spouse propagation at every level so a person and their
spouse always share the same distance.
- Memoised per (recipient xref, max distance).
Tree preference key and range kept (NEWSLETTER_LINEAL_DEPTH,
0–10, default 3); only the semantics and the user-facing label
+ help text change, with concrete examples in both English and
German.
Per-recipient: only direct ancestors and direct descendants
within a configurable number of generations (default 3) get the
full row treatment (avatar, icon, timeline). Everyone else falls
through to a compact text-only bullet list at the bottom of the
same section.
- New tree preference NEWSLETTER_LINEAL_DEPTH (range 0–10,
default 3) with a clearly-explained admin input.
- RelationshipPathFinder::linealKin() does two cheap recursive
expansions (ancestors and descendants only — no spouse or
sibling traversal) and returns the xref set. Memoised per
recipient within a dispatch run.
- Avatar attachments are filtered per recipient to only the
embeds actually referenced in their HTML, so summary-only rows
no longer inflate per-email size with unused images.
- Recipients with no PREF_TREE_ACCOUNT_XREF (external admin
addresses, users not linked to a record) see the entire
newsletter in detail — no lineal anchor to filter against.
- German translations for the three new section kickers ("Other
birthdays", etc.) and the admin input help text.
Each featured person now carries a parenthetical label relative
to the recipient: "Jane Doe (your mother) — 45th birthday",
"Karl Müller (your 4th great-grandfather) — death". Labels are
italic, muted, and only appear when a path can be computed.
- New RelationshipPathFinder service mirrors webtrees'
RelationshipService::getCloseRelationship BFS but with a
configurable depth (default 14 hops ≈ 7 generations) so it
reaches great-great-grandparents and beyond. Results are
memoised per (recipient xref, target xref) within one
dispatch run.
- nameFromPath() formatting is delegated to webtrees so the
label honours the configured UI language (German, English,
etc.) and gendered/inflected forms.
- The recipient's tree-bound Individual is looked up via
Tree::getUserPreference(user, PREF_TREE_ACCOUNT_XREF). External
admin-added recipients (no webtrees account, no linked record)
silently get no labels — names render plain.
- Trade-off: the view now renders once per recipient (instead of
once per language group), because the relationship map is
personalised. For typical subscriber counts the extra string-
concat cost is negligible compared to the SMTP send itself.
Reworks the newsletter as a family-chronicle layout: ivory paper
background, deep oxblood ink, aged-gold accents, EB Garamond
display with Georgia body fallback.
- Inline SVG event icons (sparkle for birth, dagger for death,
interlocked rings for marriage). Falls back silently in
Outlook desktop; modern Gmail / Apple / iOS / Outlook 365
render them.
- Right-side gold hairline timeline running through the date
column of every event row, with a filled dot per entry.
- Person names link to their webtrees Individual page via
Individual::url() (absolute URL through route() → BASE_URL),
including the avatar circles.
- German strings added for the new section kickers
("Family Chronicle", "Living kin who will celebrate this
fortnight.", "Marriages still intact.").
Source media files can easily be multiple megabytes — embedding
the originals made a per-recipient email balloon to 10MB+. Each
avatar is now cover-cropped to 96x96 (HiDPI for the rendered
48px circle) and re-encoded as JPEG q=75 via Intervention\Image,
which webtrees already depends on. Typical avatar payload drops
from megabytes to ~5-15KB.
Falls back to the original bytes (with a log warning) if neither
Imagick nor GD is loaded — better an oversized email than none.
The "once-per-calendar-month" gate that prevents the historical
section from appearing on every regular send also suppressed it
on admin "Send now" previews after the first run of the month —
making the section silently disappear when re-testing the email.
Force-send now bypasses the gate but still updates the
last-historical-month stamp, so the real monthly cadence stays
intact for cron-driven sends.
Pull each individual's highlighted media image via webtrees'
Individual::findHighlightedMediaFile, attach as Symfony inline
parts with stable cid:avatar-<xref> identifiers, and render
border-radius:50% on the <img>. Couples on anniversaries show
both spouses' circles side-by-side.
Fallback when no image is available (privacy-hidden record, no
OBJE, external URL, unreadable file): a CSS-only coloured circle
with the person's initials. The hue is derived from a hash of
the XREF so the same person keeps the same colour across
newsletters.
Done via a NewsletterMailer subclass of EmailService that adds a
sendWithEmbeds() method — the parent's transport() and DKIM
config still apply, only the message-construction path differs.
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").