Zimbra skill
Skill natema/zimbra-skill
Read, search, and draft email on any Zimbra mailbox (e.g. university/lab Zimbra, RENATER Partage, Inria, CNRS) via IMAP/SMTP using the dependency-free zmail.py CLI. Use when the user wants to check their Zimbra inbox, summarize or find messages, or prepare a reply as a draft they can review in webmail. Drafts are staged by default; sending is separate and requires explicit confirmation.From its SKILL.md
npx -y skills add natema/zimbra-skillAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.5 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Zimbra mail (zmail)
A stdlib-only Python CLI (zmail.py) over Zimbra IMAP/SMTP. Accounts and
credentials live in ~/.config/zimbra/config.toml (never in the repo).
Read the site notes first. If ~/.config/zimbra/NOTES.md exists, read it before
running account-specific commands — it records this machine's quirks (which account
"email" defaults to, username login forms, exact folder names like Archive vs
Archives, server capabilities such as missing IMAP MOVE). It's private and lives
outside the repo. Keep it updated when you discover a new quirk.
Safety posture: the default action STAGES a draft in the user's Drafts folder
(visible/editable/sendable from Zimbra webmail). Only run send after the user
explicitly asks to send, and it still requires --yes-really-send.
Setup check (do this first if anything fails)
python3 zmail.py accounts # lists configured accounts; proves config loads
If it errors about missing config, the user hasn't created
~/.config/zimbra/config.toml yet — point them to README.md / config.example.toml.
If IMAP login fails, the connection worked but credentials were rejected: check the
username form, or (if 2FA is on) use an application passcode generated in webmail
(Preferences > Accounts > Applications > Add Application Code).
Reading
# newest 20 in INBOX (• marks unread). Add --unread for only-unread.
python3 zmail.py -a work check --limit 20
python3 zmail.py -a work check --unread --json # JSON for parsing
python3 zmail.py -a work folders # list folders
python3 zmail.py -a work read 12345 # full message by UID
python3 zmail.py -a work read 12345 --json # structured (has message_id)
python3 zmail.py -a work search "grant deadline" --limit 10
check/search print a UID in [brackets]; feed that UID to read.
read --json returns message_id — pass it to --in-reply-to for threaded replies.
Drafting (default, safe)
python3 zmail.py -a work draft \
--to "[email protected]" \
--subject "Re: paper" \
--body-file /path/to/body.txt # or --body "short text"
# threaded reply:
python3 zmail.py -a work draft --to "[email protected]" --subject "Re: ..." \
--in-reply-to "<original-message-id>" --body-file body.txt
# with attachments (repeat --attach; MIME type guessed from extension):
python3 zmail.py -a work draft --to "[email protected]" --subject "Report" \
--body-file body.txt --attach report.pdf --attach data.csv
Replying to a message (preferred for replies)
reply <UID> auto-fills the recipient, Re: subject, and threading headers from the
original — no need to look up the Message-ID yourself. Stages a draft by default.
# find the UID with check/search, then:
python3 zmail.py -a work reply 12345 --body-file reply.txt # To: sender
python3 zmail.py -a work reply 12345 --body-file reply.txt --reply-all
python3 zmail.py -a work reply 12345 --body-file reply.txt --quote # include quoted original
python3 zmail.py -a work reply 12345 --folder Archive --body-file reply.txt
Use reply instead of draft --in-reply-to when responding to an existing message.
It defaults to a draft; only add --yes-really-send if the user explicitly asks to send.
For multi-line/accented bodies, write the body to a temp file and use --body-file
(avoids shell-quoting problems). Then tell the user the draft is in webmail Drafts.
Archiving
python3 zmail.py -a work archive 12345 # INBOX -> Archive
python3 zmail.py -a work archive 12345 --folder Archive --to "Archive/2026/work"
Moves a message between folders (uses IMAP MOVE if the server supports it, else COPY + delete + expunge). Not a destructive send/delete action, but still changes mailbox state — only archive a specific message the user pointed you at, not in bulk.
Deleting
python3 zmail.py -a work delete 12345 # move to Trash (recoverable)
python3 zmail.py -a work delete 12345 --folder Archive # delete from another folder
python3 zmail.py -a work delete 12345 --folder Trash --purge --yes-really-delete # permanent
Default delete just moves the message to Trash — recoverable from webmail, like
hitting delete there. Permanent removal needs BOTH --purge and --yes-really-delete;
without the confirmation flag it refuses. Only delete a specific message the user
pointed you at, never in bulk on your own. (Trash folder name defaults to Trash;
override with --trash if the site differs.)
Sending (only on explicit request)
python3 zmail.py -a work send --to "[email protected]" --subject "..." \
--body-file body.txt --yes-really-send
Never add --yes-really-send unless the user has clearly said to send. Prefer
draft and let the human hit send.
Conventions
- Replace
workwith the relevant account name; omit-ato use the config default. - Use
--jsonwhen you need to parse; plain text when showing the user. - Don't
--mark-readunless asked; reading is read-only by default. - UIDs are per-folder; the same message has different UIDs in different folders.
What ships with it: 7 files
1507.9 KB alongside SKILL.md, 2 of them executable
docs/
- demo.gif1462.9 KB
- make_demo.pyruns4.8 KB
- config.example.toml3.3 KB
- .gitignore108 B
- LICENSE1.0 KB
- README.md8.5 KB
- zmail.pyruns27.2 KB