Repository navigation
[TW-8105] Add nylas deliverability commands for send events, tracking events and send alerts - #129
Conversation
… events and send alerts
Adds the 17 SendEvents, TrackingEvents and send alert routes to the CLI under
`nylas deliverability {send-events,tracking-events,send-alerts}`, grouped the
same way as the "Deliverability & Engagement" API reference section.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…irect client helper - Activity --start/--end accept a bare YYYY-MM-DD as a UTC day (--end is the day's last second); zoneless times are UTC; non-positive look-backs are rejected. - --threshold must be a plain JSON number, so values like .5 or NaN fail with an input error instead of a marshal error. - send-alerts delete prints no text under --json/--format yaml. - Call common.WithClientNoGrant directly instead of a package-local wrapper. - Reuse QueryBuilder.AddInt64, normalise nil MetricsParams once, fix demo event IDs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… demo consistency - A look-back --start/--end with --page-token now fails locally; page 1 prints a "Next page: --page-token ... --start <epoch> --end <epoch>" line to reuse. - --end without --start defaults start to UTC midnight 6 days before --end, so an older --end no longer gets "start must not be after end". - Activity times at or before 1970-01-01 are rejected instead of silently dropped. - --threshold matches the API literal (up to 9 integer digits, 2 decimals). - Demo send events come from one source, so list, show and message agree. - Tests capture fatih/color output, so the delete --json test can fail. - Docs: every zoneless activity time is UTC, including today/tomorrow/9am. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stderr page hint - Activity --start/--end accept only Unix seconds, YYYY-MM-DD, "YYYY-MM-DD HH:MM" (UTC), RFC3339 and look-backs. Keyword forms (today, tomorrow, 9am) moved between runs, broke --page-token and could land in the future. - --end older than the 90-day activity retention is rejected locally instead of returning an empty list. - With --json/--format yaml/--quiet, a look-back's "Next page:" line goes to stderr. - Docs list the accepted forms and match the next-page output. - Demo: bob gets an accepted event, the timeline is a static literal, and the demo test fails if a timeline ID is missing from the list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… paging, demo timeline
- All printed times are UTC ("2026-10-29 16:13:55 UTC"), and --start/--end accept
that form back, plus zoneless "YYYY-MM-DD HH:MM[:SS]" and "YYYY-MM-DDTHH:MM[:SS]".
- --quiet always writes the next-page line to stderr (it was missing for absolute
windows); send and tracking lists share one writeStructuredPage helper.
- Demo timeline is derived from the demo events by recipient and time; the demo
test checks order, current status and recipient.
- Test for an --end one hour inside the 90-day retention.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ception - --quiet no longer writes the next-page line to stderr, so pipelines that merge stderr get only IDs; scripts page with --json (next_cursor). After a look-back, --json/--format yaml still get the "Next page:" line on stderr. - State in code and docs that deliverability prints UTC, unlike the CLI's local common.Display* layouts, so printed times paste back into --start/--end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, alert flag clarity - Every deliverability command's help states it covers only Agent Account and Transactional send messages (not connected Google/Microsoft grants), matching the API reference; tracking help adds that tracking must be on. - show/message/update/delete explain where the ID comes from and have examples. - send-alerts create marks its required flags; update says unset flags keep their value; delete --yes says it is required. - --start help and parse hint list the YYYY-MM-DDTHH:MM[:SS] form. - Docs: scope statement in docs/commands/deliverability.md and COMMANDS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code review of PR #129: Claude and CodexReviewed at Verdict: no blockers. The design is solid and the tests are strong. One finding is worth fixing before merge (it is one of your known follow-ups, but it is worse than described). The rest are small fixes that could go in this PR or a follow-up. Build and tests
Should fix1. This is known follow-up #1, but it is more than a lost
Codex reproduced this against a fake API: Both event feeds are affected. The root cause is a local helper that disagrees with the shared output-format resolution. CLAUDE.md says to rely on Fix: decide quiet mode from the effective format the shared writer resolves, and add a regression test for Minor2. The next-page hint loses the window after page 1. (
Fix: whenever a cursor comes back, print the 3. A
Fix: reject it locally, the same way the 90-day 4.
Missing yaml tags are common across the codebase. But Fix: add 5. No test pins "POST is not retried". ( The PR makes this guarantee, but if Fix: add a test where httptest returns 503, then assert exactly one request and an error. 6. Rule IDs and message IDs are not validated, so Locations:
The risk is low: it is the user's own key and argument, and other commands have the same pattern. But this PR already validates event IDs, so rule IDs are the odd one out. Fix: reject Nits
Checked and correct
Nice work overall. The page-token-to-window binding and the scope statement in every command's help are well thought out. 🤖 Generated with Claude Code (Claude and Codex reviews) |
- isQuiet now uses the shared output-format resolution (--quiet, then --json, then --format), so --format quiet --json prints the JSON envelope instead of a bare array of rows. - Deliverability output types get yaml tags matching their json tags, and embedded structs are inlined, so --format yaml has the same keys and nesting as --json and the API (was eventtype, bouncedhard, nested sendcounts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the thorough review. Fixed in 7c562a5:
The remaining items (2, 3, 5, 6 and the nits) are tracked separately; I'll follow up on them. |
Summary
Adds
nylas deliverability, a new command group for the SendEvents, TrackingEvents and send alert API routes (17 routes). The subgroups match the "Deliverability & Engagement" section of the API reference (developer.nylas.com#797):nylas deliverability send-eventslist,show,message,summary,timeseries,breakdown/v3/activity/send,/v3/metrics/sendnylas deliverability tracking-eventslist,show,message,summary,timeseries,breakdown/v3/activity/tracking,/v3/metrics/trackingnylas deliverability send-alertslist,show,create,update,delete/v3/alerts/sendJira: TW-8105 (epic TW-6497).
Why
The send and tracking event APIs are live in prod in US and EU, but the CLI had no way to read them or to manage send alert rules.
What changed
internal/domain/deliverability_*.go,internal/ports/deliverability.go,internal/adapters/nylas/deliverability_*.go, plus mock and demo clients.internal/cli/deliverability/, registered incmd/nylas/main.go.common.WithClientNoGrant. No command takes a grant; a grant token gets the server's 403.list:--event-typeand per-feed filters.--start/--endaccept Unix seconds,YYYY-MM-DD(a whole UTC day),YYYY-MM-DD HH:MM[:SS]andYYYY-MM-DDTHH:MM[:SS](UTC), RFC3339, and look-backs (24h,7d).todayor9amare rejected, because they resolve differently on each run and break--page-token.--endalone defaults--startto 6 days before it. An--endolder than the 90-day retention is rejected locally; otherwise the server returns an empty list with no error.--limitand--page-token. The page token is bound to the exact start and end, so a look-back can't be combined with--page-token. After a look-back, the CLI printsNext page: --page-token … --start <epoch>for the next page.summary(health, totals, graded rate tiles, findings),timeseriesandbreakdown. A null rate prints—rather than0%.createsends--thresholdas a JSON number; it is checked against the API's literal format first.updatesends only the flags you set (PUT with partial semantics).deleteneeds--yes.--helpstates that it covers only Agent Account and Transactional send messages, not connected-grant (Google, Microsoft) sends. The wording matches the API reference. A test fails if a new subcommand leaves this out.show,message,updateanddeletesay where their ID comes from and have examples.send-alerts createmarks its required flags,updatesays unset flags keep their value, anddelete --yessays it is required.--format yamluses the same keys and nesting as--jsonand the API (yaml tags mirror the json tags; embedded structs are inlined). Quiet mode follows the CLI-wide precedence, so--json --format quietprints the JSON envelope.docs/commands/deliverability.md, plusdocs/COMMANDS.md,INDEX.md,ARCHITECTURE.mdandDEVELOPMENT.md. The deliverability page links to the developer docs guide, which ships in developer.nylas.com#797; the link 404s until that PR merges.Testing
httptest: exact query strings, request bodies, partial PUT, null rates, error envelopes.go test -short ./...and race tests on the new packages pass.make lintfails locally, because the local golangci-lint is built with Go 1.24. I ran the CI-pinned version instead (GOTOOLCHAIN=go1.26.5 go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.1 run): 0 issues.make ci's other steps (fmt, vet, unit, race, security, vuln, build) pass.internal/cli/integration/deliverability_test.go, API key only): the read commands, and an alert rule create → update → show → delete lifecycle. They pass.make ci-fullwas not run.send-events show --jsonis identical tocurlof the same route.summaryandlistwithconfig set region eu./code-review highrounds ran on the branch; the fixes are in the later commits.Notes
2026-10-29 16:13:55 UTCinstead of the CLI's usual localcommon.Display*formats. That matches the API's UTC windows and metrics days, and a printed time can be pasted back into--start/--end.--quietprints only IDs. To page from a script, use--jsonand readnext_cursor.UTCis accepted after any time value, including one with an RFC3339 offset.UTCis removed, not what the user typed.endvalue that was sent.UTCrepeats on every table row; aTIME (UTC)header would save width.🤖 Generated with Claude Code