Copy sent mail to Sent folder; add message field to Poke webhook
- send_email only relayed via SMTP, which doesn't copy the message to Sent the way a provider's own webmail/Mail app does — that's a client-side IMAP APPEND step that was simply missing, so sent mail never showed up anywhere in the account. Added detect_sent_folder (mirrors detect_drafts_folder) and append the sent copy after a successful SMTP send. - forward_to_poke's payload never included a top-level "message" field, which every documented /api/v1/inbound/api-message example uses to give the agent something actionable — without it the webhook could get accepted (200/success) but ingested as inert context with nothing to surface. Added _build_poke_message() to generate one from the email's from/subject/body. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -405,15 +405,53 @@ def detect_drafts_folder(client: IMAPClient) -> str:
|
||||
return "Drafts"
|
||||
|
||||
|
||||
def detect_sent_folder(client: IMAPClient) -> str:
|
||||
folders = client.list_folders()
|
||||
for flags, _delim, name in folders:
|
||||
if b"\\Sent" in flags:
|
||||
return name
|
||||
for name in ("Sent", "Sent Messages", "Sent Items", "[Gmail]/Sent Mail", "INBOX.Sent"):
|
||||
if client.folder_exists(name):
|
||||
return name
|
||||
return "Sent"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Poke webhook
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
_BODY_PREVIEW_LIMIT = 2000
|
||||
|
||||
|
||||
def _build_poke_message(email_data: dict, account: dict) -> str:
|
||||
"""Build the natural-language 'message' Poke's api-message endpoint expects.
|
||||
|
||||
Every documented usage example for /api/v1/inbound/api-message wraps its
|
||||
payload in a top-level 'message' string — the endpoint accepts "any JSON
|
||||
object", but without 'message' there's nothing for Poke's agent to treat
|
||||
as an actionable instruction, so it can ingest the payload as inert
|
||||
context without ever surfacing a heads-up.
|
||||
"""
|
||||
preview = (email_data.get("body_text") or "").strip()
|
||||
if not preview and email_data.get("body_html"):
|
||||
preview = "(HTML-only email — see body_html for content)"
|
||||
if len(preview) > _BODY_PREVIEW_LIMIT:
|
||||
preview = preview[:_BODY_PREVIEW_LIMIT] + "... (truncated, see body_text for full content)"
|
||||
|
||||
return (
|
||||
f"New email received on {account['from_address']}\n"
|
||||
f"From: {email_data['from']}\n"
|
||||
f"Subject: {email_data['subject'] or '(no subject)'}\n\n"
|
||||
f"{preview}"
|
||||
)
|
||||
|
||||
|
||||
async def forward_to_poke(
|
||||
email_data: dict, account: dict, webhook_url: str, api_key: str
|
||||
) -> bool:
|
||||
payload = {
|
||||
"message": _build_poke_message(email_data, account),
|
||||
"account_id": account["id"],
|
||||
"from_address": account["from_address"],
|
||||
"from": email_data["from"],
|
||||
@@ -1012,6 +1050,26 @@ async def send_email(
|
||||
smtp.login(acc["smtp_username"], acc["smtp_password"])
|
||||
smtp.sendmail(acc["smtp_username"], recipients, msg.as_string())
|
||||
|
||||
# SMTP relay doesn't copy the message to Sent the way a provider's
|
||||
# own webmail/Mail app does — that's a client-side step over IMAP,
|
||||
# so we have to do it ourselves or the message is never visible
|
||||
# anywhere in the account after sending.
|
||||
try:
|
||||
imap = get_imap_client(acc)
|
||||
try:
|
||||
sent_folder = detect_sent_folder(imap)
|
||||
if not imap.folder_exists(sent_folder):
|
||||
imap.create_folder(sent_folder)
|
||||
imap.append(sent_folder, msg.as_bytes(), flags=[b"\\Seen"])
|
||||
finally:
|
||||
imap.logout()
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Sent via SMTP but failed to save copy to Sent folder for '%s': %s",
|
||||
acc["id"],
|
||||
e,
|
||||
)
|
||||
|
||||
return {"success": True, "message_id": msg.get("Message-ID", "")}
|
||||
|
||||
return await asyncio.to_thread(_send)
|
||||
|
||||
Reference in New Issue
Block a user