Add reply_email tool and sender override
All checks were successful
Build And Test / publish (push) Successful in 1m0s
All checks were successful
Build And Test / publish (push) Successful in 1m0s
This commit is contained in:
@@ -543,19 +543,26 @@ class EmailService:
|
||||
bcc: Optional[list[str]] = None,
|
||||
reply_to: Optional[str] = None,
|
||||
html_body: Optional[str] = None,
|
||||
sender_email: Optional[str] = None,
|
||||
sender_name: Optional[str] = None,
|
||||
in_reply_to: Optional[str] = None,
|
||||
references: Optional[list[str]] = None,
|
||||
) -> OperationResult:
|
||||
try:
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = formataddr(
|
||||
(self.settings.smtp_from_name or "", self.settings.smtp_from_email)
|
||||
)
|
||||
resolved_name, resolved_email = self._resolve_sender(sender_email, sender_name)
|
||||
msg["From"] = formataddr((resolved_name or "", resolved_email))
|
||||
msg["To"] = ", ".join(to)
|
||||
|
||||
if cc:
|
||||
msg["Cc"] = ", ".join(cc)
|
||||
if reply_to:
|
||||
msg["Reply-To"] = reply_to
|
||||
if in_reply_to:
|
||||
msg["In-Reply-To"] = in_reply_to
|
||||
if references:
|
||||
msg["References"] = " ".join(references)
|
||||
|
||||
# Add plain text body
|
||||
msg.attach(MIMEText(body, "plain", "utf-8"))
|
||||
@@ -589,6 +596,82 @@ class EmailService:
|
||||
except Exception as e:
|
||||
return OperationResult(success=False, message=str(e))
|
||||
|
||||
async def reply_email(
|
||||
self,
|
||||
mailbox: str,
|
||||
email_id: str,
|
||||
body: str,
|
||||
reply_all: bool = False,
|
||||
cc: Optional[list[str]] = None,
|
||||
bcc: Optional[list[str]] = None,
|
||||
reply_to: Optional[str] = None,
|
||||
html_body: Optional[str] = None,
|
||||
sender_email: Optional[str] = None,
|
||||
sender_name: Optional[str] = None,
|
||||
) -> OperationResult:
|
||||
original = self.read_email(mailbox, email_id, format="both")
|
||||
if not original:
|
||||
return OperationResult(
|
||||
success=False,
|
||||
message=f"Email {email_id} not found in {mailbox}",
|
||||
id=email_id,
|
||||
)
|
||||
|
||||
resolved_name, resolved_email = self._resolve_sender(sender_email, sender_name)
|
||||
reply_to_header = original.headers.get("Reply-To")
|
||||
reply_to_email = None
|
||||
if reply_to_header:
|
||||
_, reply_to_email = parseaddr(reply_to_header)
|
||||
if not reply_to_email:
|
||||
reply_to_email = original.from_address.email
|
||||
|
||||
to = [reply_to_email] if reply_to_email else []
|
||||
reply_cc: list[str] = []
|
||||
|
||||
if reply_all:
|
||||
for addr in original.to_addresses + original.cc_addresses:
|
||||
if addr.email and addr.email not in to:
|
||||
reply_cc.append(addr.email)
|
||||
|
||||
if cc:
|
||||
reply_cc.extend(cc)
|
||||
|
||||
to = self._dedupe_emails(to, resolved_email)
|
||||
reply_cc = self._dedupe_emails(reply_cc, resolved_email)
|
||||
|
||||
if not to and reply_cc:
|
||||
to = [reply_cc.pop(0)]
|
||||
|
||||
if not to:
|
||||
return OperationResult(
|
||||
success=False,
|
||||
message="No valid recipients found for reply",
|
||||
id=email_id,
|
||||
)
|
||||
|
||||
subject = original.subject or "(No Subject)"
|
||||
if not subject.lower().startswith("re:"):
|
||||
subject = f"Re: {subject}"
|
||||
|
||||
in_reply_to = original.headers.get("Message-ID") or original.in_reply_to
|
||||
references = list(original.references)
|
||||
if in_reply_to and in_reply_to not in references:
|
||||
references.append(in_reply_to)
|
||||
|
||||
return await self.send_email(
|
||||
to=to,
|
||||
subject=subject,
|
||||
body=body,
|
||||
cc=reply_cc or None,
|
||||
bcc=bcc,
|
||||
reply_to=reply_to,
|
||||
html_body=html_body,
|
||||
sender_email=resolved_email,
|
||||
sender_name=resolved_name,
|
||||
in_reply_to=in_reply_to,
|
||||
references=references or None,
|
||||
)
|
||||
|
||||
def _parse_envelope_addresses(self, addresses) -> list[EmailAddress]:
|
||||
if not addresses:
|
||||
return []
|
||||
@@ -744,6 +827,32 @@ class EmailService:
|
||||
msg.attach(MIMEText(html_body, "html", "utf-8"))
|
||||
return msg
|
||||
|
||||
def _resolve_sender(
|
||||
self, sender_email: Optional[str], sender_name: Optional[str]
|
||||
) -> tuple[Optional[str], str]:
|
||||
if sender_email:
|
||||
return sender_name, sender_email
|
||||
if sender_name:
|
||||
name, email_addr = parseaddr(sender_name)
|
||||
if email_addr:
|
||||
return name or None, email_addr
|
||||
return self.settings.smtp_from_name, self.settings.smtp_from_email
|
||||
|
||||
def _dedupe_emails(self, emails: list[str], self_email: Optional[str]) -> list[str]:
|
||||
seen = set()
|
||||
cleaned = []
|
||||
for addr in emails:
|
||||
if not addr:
|
||||
continue
|
||||
addr_lower = addr.lower()
|
||||
if self_email and addr_lower == self_email.lower():
|
||||
continue
|
||||
if addr_lower in seen:
|
||||
continue
|
||||
seen.add(addr_lower)
|
||||
cleaned.append(addr)
|
||||
return cleaned
|
||||
|
||||
def set_flags(
|
||||
self,
|
||||
email_id: str,
|
||||
|
||||
Reference in New Issue
Block a user