_extract_body previously discarded real HTML content, replacing it with
a placeholder whenever a multipart email had no text/plain part. Now
falls back to the raw text/html payload before giving up, only using
the placeholder when a message has no text-bearing part at all.
poll_mailboxes also had no error isolation: an unhandled IMAP exception
from any one mailbox would stop the discord.ext.tasks.Loop permanently,
silently killing ingestion for all three mailboxes. Each mailbox's poll
is now wrapped in try/except so one failure doesn't block the others.
Separate display snippet from raw_content: use actual message.content
for raw_content (which may be empty string) and only use the placeholder
string for the display snippet. This matches the precedent from Task 5's
ping.py where build_ping_embed must never substitute raw_content with a
placeholder, preserving the factual state of whether content was present.
- Remove 'or (empty)' fallback in build_ping_embed to preserve empty strings in raw_content
- Fix parse_ping_embed to use direct indexing for all required fields (modality, raw_content, captured_at)
- Remove unused timezone import
- Add test case for empty raw_content round-trip to catch future regressions
Fixes code review findings: empty raw_content was being converted to literal "(empty)" string,
breaking lossless round-trip requirement needed by Task 7's thread-open handler.