first commit
This commit is contained in:
78
email/himalaya/references/python-email-fallback.md
Normal file
78
email/himalaya/references/python-email-fallback.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Python Email Fallback (when himalaya is not installed)
|
||||
|
||||
When `himalaya` CLI is unavailable, use Python's built-in `smtplib` and `imaplib` with credentials from `~/.hermes/config.yaml`.
|
||||
|
||||
## Sending Email
|
||||
|
||||
```python
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
smtp_host = "claw.163.com"
|
||||
sender = "ephronren@claw.163.com"
|
||||
password = "<from config>"
|
||||
recipient = "recipient@example.com"
|
||||
|
||||
msg = MIMEText("Body text", "plain", "utf-8")
|
||||
msg["From"] = sender
|
||||
msg["To"] = recipient
|
||||
msg["Subject"] = "Subject"
|
||||
|
||||
with smtplib.SMTP_SSL(smtp_host, 465) as server:
|
||||
server.login(sender, password)
|
||||
server.sendmail(sender, recipient, msg.as_string())
|
||||
```
|
||||
|
||||
## Reading Email
|
||||
|
||||
```python
|
||||
import imaplib
|
||||
from email import message_from_bytes
|
||||
from email.header import decode_header
|
||||
|
||||
imap_host = "claw.163.com"
|
||||
imap_port = 993
|
||||
user = "ephronren@claw.163.com"
|
||||
password = "<from config>"
|
||||
|
||||
with imaplib.IMAP4_SSL(imap_host, imap_port) as mail:
|
||||
mail.login(user, password)
|
||||
mail.select("INBOX")
|
||||
_, data = mail.search(None, "ALL")
|
||||
ids = data[0].split()
|
||||
eid = ids[-1] # latest
|
||||
|
||||
_, msg_data = mail.fetch(eid, "(RFC822)")
|
||||
msg = message_from_bytes(msg_data[0][1])
|
||||
|
||||
def decode_str(s):
|
||||
if s is None:
|
||||
return ""
|
||||
parts = decode_header(s)
|
||||
return " ".join(
|
||||
p.decode(c or "utf-8", errors="replace") if isinstance(p, bytes) else p
|
||||
for p, c in parts
|
||||
)
|
||||
|
||||
print(f"From: {decode_str(msg['From'])}")
|
||||
print(f"Subject: {decode_str(msg['Subject'])}")
|
||||
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
if part.get_content_type() == "text/plain":
|
||||
payload = part.get_payload(decode=True)
|
||||
charset = part.get_content_charset() or "utf-8"
|
||||
print(payload.decode(charset, errors="replace"))
|
||||
break
|
||||
else:
|
||||
payload = msg.get_payload(decode=True)
|
||||
charset = msg.get_content_charset() or "utf-8"
|
||||
print(payload.decode(charset, errors="replace"))
|
||||
```
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **Port 25 times out** — 163 SMTP blocks plain SMTP. Use `SMTP_SSL` with port **465** instead.
|
||||
- **Port 465 config mismatch** — config.yaml says port 25, but actual working port is 465.
|
||||
- **send_message tool limitation** — `send_message(action='send', target='email:addr@domain')` does NOT resolve external addresses. Must use Python/terminal directly.
|
||||
- **IMAP fetch auto-marks as read** — `mail.fetch(eid, "(RFC822)")` and even `fetch(eid, "(BODY[HEADER.FIELDS ...])")` automatically set the `\Seen` flag. If you need to check unread count after reading, the count will drop. Use `mail.search(None, "UNSEEN")` before fetching to get accurate unread count.
|
||||
Reference in New Issue
Block a user