Skip to content

Commit 192b06b

Browse files
turianclaude
andcommitted
Fix jmap_request usage and rename token env var
Bug fixes: - Use jmap_request() instead of broken call.to_api() pattern - Fix find_drafts_mailbox_id, handle_email_draft, handle_email_draft_reply Token rename: - FASTMAIL_READONLY_API_TOKEN → FASTMAIL_API_TOKEN - Keep backwards compatibility with legacy env var - Draft commands need write scope, so "readonly" was misleading Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 50ae2f0 commit 192b06b

4 files changed

Lines changed: 22 additions & 40 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ uvx fastmail-cli help
3838
## Setup
3939

4040
```bash
41-
export FASTMAIL_READONLY_API_TOKEN="fmu1-..." # from Fastmail Settings → Integrations
41+
export FASTMAIL_API_TOKEN="fmu1-..." # from Fastmail Settings → Integrations
4242
```
4343

4444
## Usage

src/fastmail_cli/cli.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@
66
import sys
77

88
DEFAULT_HOST = "api.fastmail.com"
9-
DEFAULT_TOKEN_ENV = "FASTMAIL_READONLY_API_TOKEN"
9+
DEFAULT_TOKEN_ENV = "FASTMAIL_API_TOKEN"
10+
LEGACY_TOKEN_ENV = "FASTMAIL_READONLY_API_TOKEN"
1011

1112

1213
def main(argv=None) -> int:
1314
# Set default host if not provided
1415
os.environ.setdefault("JMAP_HOST", DEFAULT_HOST)
15-
# Promote FASTMAIL_READONLY_API_TOKEN to JMAP_API_TOKEN if not already set
16-
if "JMAP_API_TOKEN" not in os.environ and DEFAULT_TOKEN_ENV in os.environ:
17-
os.environ["JMAP_API_TOKEN"] = os.environ[DEFAULT_TOKEN_ENV]
16+
# Promote FASTMAIL_API_TOKEN to JMAP_API_TOKEN if not already set
17+
# Fall back to legacy FASTMAIL_READONLY_API_TOKEN for backwards compatibility
18+
if "JMAP_API_TOKEN" not in os.environ:
19+
if DEFAULT_TOKEN_ENV in os.environ:
20+
os.environ["JMAP_API_TOKEN"] = os.environ[DEFAULT_TOKEN_ENV]
21+
elif LEGACY_TOKEN_ENV in os.environ:
22+
os.environ["JMAP_API_TOKEN"] = os.environ[LEGACY_TOKEN_ENV]
1823
from .jmapc import main as jmap_main
1924
return jmap_main(argv)
2025

src/fastmail_cli/jmapc.py

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -260,16 +260,11 @@ def handle_email_get(args: argparse.Namespace) -> Tuple[int, Dict[str, Any]]:
260260
def find_drafts_mailbox_id(client: Client, account_id: str) -> str:
261261
"""Find the Drafts mailbox ID by querying for role=drafts."""
262262
call = MailboxQuery(filter={"role": "drafts"})
263-
using = ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"]
264-
resp = client.request(
265-
[call.to_api(account_id=account_id)],
266-
using=using,
267-
raise_errors=True,
268-
)
269-
mrs = resp.method_responses
270-
if not mrs or not mrs[0].response.ids:
263+
_, mrs = jmap_request(client, account_id, call, raise_errors=True)
264+
resp = mrs[0].response
265+
if not resp.ids:
271266
raise ValueError("Could not find Drafts mailbox")
272-
return mrs[0].response.ids[0]
267+
return resp.ids[0]
273268

274269

275270
def parse_email_addresses(val: Optional[str]) -> Optional[List[EmailAddress]]:
@@ -334,14 +329,7 @@ def handle_email_draft(args: argparse.Namespace) -> Tuple[int, Dict[str, Any]]:
334329

335330
# Create the draft using EmailSet
336331
call = EmailSet(create={"draft": email})
337-
using = ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"]
338-
resp = client.request(
339-
[call.to_api(account_id=account_id)],
340-
using=using,
341-
raise_errors=True,
342-
)
343-
344-
mrs = resp.method_responses
332+
using, mrs = jmap_request(client, account_id, call, raise_errors=True)
345333
set_response = mrs[0].response
346334

347335
capabilities_server = session_json.get("capabilities", {}).keys()
@@ -383,17 +371,12 @@ def handle_email_draft_reply(args: argparse.Namespace) -> Tuple[int, Dict[str, A
383371
# Fetch the original email
384372
original_props = ["from", "to", "cc", "subject", "messageId", "references", "replyTo"]
385373
call = EmailGet(ids=[args.id], properties=original_props)
386-
using = ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"]
387-
resp = client.request(
388-
[call.to_api(account_id=account_id)],
389-
using=using,
390-
raise_errors=True,
391-
)
392-
mrs = resp.method_responses
393-
if not mrs or not mrs[0].response.data:
374+
using, mrs = jmap_request(client, account_id, call, raise_errors=True)
375+
resp = mrs[0].response
376+
if not resp.data:
394377
raise ValueError(f"Email not found: {args.id}")
395378

396-
original = mrs[0].response.data[0]
379+
original = resp.data[0]
397380

398381
# Determine reply recipient (use reply-to if available, else from)
399382
if args.to:
@@ -467,13 +450,7 @@ def handle_email_draft_reply(args: argparse.Namespace) -> Tuple[int, Dict[str, A
467450

468451
# Create the draft using EmailSet
469452
call = EmailSet(create={"draft": email})
470-
resp = client.request(
471-
[call.to_api(account_id=account_id)],
472-
using=using,
473-
raise_errors=True,
474-
)
475-
476-
mrs = resp.method_responses
453+
using, mrs = jmap_request(client, account_id, call, raise_errors=True)
477454
set_response = mrs[0].response
478455

479456
capabilities_server = session_json.get("capabilities", {}).keys()
@@ -892,7 +869,7 @@ def COMMAND_SPECS() -> Dict[str, Dict[str, Any]]:
892869

893870
def add_connection_opts(p: argparse.ArgumentParser) -> None:
894871
p.add_argument("--host", default=env_default("JMAP_HOST", "api.fastmail.com"), help="JMAP host")
895-
p.add_argument("--api-token", default=env_default("JMAP_API_TOKEN", env_default("FASTMAIL_READONLY_API_TOKEN", None)), help="JMAP API token (read-only)")
872+
p.add_argument("--api-token", default=env_default("JMAP_API_TOKEN", env_default("FASTMAIL_API_TOKEN", env_default("FASTMAIL_READONLY_API_TOKEN", None))), help="JMAP API token")
896873
p.add_argument("--account", default=env_default("JMAP_ACCOUNT", "primary"), help="Account id or 'primary'")
897874
p.add_argument("--timeout", type=float, default=float(env_default("JMAP_TIMEOUT", "30")), help="HTTP timeout seconds")
898875
p.add_argument("--insecure", action="store_true", help="Skip TLS verification")

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)