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:
		
							parent
							
								
									ebceb4cb51
								
							
						
					
					
						commit
						97c0531a22
					
				
							
								
								
									
										89
									
								
								update.py
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								update.py
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -21,6 +21,7 @@ import agenda.mail
 | 
			
		|||
import agenda.thespacedevs
 | 
			
		||||
import agenda.types
 | 
			
		||||
import agenda.uk_holiday
 | 
			
		||||
from agenda.event import Event
 | 
			
		||||
from agenda.types import StrDict
 | 
			
		||||
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:
 | 
			
		||||
    """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()
 | 
			
		||||
    data_dir = config["PERSONAL_DATA"]
 | 
			
		||||
    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):
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    # Get upcoming birthdays (next 7 days)
 | 
			
		||||
    birthdays = agenda.birthday.get_birthdays(today, entities_file)
 | 
			
		||||
    upcoming_birthdays = []
 | 
			
		||||
 | 
			
		||||
    for birthday in birthdays:
 | 
			
		||||
        days_until = (birthday.date - today).days
 | 
			
		||||
    # Collect next 7 days into a dict keyed by days-until
 | 
			
		||||
    by_days: dict[int, list[Event]] = {}
 | 
			
		||||
    for ev in birthdays:
 | 
			
		||||
        days_until = (ev.as_date - today).days
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
    # Group birthdays by days until
 | 
			
		||||
    birthday_groups = {}
 | 
			
		||||
    for birthday, days_until in upcoming_birthdays:
 | 
			
		||||
        if days_until not in birthday_groups:
 | 
			
		||||
            birthday_groups[days_until] = []
 | 
			
		||||
        birthday_groups[days_until].append(birthday)
 | 
			
		||||
    # Build subject
 | 
			
		||||
    headings: list[str] = []
 | 
			
		||||
    if 0 in by_days:
 | 
			
		||||
        headings.append("today")
 | 
			
		||||
    if 1 in by_days:
 | 
			
		||||
        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
 | 
			
		||||
    for days_until, birthdays in birthday_groups.items():
 | 
			
		||||
        if days_until == 0:
 | 
			
		||||
            subject = "🎂 Birthday Today!"
 | 
			
		||||
        elif days_until == 1:
 | 
			
		||||
            subject = "🎂 Birthday Tomorrow!"
 | 
			
		||||
    # Build body (UK style dates)
 | 
			
		||||
    lines: list[str] = ["Upcoming birthdays (next 7 days):", ""]
 | 
			
		||||
    for delta in sorted(by_days.keys()):
 | 
			
		||||
        if delta == 0:
 | 
			
		||||
            lines.append("Today")
 | 
			
		||||
        elif delta == 1:
 | 
			
		||||
            lines.append("Tomorrow")
 | 
			
		||||
        else:
 | 
			
		||||
            subject = f"🎂 Birthday in {days_until} days"
 | 
			
		||||
            lines.append(f"In {delta} days")
 | 
			
		||||
 | 
			
		||||
        body_lines = ["Birthday Reminder:\n"]
 | 
			
		||||
        for birthday in birthdays:
 | 
			
		||||
            date_str = birthday.date.strftime("%A, %B %d, %Y")
 | 
			
		||||
            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"
 | 
			
		||||
        entries = sorted(
 | 
			
		||||
            by_days[delta],
 | 
			
		||||
            key=lambda e: (e.as_date, (e.title or e.name or "")),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        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():
 | 
			
		||||
            print(f"Birthday reminder: {subject}")
 | 
			
		||||
            print(body)
 | 
			
		||||
        lines.append("")
 | 
			
		||||
 | 
			
		||||
        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:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue