選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. #
  2. # Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. import os
  17. import time
  18. from abc import ABC
  19. import json
  20. import smtplib
  21. import logging
  22. from email.mime.text import MIMEText
  23. from email.mime.multipart import MIMEMultipart
  24. from email.header import Header
  25. from email.utils import formataddr
  26. from agent.tools.base import ToolParamBase, ToolBase, ToolMeta
  27. from api.utils.api_utils import timeout
  28. class EmailParam(ToolParamBase):
  29. """
  30. Define the Email component parameters.
  31. """
  32. def __init__(self):
  33. self.meta:ToolMeta = {
  34. "name": "email",
  35. "description": "The email is a method of electronic communication for sending and receiving information through the Internet. This tool helps users to send emails to one person or to multiple recipients with support for CC, BCC, file attachments, and markdown-to-HTML conversion.",
  36. "parameters": {
  37. "to_email": {
  38. "type": "string",
  39. "description": "The target email address.",
  40. "default": "{sys.query}",
  41. "required": True
  42. },
  43. "cc_email": {
  44. "type": "string",
  45. "description": "The other email addresses needs to be send to. Comma splited.",
  46. "default": "",
  47. "required": False
  48. },
  49. "content": {
  50. "type": "string",
  51. "description": "The content of the email.",
  52. "default": "",
  53. "required": False
  54. },
  55. "subject": {
  56. "type": "string",
  57. "description": "The subject/title of the email.",
  58. "default": "",
  59. "required": False
  60. }
  61. }
  62. }
  63. super().__init__()
  64. # Fixed configuration parameters
  65. self.smtp_server = "" # SMTP server address
  66. self.smtp_port = 465 # SMTP port
  67. self.email = "" # Sender email
  68. self.password = "" # Email authorization code
  69. self.sender_name = "" # Sender name
  70. def check(self):
  71. # Check required parameters
  72. self.check_empty(self.smtp_server, "SMTP Server")
  73. self.check_empty(self.email, "Email")
  74. self.check_empty(self.password, "Password")
  75. self.check_empty(self.sender_name, "Sender Name")
  76. def get_input_form(self) -> dict[str, dict]:
  77. return {
  78. "to_email": {
  79. "name": "To ",
  80. "type": "line"
  81. },
  82. "subject": {
  83. "name": "Subject",
  84. "type": "line",
  85. "optional": True
  86. },
  87. "cc_email": {
  88. "name": "CC To",
  89. "type": "line",
  90. "optional": True
  91. },
  92. }
  93. class Email(ToolBase, ABC):
  94. component_name = "Email"
  95. @timeout(os.environ.get("COMPONENT_EXEC_TIMEOUT", 60))
  96. def _invoke(self, **kwargs):
  97. if not kwargs.get("to_email"):
  98. self.set_output("success", False)
  99. return ""
  100. last_e = ""
  101. for _ in range(self._param.max_retries+1):
  102. try:
  103. # Parse JSON string passed from upstream
  104. email_data = kwargs
  105. # Validate required fields
  106. if "to_email" not in email_data:
  107. return Email.be_output("Missing required field: to_email")
  108. # Create email object
  109. msg = MIMEMultipart('alternative')
  110. # Properly handle sender name encoding
  111. msg['From'] = formataddr((str(Header(self._param.sender_name,'utf-8')), self._param.email))
  112. msg['To'] = email_data["to_email"]
  113. if email_data.get("cc_email"):
  114. msg['Cc'] = email_data["cc_email"]
  115. msg['Subject'] = Header(email_data.get("subject", "No Subject"), 'utf-8').encode()
  116. # Use content from email_data or default content
  117. email_content = email_data.get("content", "No content provided")
  118. # msg.attach(MIMEText(email_content, 'plain', 'utf-8'))
  119. msg.attach(MIMEText(email_content, 'html', 'utf-8'))
  120. # Connect to SMTP server and send
  121. logging.info(f"Connecting to SMTP server {self._param.smtp_server}:{self._param.smtp_port}")
  122. context = smtplib.ssl.create_default_context()
  123. with smtplib.SMTP(self._param.smtp_server, self._param.smtp_port) as server:
  124. server.ehlo()
  125. server.starttls(context=context)
  126. server.ehlo()
  127. # Login
  128. logging.info(f"Attempting to login with email: {self._param.email}")
  129. server.login(self._param.email, self._param.password)
  130. # Get all recipient list
  131. recipients = [email_data["to_email"]]
  132. if email_data.get("cc_email"):
  133. recipients.extend(email_data["cc_email"].split(','))
  134. # Send email
  135. logging.info(f"Sending email to recipients: {recipients}")
  136. try:
  137. server.send_message(msg, self._param.email, recipients)
  138. success = True
  139. except Exception as e:
  140. logging.error(f"Error during send_message: {str(e)}")
  141. # Try alternative method
  142. server.sendmail(self._param.email, recipients, msg.as_string())
  143. success = True
  144. try:
  145. server.quit()
  146. except Exception as e:
  147. # Ignore errors when closing connection
  148. logging.warning(f"Non-fatal error during connection close: {str(e)}")
  149. self.set_output("success", success)
  150. return success
  151. except json.JSONDecodeError:
  152. error_msg = "Invalid JSON format in input"
  153. logging.error(error_msg)
  154. self.set_output("_ERROR", error_msg)
  155. self.set_output("success", False)
  156. return False
  157. except smtplib.SMTPAuthenticationError:
  158. error_msg = "SMTP Authentication failed. Please check your email and authorization code."
  159. logging.error(error_msg)
  160. self.set_output("_ERROR", error_msg)
  161. self.set_output("success", False)
  162. return False
  163. except smtplib.SMTPConnectError:
  164. error_msg = f"Failed to connect to SMTP server {self._param.smtp_server}:{self._param.smtp_port}"
  165. logging.error(error_msg)
  166. last_e = error_msg
  167. time.sleep(self._param.delay_after_error)
  168. except smtplib.SMTPException as e:
  169. error_msg = f"SMTP error occurred: {str(e)}"
  170. logging.error(error_msg)
  171. last_e = error_msg
  172. time.sleep(self._param.delay_after_error)
  173. except Exception as e:
  174. error_msg = f"Unexpected error: {str(e)}"
  175. logging.error(error_msg)
  176. self.set_output("_ERROR", error_msg)
  177. self.set_output("success", False)
  178. return False
  179. if last_e:
  180. self.set_output("_ERROR", str(last_e))
  181. return False
  182. assert False, self.output()
  183. def thoughts(self) -> str:
  184. inputs = self.get_input()
  185. return """
  186. To: {}
  187. Subject: {}
  188. Your email is on its way—sit tight!
  189. """.format(inputs.get("to_email", "-_-!"), inputs.get("subject", "-_-!"))