Send at most one grouped birthday reminder email

Collects birthdays in the next 7 days, groups them into sections
(Today/Tomorrow/In N days), and sends a single email.

Fixes #201.
This commit is contained in:
Edward Betts 2025-08-12 22:33:52 +01:00
parent ebceb4cb51
commit 97c0531a22

View file

@ -21,6 +21,7 @@ import agenda.mail
import agenda.thespacedevs import agenda.thespacedevs
import agenda.types import agenda.types
import agenda.uk_holiday import agenda.uk_holiday
from agenda.event import Event
from agenda.types import StrDict from agenda.types import StrDict
from web_view import app from web_view import app
@ -295,7 +296,11 @@ def update_gandi(config: flask.config.Config) -> None:
def check_birthday_reminders(config: flask.config.Config) -> None: def check_birthday_reminders(config: flask.config.Config) -> None:
"""Check for upcoming birthdays and send email reminders.""" """Send at most one grouped birthday reminder email per day.
Collects birthdays in the next 7 days, groups them into sections
(Today/Tomorrow/In N days), and sends a single email.
"""
today = date.today() today = date.today()
data_dir = config["PERSONAL_DATA"] data_dir = config["PERSONAL_DATA"]
entities_file = os.path.join(data_dir, "entities.yaml") entities_file = os.path.join(data_dir, "entities.yaml")
@ -303,55 +308,65 @@ def check_birthday_reminders(config: flask.config.Config) -> None:
if not os.path.exists(entities_file): if not os.path.exists(entities_file):
return return
# Get upcoming birthdays (next 7 days)
birthdays = agenda.birthday.get_birthdays(today, entities_file) birthdays = agenda.birthday.get_birthdays(today, entities_file)
upcoming_birthdays = []
for birthday in birthdays: # Collect next 7 days into a dict keyed by days-until
days_until = (birthday.date - today).days by_days: dict[int, list[Event]] = {}
for ev in birthdays:
days_until = (ev.as_date - today).days
if 0 <= days_until <= 7: if 0 <= days_until <= 7:
upcoming_birthdays.append((birthday, days_until)) by_days.setdefault(days_until, []).append(ev)
if not upcoming_birthdays: if not by_days:
return return
# Group birthdays by days until # Build subject
birthday_groups = {} headings: list[str] = []
for birthday, days_until in upcoming_birthdays: if 0 in by_days:
if days_until not in birthday_groups: headings.append("today")
birthday_groups[days_until] = [] if 1 in by_days:
birthday_groups[days_until].append(birthday) headings.append("tomorrow")
others = sum(1 for k in by_days.keys() if k not in (0, 1))
if others:
plural = "s" if others != 1 else ""
headings.append(f"{others} other{plural}")
subject = (
f"🎂 Birthday reminders ({', '.join(headings)})"
if headings
else "🎂 Birthday reminders"
)
# Send reminder emails # Build body (UK style dates)
for days_until, birthdays in birthday_groups.items(): lines: list[str] = ["Upcoming birthdays (next 7 days):", ""]
if days_until == 0: for delta in sorted(by_days.keys()):
subject = "🎂 Birthday Today!" if delta == 0:
elif days_until == 1: lines.append("Today")
subject = "🎂 Birthday Tomorrow!" elif delta == 1:
lines.append("Tomorrow")
else: else:
subject = f"🎂 Birthday in {days_until} days" lines.append(f"In {delta} days")
body_lines = ["Birthday Reminder:\n"] entries = sorted(
for birthday in birthdays: by_days[delta],
date_str = birthday.date.strftime("%A, %B %d, %Y") key=lambda e: (e.as_date, (e.title or e.name or "")),
if days_until == 0:
body_lines.append(f"Today: {birthday.title} - {date_str}")
elif days_until == 1:
body_lines.append(f"Tomorrow: {birthday.title} - {date_str}")
else:
body_lines.append(f"{date_str}: {birthday.title}")
body_lines.append(
f"\nView all birthdays: https://edwardbetts.com/agenda/birthdays"
) )
body = "\n".join(body_lines) for ev in entries:
d = ev.as_date
# Portable UK-style date: weekday, D Month YYYY
date_str = f"{d:%A}, {d.day} {d:%B %Y}"
label = ev.title or ev.name
lines.append(f"{label}{date_str}")
if sys.stdin.isatty(): lines.append("")
print(f"Birthday reminder: {subject}")
print(body)
agenda.mail.send_mail(config, subject, body) lines.append("View all birthdays: https://edwardbetts.com/agenda/birthdays")
body = "\n".join(lines)
if sys.stdin.isatty():
print(f"Birthday reminder: {subject}\n{body}")
agenda.mail.send_mail(config, subject, body)
def main() -> None: def main() -> None: