send_email.py
1 def send_email(subject: str, body: str, **kwargs) -> str: 2 """Send an email to the project's pre-configured recipient via SMTP. 3 4 The recipient address is fixed (``email_default_to`` on the project), 5 typically the admin's own inbox for proactive notifications. The 6 SMTP relay is also configured per-project (``smtp_host``, 7 ``smtp_port``, ``smtp_user``, ``smtp_password``, ``smtp_from``). 8 9 Use this when the agent needs to push a status update, alert, or 10 finished-job notification to a known email address. 11 12 Args: 13 subject (str): Email subject line. 14 body (str): Plain-text body of the email. 15 """ 16 import json 17 import smtplib 18 from email.message import EmailMessage 19 20 brain = kwargs.get("_brain") 21 project_id = kwargs.get("_project_id") 22 23 if not brain or project_id is None: 24 return "ERROR: send_email requires a project context — it can only be used by an agent project." 25 26 from restai.database import get_db_wrapper 27 from restai.utils.crypto import decrypt_field 28 29 db = get_db_wrapper() 30 try: 31 proj = db.get_project_by_id(int(project_id)) 32 if proj is None: 33 return f"ERROR: Project {project_id} not found." 34 35 try: 36 opts = json.loads(proj.options) if proj.options else {} 37 except Exception: 38 opts = {} 39 40 host = opts.get("smtp_host") or "" 41 port = int(opts.get("smtp_port") or 587) 42 user = opts.get("smtp_user") or "" 43 password = decrypt_field(opts.get("smtp_password") or "") 44 sender = opts.get("smtp_from") or user 45 to = opts.get("email_default_to") or "" 46 47 if not host or not sender: 48 return ( 49 "ERROR: Email is not configured for this project. " 50 "Set smtp_host and smtp_from in project edit → Integrations." 51 ) 52 if not to: 53 return ( 54 "ERROR: No default email recipient configured. " 55 "Set email_default_to in project options." 56 ) 57 58 msg = EmailMessage() 59 msg["Subject"] = subject or "(no subject)" 60 msg["From"] = sender 61 msg["To"] = to 62 msg.set_content(body or "") 63 64 try: 65 # Port 465 means implicit TLS; everything else assumes 66 # STARTTLS on a plaintext socket (the modern default that 67 # Gmail, SES, Mailgun, Postmark, etc. all support). 68 if port == 465: 69 with smtplib.SMTP_SSL(host, port, timeout=15) as s: 70 if user and password: 71 s.login(user, password) 72 s.send_message(msg) 73 else: 74 with smtplib.SMTP(host, port, timeout=15) as s: 75 s.ehlo() 76 try: 77 s.starttls() 78 s.ehlo() 79 except smtplib.SMTPNotSupportedError: 80 # Some local relays don't offer STARTTLS — fall 81 # through and send unencrypted. The admin chose 82 # this host on purpose. 83 pass 84 if user and password: 85 s.login(user, password) 86 s.send_message(msg) 87 except Exception as e: 88 return f"ERROR: SMTP send failed: {e}" 89 90 return f"OK: email sent to {to}." 91 finally: 92 db.db.close()