Spaces:
Sleeping
Sleeping
| """Email service for sending calendar invitations.""" | |
| import smtplib | |
| import uuid | |
| from datetime import datetime, timezone | |
| from email.mime.multipart import MIMEMultipart | |
| from email.mime.text import MIMEText | |
| from email.mime.base import MIMEBase | |
| from email import encoders | |
| from typing import Optional, Dict, Any | |
| from app.config import settings | |
| class EmailService: | |
| """Handles sending email invitations for calendar appointments.""" | |
| def __init__(self): | |
| self.smtp_server = settings.smtp_server | |
| self.smtp_port = settings.smtp_port | |
| self.username = settings.smtp_username or settings.my_email_address | |
| self.password = settings.smtp_password | |
| self.from_name = settings.email_from_name | |
| self.from_email = settings.my_email_address | |
| def create_calendar_invite(self, | |
| title: str, | |
| start_datetime: datetime, | |
| end_datetime: datetime, | |
| description: str = "", | |
| location: str = "", | |
| organizer_email: str = None, | |
| attendee_emails: list = None) -> str: | |
| """Create an iCal calendar invitation.""" | |
| if not organizer_email: | |
| organizer_email = self.from_email | |
| if not attendee_emails: | |
| attendee_emails = [] | |
| # Generate unique UID for the event | |
| uid = str(uuid.uuid4()) | |
| # Format datetime for iCal | |
| def format_dt(dt): | |
| return dt.strftime('%Y%m%dT%H%M%SZ') | |
| now = datetime.now(timezone.utc) | |
| ical_content = f"""BEGIN:VCALENDAR | |
| VERSION:2.0 | |
| PRODID:-//ChatCal.ai//Calendar Event//EN | |
| METHOD:REQUEST | |
| BEGIN:VEVENT | |
| UID:{uid} | |
| DTSTAMP:{format_dt(now)} | |
| DTSTART:{format_dt(start_datetime)} | |
| DTEND:{format_dt(end_datetime)} | |
| SUMMARY:{title} | |
| DESCRIPTION:{description} | |
| LOCATION:{location} | |
| ORGANIZER:MAILTO:{organizer_email}""" | |
| for attendee in attendee_emails: | |
| ical_content += f"\nATTENDEE:MAILTO:{attendee}" | |
| ical_content += f""" | |
| STATUS:CONFIRMED | |
| TRANSP:OPAQUE | |
| END:VEVENT | |
| END:VCALENDAR""" | |
| return ical_content | |
| def send_invitation_email(self, | |
| to_email: str, | |
| to_name: str, | |
| title: str, | |
| start_datetime: datetime, | |
| end_datetime: datetime, | |
| description: str = "", | |
| user_phone: str = "", | |
| meeting_type: str = "Meeting", | |
| meet_link: str = "") -> bool: | |
| """Send a calendar invitation email.""" | |
| try: | |
| # Create email message | |
| msg = MIMEMultipart('alternative') | |
| msg['Subject'] = f"Meeting Invitation: {title}" | |
| msg['From'] = f"{self.from_name} <{self.from_email}>" | |
| msg['To'] = f"{to_name} <{to_email}>" | |
| # Create email body | |
| # In testing mode, treat Peter's email as a regular user email for testing purposes | |
| is_peter_email = (to_email == settings.my_email_address and not settings.testing_mode) | |
| if is_peter_email: | |
| # Email to Peter | |
| html_body = self._create_peter_email_body( | |
| title, start_datetime, end_datetime, description, to_name, user_phone, meet_link | |
| ) | |
| else: | |
| # Email to user (or Peter's email in testing mode) | |
| html_body = self._create_user_email_body( | |
| title, start_datetime, end_datetime, description, meeting_type, meet_link | |
| ) | |
| # Create plain text version | |
| text_body = self._html_to_text(html_body) | |
| # Attach both versions | |
| part1 = MIMEText(text_body, 'plain') | |
| part2 = MIMEText(html_body, 'html') | |
| msg.attach(part1) | |
| msg.attach(part2) | |
| # Create and attach calendar invitation | |
| ical_content = self.create_calendar_invite( | |
| title=title, | |
| start_datetime=start_datetime, | |
| end_datetime=end_datetime, | |
| description=description, | |
| organizer_email=self.from_email, | |
| attendee_emails=[to_email, self.from_email] | |
| ) | |
| # Attach calendar file | |
| cal_attachment = MIMEBase('text', 'calendar') | |
| cal_attachment.set_payload(ical_content.encode('utf-8')) | |
| encoders.encode_base64(cal_attachment) | |
| cal_attachment.add_header('Content-Disposition', 'attachment; filename="meeting.ics"') | |
| cal_attachment.add_header('Content-Type', 'text/calendar; method=REQUEST; name="meeting.ics"') | |
| msg.attach(cal_attachment) | |
| # Send email | |
| if self.password: # Only send if SMTP credentials are configured | |
| print(f"π§ SMTP configured, sending email to {to_email}") | |
| with smtplib.SMTP(self.smtp_server, self.smtp_port) as server: | |
| server.starttls() | |
| server.login(self.username, self.password) | |
| server.send_message(msg) | |
| print(f"β Email invitation sent to {to_email}") | |
| return True | |
| else: | |
| print(f"β οΈ SMTP not configured (no password), skipping actual email send to {to_email}") | |
| print(f"π§ Email invitation prepared for {to_email} (SMTP not configured)") | |
| return True # Consider it successful for demo purposes | |
| except Exception as e: | |
| print(f"β Failed to send email to {to_email}: {e}") | |
| return False | |
| def send_cancellation_email(self, | |
| to_email: str, | |
| to_name: str, | |
| meeting_title: str, | |
| original_datetime: datetime) -> bool: | |
| """Send a meeting cancellation email.""" | |
| try: | |
| # Create email message | |
| msg = MIMEMultipart('alternative') | |
| msg['Subject'] = f"Meeting Cancelled: {meeting_title}" | |
| msg['From'] = f"{self.from_name} <{self.from_email}>" | |
| msg['To'] = f"{to_name} <{to_email}>" | |
| # Create email body | |
| formatted_time = original_datetime.strftime('%A, %B %d, %Y at %I:%M %p %Z') | |
| html_body = f""" | |
| <html> | |
| <body style="font-family: Arial, sans-serif; margin: 20px; color: #333;"> | |
| <h2 style="color: #f44336;">β Meeting Cancelled</h2> | |
| <div style="background: #ffebee; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #f44336;"> | |
| <h3 style="margin-top: 0; color: #c62828;">{meeting_title}</h3> | |
| <div style="margin: 15px 0;"> | |
| <strong>π Was scheduled for:</strong><br> | |
| {formatted_time} | |
| </div> | |
| <div style="margin: 15px 0;"> | |
| <strong>π€ With:</strong> Peter Michael Gits<br> | |
| <strong>π Peter's Phone:</strong> {settings.my_phone_number}<br> | |
| <strong>π§ Peter's Email:</strong> {settings.my_email_address} | |
| </div> | |
| </div> | |
| <div style="background: #e3f2fd; padding: 15px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #2196f3;"> | |
| <h4 style="margin-top: 0; color: #1565c0;">Need to reschedule?</h4> | |
| <p style="margin-bottom: 0; color: #1565c0;"> | |
| Feel free to book a new appointment through ChatCal.ai or contact Peter directly. | |
| </p> | |
| </div> | |
| <p style="color: #6c757d; font-size: 14px;"> | |
| This cancellation was processed through ChatCal.ai. | |
| </p> | |
| <hr style="border: none; border-top: 1px solid #dee2e6; margin: 20px 0;"> | |
| <p style="color: #6c757d; font-size: 12px;"> | |
| Sent by ChatCal.ai - Peter Michael Gits' AI Scheduling Assistant<br> | |
| Peter: {settings.my_phone_number} | {settings.my_email_address} | |
| </p> | |
| </body> | |
| </html> | |
| """ | |
| # Create plain text version | |
| text_body = f""" | |
| Meeting Cancelled: {meeting_title} | |
| Was scheduled for: {formatted_time} | |
| With: Peter Michael Gits | |
| Need to reschedule? Contact Peter: | |
| Phone: {settings.my_phone_number} | |
| Email: {settings.my_email_address} | |
| This cancellation was processed through ChatCal.ai. | |
| """.strip() | |
| # Attach both versions | |
| part1 = MIMEText(text_body, 'plain') | |
| part2 = MIMEText(html_body, 'html') | |
| msg.attach(part1) | |
| msg.attach(part2) | |
| # Send email | |
| if self.password: # Only send if SMTP credentials are configured | |
| print(f"π§ SMTP configured, sending cancellation email to {to_email}") | |
| with smtplib.SMTP(self.smtp_server, self.smtp_port) as server: | |
| server.starttls() | |
| server.login(self.username, self.password) | |
| server.send_message(msg) | |
| print(f"β Cancellation email sent to {to_email}") | |
| return True | |
| else: | |
| print(f"β οΈ SMTP not configured, skipping cancellation email to {to_email}") | |
| print(f"π§ Cancellation email prepared for {to_email} (SMTP not configured)") | |
| return True # Consider it successful for demo purposes | |
| except Exception as e: | |
| print(f"β Failed to send cancellation email to {to_email}: {e}") | |
| return False | |
| def _create_peter_email_body(self, title: str, start_datetime: datetime, | |
| end_datetime: datetime, description: str, | |
| user_name: str, user_phone: str, meet_link: str = "") -> str: | |
| """Create email body for Peter.""" | |
| return f""" | |
| <html> | |
| <body style="font-family: Arial, sans-serif; margin: 20px; color: #333;"> | |
| <h2 style="color: #2c3e50;">π New Meeting Scheduled</h2> | |
| <div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin: 20px 0;"> | |
| <h3 style="margin-top: 0; color: #495057;">{title}</h3> | |
| <div style="margin: 15px 0;"> | |
| <strong>π Date & Time:</strong><br> | |
| {start_datetime.strftime('%A, %B %d, %Y')}<br> | |
| {start_datetime.strftime('%I:%M %p')} - {end_datetime.strftime('%I:%M %p')} ({start_datetime.strftime('%Z')}) | |
| </div> | |
| <div style="margin: 15px 0;"> | |
| <strong>π€ Meeting with:</strong> {user_name}<br> | |
| <strong>π Phone:</strong> {user_phone or 'Not provided'} | |
| {f'<br><strong>π₯ Google Meet:</strong> <a href="{meet_link}" style="color: #1976d2;">{meet_link}</a>' if meet_link else ''} | |
| </div> | |
| {f'<div style="margin: 15px 0;"><strong>π Details:</strong><br>{description}</div>' if description else ''} | |
| </div> | |
| <p style="color: #6c757d; font-size: 14px;"> | |
| This meeting was scheduled through ChatCal.ai. The calendar invitation is attached to this email. | |
| </p> | |
| <hr style="border: none; border-top: 1px solid #dee2e6; margin: 20px 0;"> | |
| <p style="color: #6c757d; font-size: 12px;"> | |
| Sent by ChatCal.ai - Peter Michael Gits' AI Scheduling Assistant | |
| </p> | |
| </body> | |
| </html> | |
| """ | |
| def _create_user_email_body(self, title: str, start_datetime: datetime, | |
| end_datetime: datetime, description: str, | |
| meeting_type: str, meet_link: str = "") -> str: | |
| """Create email body for the user.""" | |
| return f""" | |
| <html> | |
| <body style="font-family: Arial, sans-serif; margin: 20px; color: #333;"> | |
| <h2 style="color: #2c3e50;">β Your Meeting with Peter Michael Gits is Confirmed!</h2> | |
| <div style="background: #e8f5e9; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #4caf50;"> | |
| <h3 style="margin-top: 0; color: #2e7d32;">{title}</h3> | |
| <div style="margin: 15px 0;"> | |
| <strong>π Date & Time:</strong><br> | |
| {start_datetime.strftime('%A, %B %d, %Y')}<br> | |
| {start_datetime.strftime('%I:%M %p')} - {end_datetime.strftime('%I:%M %p')} ({start_datetime.strftime('%Z')}) | |
| </div> | |
| <div style="margin: 15px 0;"> | |
| <strong>π€ Meeting with:</strong> Peter Michael Gits<br> | |
| <strong>π Peter's Phone:</strong> {settings.my_phone_number}<br> | |
| <strong>π§ Peter's Email:</strong> {settings.my_email_address} | |
| {f'<br><strong>π₯ Google Meet Link:</strong> <a href="{meet_link}" style="color: #1976d2; font-weight: bold;">{meet_link}</a>' if meet_link else ''} | |
| </div> | |
| {f'<div style="margin: 15px 0;"><strong>π Meeting Details:</strong><br>{description}</div>' if description else ''} | |
| </div> | |
| <div style="background: #fff3cd; padding: 15px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #ffc107;"> | |
| <h4 style="margin-top: 0; color: #856404;">π What's Next?</h4> | |
| <ul style="color: #856404; margin-bottom: 0;"> | |
| <li>Add this meeting to your calendar using the attached invitation file</li> | |
| <li>Peter will reach out via the contact method you provided</li> | |
| <li>If you need to reschedule, please contact Peter directly</li> | |
| </ul> | |
| </div> | |
| <p style="color: #6c757d; font-size: 14px;"> | |
| Looking forward to your meeting! The calendar invitation is attached to this email. | |
| </p> | |
| <hr style="border: none; border-top: 1px solid #dee2e6; margin: 20px 0;"> | |
| <p style="color: #6c757d; font-size: 12px;"> | |
| Sent by ChatCal.ai - Peter Michael Gits' AI Scheduling Assistant<br> | |
| Peter: {settings.my_phone_number} | {settings.my_email_address} | |
| </p> | |
| </body> | |
| </html> | |
| """ | |
| def _html_to_text(self, html: str) -> str: | |
| """Convert HTML email to plain text.""" | |
| import re | |
| # Remove HTML tags | |
| text = re.sub('<[^<]+?>', '', html) | |
| # Clean up whitespace | |
| text = re.sub(r'\n\s*\n', '\n\n', text) | |
| text = text.strip() | |
| return text | |
| # Global email service instance | |
| email_service = EmailService() |