Skip to content

feat: add --attach flag to send and reply commands - #3

Open
nitsanavni wants to merge 11 commits into
saadiq:mainfrom
nitsanavni:add-send-attachments
Open

feat: add --attach flag to send and reply commands#3
nitsanavni wants to merge 11 commits into
saadiq:mainfrom
nitsanavni:add-send-attachments

Conversation

@nitsanavni

Copy link
Copy Markdown

Summary

  • Add repeatable --attach flag to both send and reply commands via add_compose_args
  • New build_message helper in commands.py: when attachments are provided, builds a MIMEMultipart message with the text body and base64-encoded file parts; without attachments, returns plain MIMEText as before
  • Uses mimetypes.guess_type for content type detection, falls back to application/octet-stream

Test plan

  • gmail send --help shows --attach flag
  • gmail reply --help shows --attach flag
  • Created draft with attachment via --draft --attach /tmp/test-attachment.txt -- draft appeared in Gmail with attachment

🤖 Generated with Claude Code

nitsanavni and others added 5 commits February 27, 2026 12:36
Adds gmail archive <message-id>... which removes the INBOX label,
matching Gmail archive behavior. Adds gmail.modify scope to support this.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The archive branch swapped the HTML-to-markdown dependency from
html_to_markdown to markdownify. markdownify is declared in neither
pyproject.toml nor uv.lock, so `gmail read` raised ModuleNotFoundError
on any HTML email. The swap is also unrelated to the archive feature.

Claude-Session: https://claude.ai/code/session_01XN4T96G5R4UczQpgA2nqES
An invalid message ID raised HttpError out of the loop, so IDs before it
were already archived while IDs after it silently never ran. Report the
failure, continue the batch, and exit non-zero.

Claude-Session: https://claude.ai/code/session_01XN4T96G5R4UczQpgA2nqES
Adding gmail.modify invalidates every existing token, but the old token
still refreshes cleanly, so the API rejected archive with an opaque 403.
Credentials.from_authorized_user_info() overrides a token's scopes with
the ones passed in, so creds.scopes cannot detect this - read the granted
scopes from the token file and re-consent when any required scope is absent.

Claude-Session: https://claude.ai/code/session_01XN4T96G5R4UczQpgA2nqES
@saadiq saadiq mentioned this pull request Jul 14, 2026
3 tasks
nitsanavni and others added 6 commits July 13, 2026 23:47
Support sending file attachments via repeatable --attach flag.
When attachments are provided, builds a MIMEMultipart message
with the text body and base64-encoded file parts. Without
attachments, behavior is unchanged (plain MIMEText).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Attachment support pushed commands.py to 298 lines, leaving no room under the
project's 300-line limit for the error handling the feature still needs. Move
the message-building helpers into their own module. Pure move - behavior is
unchanged.

Claude-Session: https://claude.ai/code/session_01XN4T96G5R4UczQpgA2nqES
build_message() can now return a MIMEMultipart, but set_optional_recipients()
and encode_message() were still annotated as taking MIMEText, so every call
with an attachment was a type error. Add a Message alias and use it throughout.

Claude-Session: https://claude.ai/code/session_01XN4T96G5R4UczQpgA2nqES
A missing or unreadable --attach path raised FileNotFoundError out of
build_message(). Report every bad path with a clear message and exit 1, and do
it before authenticate() so a typo doesn't drag the user through OAuth first.

Also reject messages over Gmail's 25MB limit, which otherwise fail with an
opaque API error. The check measures the RFC822 message rather than the
base64url 'raw' field: attachments are already base64-encoded inside the
message, so 'raw' runs ~1.8x the original file and would reject legal sends
(a 17MB file is a 23MB message - fine - but a 30.6MB 'raw').

Claude-Session: https://claude.ai/code/session_01XN4T96G5R4UczQpgA2nqES
@saadiq
saadiq force-pushed the add-send-attachments branch from 1df4306 to f8a03de Compare July 14, 2026 03:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants