feat: add email, scheduler, and Zulip integration services
- Add email service for sending meeting invites with ICS attachments - Add scheduler for background calendar sync jobs - Add Zulip service for meeting notifications - Make ics_url optional for participants - Add /api/schedule endpoint with 2-hour lead time validation - Update frontend to support scheduling flow Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
82
backend/src/app/email_service.py
Normal file
82
backend/src/app/email_service.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
import aiosmtplib
|
||||
from email.message import EmailMessage
|
||||
from icalendar import Calendar, Event, vCalAddress, vText
|
||||
from app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def send_meeting_invite(
|
||||
participants: list[dict],
|
||||
title: str,
|
||||
description: str,
|
||||
start_time: datetime,
|
||||
end_time: datetime,
|
||||
) -> bool:
|
||||
if not settings.smtp_host or not settings.smtp_user or not settings.smtp_password:
|
||||
logger.warning("SMTP credentials not configured. Skipping email invite.")
|
||||
return False
|
||||
|
||||
# Create the ICS content
|
||||
cal = Calendar()
|
||||
cal.add('prodid', '-//Common Availability//m.com//')
|
||||
cal.add('version', '2.0')
|
||||
cal.add('method', 'REQUEST')
|
||||
|
||||
event = Event()
|
||||
event.add('summary', title)
|
||||
event.add('dtstart', start_time)
|
||||
event.add('dtend', end_time)
|
||||
event.add('dtstamp', datetime.now(timezone.utc))
|
||||
event.add('description', description)
|
||||
event.add('uid', str(uuid.uuid4()))
|
||||
event.add('organizer', vCalAddress(f'MAILTO:{settings.smtp_user}'))
|
||||
|
||||
attendee_emails = []
|
||||
for p in participants:
|
||||
attendee = vCalAddress(f'MAILTO:{p["email"]}')
|
||||
attendee.params['cn'] = vText(p["name"])
|
||||
attendee.params['ROLE'] = vText('REQ-PARTICIPANT')
|
||||
event.add('attendee', attendee, encode=0)
|
||||
attendee_emails.append(p["email"])
|
||||
|
||||
cal.add_component(event)
|
||||
ics_data = cal.to_ical()
|
||||
|
||||
# Create Email
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = f"Invitation: {title}"
|
||||
msg["From"] = settings.smtp_user
|
||||
msg["To"] = ", ".join(attendee_emails)
|
||||
msg.set_content(
|
||||
f"You have been invited to: {title}\n"
|
||||
f"When: {start_time.strftime('%Y-%m-%d %H:%M %Z')}\n\n"
|
||||
f"{description}"
|
||||
)
|
||||
|
||||
# Attach ICS
|
||||
msg.add_attachment(
|
||||
ics_data,
|
||||
maintype="text",
|
||||
subtype="calendar",
|
||||
filename="invite.ics",
|
||||
params={"method": "REQUEST"}
|
||||
)
|
||||
|
||||
# Send
|
||||
try:
|
||||
await aiosmtplib.send(
|
||||
msg,
|
||||
hostname=settings.smtp_host,
|
||||
port=settings.smtp_port,
|
||||
username=settings.smtp_user,
|
||||
password=settings.smtp_password,
|
||||
start_tls=True
|
||||
)
|
||||
logger.info(f"Sent meeting invites to {len(attendee_emails)} participants")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send email invite: {e}")
|
||||
return False
|
||||
Reference in New Issue
Block a user