| 13 | |
| 14 | |
| 15 | class EmailService: |
| 16 | def __init__( |
| 17 | self, |
| 18 | smtp_server: str, |
| 19 | smtp_port: int, |
| 20 | app_password: str, |
| 21 | from_email: str, |
| 22 | ) -> None: |
| 23 | self.smtp_server = smtp_server |
| 24 | self.smtp_port = smtp_port |
| 25 | self.from_email = from_email |
| 26 | self.app_password = app_password |
| 27 | |
| 28 | def send_email(self, message: EMessage) -> None: |
| 29 | msg = MIMEMultipart() |
| 30 | msg["From"] = self.from_email |
| 31 | msg["To"] = message.to if isinstance(message.to, str) else ", ".join(message.to) |
| 32 | msg["Subject"] = message.subject |
| 33 | if message.cc: |
| 34 | msg["Cc"] = ", ".join(message.cc) |
| 35 | |
| 36 | if message.bcc: |
| 37 | msg["Bcc"] = ", ".join(message.bcc) |
| 38 | |
| 39 | msg.attach(MIMEText(message.body, message.body_type)) |
| 40 | |
| 41 | if message.attachments: |
| 42 | for file_path in message.attachments: |
| 43 | file = Path(file_path) |
| 44 | if not file.exists(): |
| 45 | raise NotFoundException( |
| 46 | error_no=ErrorNo.EMAIL_ATTACHMENT_NOT_FOUND, message=f"File not found: {file_path}" |
| 47 | ) |
| 48 | msg.attach(EmailService._attach_file(file)) |
| 49 | |
| 50 | self._send_message(msg, message.to) |
| 51 | |
| 52 | @staticmethod |
| 53 | def _attach_file(file_path: Path) -> MIMEBase: |
| 54 | filename = file_path.name |
| 55 | ext = file_path.suffix.lower() |
| 56 | if ext in [".jpg", ".jpeg", ".png", ".gif", ".bmp"]: |
| 57 | with open(file_path, "rb") as f: |
| 58 | attachment = MIMEImage(f.read()) |
| 59 | attachment.add_header("Content-Disposition", f"attachment; filename= {filename}") |
| 60 | |
| 61 | elif ext == ".pdf": |
| 62 | with open(file_path, "rb") as f: |
| 63 | attachment = MIMEApplication(f.read(), _subtype="pdf") |
| 64 | attachment.add_header("Content-Disposition", f"attachment; filename= {filename}") |
| 65 | |
| 66 | elif ext in [".doc", ".docx"]: |
| 67 | with open(file_path, "rb") as f: |
| 68 | attachment = MIMEApplication(f.read(), _subtype="msword") |
| 69 | attachment.add_header("Content-Disposition", f"attachment; filename= {filename}") |
| 70 | |
| 71 | elif ext in [".xls", ".xlsx"]: |
| 72 | with open(file_path, "rb") as f: |
nothing calls this directly
no outgoing calls
no test coverage detected