""" Email Service 處理所有郵件相關功能,包括通知、提醒和摘要郵件 """ import os import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication from datetime import datetime, date from flask import current_app from jinja2 import Environment, FileSystemLoader, select_autoescape from utils.logger import get_logger from utils.ldap_utils import get_user_info logger = get_logger(__name__) class EmailService: """郵件服務類別""" def __init__(self): self.smtp_server = os.getenv('SMTP_SERVER') self.smtp_port = int(os.getenv('SMTP_PORT', 587)) self.use_tls = os.getenv('SMTP_USE_TLS', 'false').lower() == 'true' self.use_ssl = os.getenv('SMTP_USE_SSL', 'false').lower() == 'true' self.auth_required = os.getenv('SMTP_AUTH_REQUIRED', 'false').lower() == 'true' self.sender_email = os.getenv('SMTP_SENDER_EMAIL') self.sender_password = os.getenv('SMTP_SENDER_PASSWORD', '') # 設定 Jinja2 模板環境 template_dir = os.path.join(os.path.dirname(__file__), '..', 'templates', 'emails') self.jinja_env = Environment( loader=FileSystemLoader(template_dir), autoescape=select_autoescape(['html', 'xml']) ) def _create_smtp_connection(self): """建立 SMTP 連線""" try: if self.use_ssl: server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) else: server = smtplib.SMTP(self.smtp_server, self.smtp_port) if self.use_tls: server.starttls() if self.auth_required and self.sender_password: server.login(self.sender_email, self.sender_password) return server except Exception as e: logger.error(f"SMTP connection failed: {str(e)}") return None def _send_email(self, to_email, subject, html_content, text_content=None): """發送郵件的基礎方法""" try: if not self.smtp_server or not self.sender_email: logger.error("SMTP configuration incomplete") return False # 建立郵件 msg = MIMEMultipart('alternative') msg['From'] = self.sender_email msg['To'] = to_email msg['Subject'] = subject # 添加文本內容 if text_content: text_part = MIMEText(text_content, 'plain', 'utf-8') msg.attach(text_part) # 添加 HTML 內容 html_part = MIMEText(html_content, 'html', 'utf-8') msg.attach(html_part) # 發送郵件 server = self._create_smtp_connection() if not server: return False server.send_message(msg) server.quit() logger.info(f"Email sent successfully to {to_email}") return True except Exception as e: logger.error(f"Failed to send email to {to_email}: {str(e)}") return False def _get_user_email(self, ad_account): """取得使用者郵件地址""" user_info = get_user_info(ad_account) if user_info and user_info.get('email'): return user_info['email'] # 如果無法從 LDAP 取得,嘗試組合郵件地址 domain = os.getenv('LDAP_DOMAIN', 'panjit.com.tw') return f"{ad_account}@{domain}" def send_fire_email(self, todo, recipient, sender, custom_message=''): """發送緊急通知郵件""" try: recipient_email = self._get_user_email(recipient) sender_info = get_user_info(sender) sender_name = sender_info.get('displayName', sender) if sender_info else sender # 準備模板資料 template_data = { 'todo': todo, 'recipient': recipient, 'sender': sender, 'sender_name': sender_name, 'custom_message': custom_message, 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'app_name': current_app.config.get('APP_NAME', 'PANJIT Todo List') } # 渲染模板 template = self.jinja_env.get_template('fire_email.html') html_content = template.render(**template_data) # 主題 subject = f"🚨 緊急通知 - {todo.title}" return self._send_email(recipient_email, subject, html_content) except Exception as e: logger.error(f"Fire email failed for {recipient}: {str(e)}") return False def send_reminder_email(self, todo, recipient, reminder_type): """發送提醒郵件""" try: recipient_email = self._get_user_email(recipient) # 根據提醒類型設定主題和模板 if reminder_type == 'due_tomorrow': subject = f"📅 明日到期提醒 - {todo.title}" template_name = 'reminder_due_tomorrow.html' elif reminder_type == 'overdue': subject = f"⚠️ 逾期提醒 - {todo.title}" template_name = 'reminder_overdue.html' else: subject = f"📋 待辦提醒 - {todo.title}" template_name = 'reminder_general.html' # 準備模板資料 template_data = { 'todo': todo, 'recipient': recipient, 'reminder_type': reminder_type, 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'app_name': current_app.config.get('APP_NAME', 'PANJIT Todo List') } # 渲染模板 template = self.jinja_env.get_template(template_name) html_content = template.render(**template_data) return self._send_email(recipient_email, subject, html_content) except Exception as e: logger.error(f"Reminder email failed for {recipient}: {str(e)}") return False def send_digest_email(self, recipient, digest_data): """發送摘要郵件""" try: recipient_email = self._get_user_email(recipient) # 根據摘要類型設定主題 digest_type = digest_data.get('type', 'weekly') type_names = { 'daily': '每日', 'weekly': '每週', 'monthly': '每月' } subject = f"📊 {type_names.get(digest_type, '定期')}摘要報告" # 準備模板資料 template_data = { 'recipient': recipient, 'digest_data': digest_data, 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'app_name': current_app.config.get('APP_NAME', 'PANJIT Todo List') } # 渲染模板 template = self.jinja_env.get_template('digest.html') html_content = template.render(**template_data) return self._send_email(recipient_email, subject, html_content) except Exception as e: logger.error(f"Digest email failed for {recipient}: {str(e)}") return False def send_todo_notification(self, todo, recipients, action, actor): """發送待辦事項變更通知""" try: success_count = 0 for recipient in recipients: try: recipient_email = self._get_user_email(recipient) actor_info = get_user_info(actor) actor_name = actor_info.get('displayName', actor) if actor_info else actor # 根據動作類型設定主題和模板 action_names = { 'CREATE': '建立', 'UPDATE': '更新', 'DELETE': '刪除', 'ASSIGN': '指派', 'COMPLETE': '完成' } action_name = action_names.get(action, action) subject = f"📋 待辦事項{action_name} - {todo.title}" # 準備模板資料 template_data = { 'todo': todo, 'recipient': recipient, 'action': action, 'action_name': action_name, 'actor': actor, 'actor_name': actor_name, 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'app_name': current_app.config.get('APP_NAME', 'PANJIT Todo List') } # 渲染模板 template = self.jinja_env.get_template('todo_notification.html') html_content = template.render(**template_data) if self._send_email(recipient_email, subject, html_content): success_count += 1 except Exception as e: logger.error(f"Todo notification failed for {recipient}: {str(e)}") return success_count except Exception as e: logger.error(f"Todo notification batch failed: {str(e)}") return 0 def send_test_email(self, recipient): """發送測試郵件""" try: recipient_email = self._get_user_email(recipient) subject = "✅ 郵件服務測試" html_content = f"""
您好 {recipient},
這是一封測試郵件,用於驗證 PANJIT Todo List 系統的郵件功能是否正常運作。
如果您收到這封郵件,表示郵件服務配置正確。
測試時間: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
此郵件由系統自動發送,請勿回覆。
""" return self._send_email(recipient_email, subject, html_content) except Exception as e: logger.error(f"Test email failed for {recipient}: {str(e)}") return False def send_test_email_direct(self, recipient_email): """直接發送測試郵件到指定郵件地址""" try: subject = "✅ PANJIT Todo List 郵件服務測試" html_content = f"""您好!
這是一封來自 PANJIT Todo List 系統 的測試郵件,用於驗證郵件服務功能是否正常運作。
✅ 如果您收到這封郵件,表示:
測試詳細資訊:
📅 測試時間: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
📧 收件人: {recipient_email}
🏢 發件人: PANJIT Todo List 系統
此郵件由系統自動發送,請勿回覆。如有任何問題,請聯繫系統管理員。