/ restai / llms / tools / send_email.py
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()