Skip to content

feat(email): implement complete Zeabur Email CLI commands (PLA-933)#200

Merged
yuaanlin merged 3 commits intomainfrom
bruce/pla-933-cli-complete-zeabur-email-z-send-commands
Mar 25, 2026
Merged

feat(email): implement complete Zeabur Email CLI commands (PLA-933)#200
yuaanlin merged 3 commits intomainfrom
bruce/pla-933-cli-complete-zeabur-email-z-send-commands

Conversation

@BruceDu521
Copy link
Contributor

@BruceDu521 BruceDu521 commented Mar 25, 2026

Summary

  • Add email send command supporting single / scheduled (--scheduled-at) / batch (--batch-file) sending, authenticated via --api-key or $ZSEND_API_KEY
  • Add email emails list/get for querying sending records with --status / --job-type / --job-id filters
  • Add email scheduled list/get/cancel for managing scheduled emails
  • Add email batch list/get for managing batch email jobs
  • Add email keys get and email webhooks get to fill existing gaps

Implementation notes

  • REST endpoints (send/scheduled/batch) require a zs_ prefixed Z-Send API key; a new pkg/api/zsend_rest.go implements a lightweight HTTP client for these
  • GraphQL-based commands (emails list/get, keys get, webhooks get) reuse the existing GraphQL client
  • email scheduled list and email batch list are intentional convenience aliases for email emails list --job-type scheduled/batch, but expose dedicated REST endpoints with type-specific fields (e.g. attempts, last_error for scheduled; job-level stats for batch)

Test plan

  • go build ./... passes
  • All --help outputs verified locally
  • Error paths verified: missing API key, invalid key format, empty/invalid batch file

Closes PLA-933

Made with Cursor

Summary by CodeRabbit

  • New Features
    • Send emails: immediate, scheduled, and batch (JSON or table output)
    • Manage batch email jobs: list and get job details
    • Manage scheduled emails: list, get details, and cancel
    • View email records with filtering and pagination (list, get)
    • Retrieve API key details
    • View webhook details
    • Reorganized email CLI with clear subcommands for above actions

Add send, emails, scheduled, batch commands and fill gaps in keys/webhooks:

- email send: single / scheduled (--scheduled-at) / batch (--batch-file),
  authenticated via --api-key or $ZSEND_API_KEY
- email emails list/get: query sending records with --status/--job-type/--job-id filters
- email scheduled list/get/cancel: manage scheduled emails (REST)
- email batch list/get: manage batch email jobs (REST)
- email keys get: retrieve API key details (GraphQL)
- email webhooks get: retrieve webhook details (GraphQL)

New packages:
- pkg/api/zsend_rest.go: REST HTTP client for Z-Send API endpoints
- pkg/model/zsend.go: REST request/reply types with Table display support

Made-with: Cursor
@coderabbitai
Copy link

coderabbitai bot commented Mar 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e57912b9-0ea0-47d0-8519-400f91803628

📥 Commits

Reviewing files that changed from the base of the PR and between da600d5 and c74c0c4.

📒 Files selected for processing (6)
  • internal/cmd/email/batch/get/get.go
  • internal/cmd/email/emails/get/get.go
  • internal/cmd/email/keys/get/get.go
  • internal/cmd/email/scheduled/cancel/cancel.go
  • internal/cmd/email/scheduled/get/get.go
  • internal/cmd/email/webhooks/get/get.go
✅ Files skipped from review due to trivial changes (1)
  • internal/cmd/email/scheduled/get/get.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/cmd/email/webhooks/get/get.go
  • internal/cmd/email/emails/get/get.go
  • internal/cmd/email/keys/get/get.go
  • internal/cmd/email/batch/get/get.go
  • internal/cmd/email/scheduled/cancel/cancel.go

Walkthrough

Adds Z-Send email features: CLI commands for send (immediate/scheduled/batch), emails list/get, scheduled email management, batch job management, API key/webhook retrieval, plus REST client implementations and new request/response models.

Changes

Cohort / File(s) Summary
CLI — Send
internal/cmd/email/send/send.go
New send command supporting immediate, scheduled, and batch sends; input validation, API key resolution, batch file JSON parsing, RFC3339 schedule handling, spinner-wrapped REST calls, JSON or formatted output. Attention: file I/O, JSON unmarshalling, scheduled time parsing.
CLI — Emails (records)
internal/cmd/email/emails/...
internal/cmd/email/emails/emails.go, internal/cmd/email/emails/list/list.go, internal/cmd/email/emails/get/get.go
New emails group with list (filters, pagination) and get commands; validates pagination/params, calls ApiClient, outputs JSON or table.
CLI — Scheduled emails
internal/cmd/email/scheduled/...
internal/cmd/email/scheduled/scheduled.go, .../list/list.go, .../get/get.go, .../cancel/cancel.go
New scheduled group with list/get/cancel; API key validation, interactive prompts and confirmation on cancel, spinner-wrapped API calls.
CLI — Batch jobs
internal/cmd/email/batch/...
internal/cmd/email/batch/batch.go, .../list/list.go, .../get/get.go
New batch group with list and get for batch email jobs; pagination/status filters, API key validation, JSON or table rendering.
CLI — Keys & Webhooks
internal/cmd/email/keys/..., internal/cmd/email/webhooks/...
internal/cmd/email/keys/get/get.go, internal/cmd/email/keys/keys.go, internal/cmd/email/webhooks/get/get.go, internal/cmd/email/webhooks/webhooks.go
Added get subcommands for API keys and webhooks; interactive prompting, spinner during fetch, JSON/table output.
CLI Root Wiring
internal/cmd/email/email.go
Registered new subcommands: send, emails, scheduled, and batch under the email root command.
API Interface
pkg/api/interface.go
Extended ZSendAPI interface with methods for send/schedule/batch, list/get emails, list/get/cancel scheduled emails, and list/get batch jobs.
REST Client Implementation
pkg/api/zsend_rest.go
New REST client and helper zsendDo (auth header, JSON body, 30s timeout, error decoding); implementations for all added ZSend endpoints. Attention: HTTP error decoding and timeouts.
Models
pkg/model/zsend.go
New request/response models for send/schedule/batch, attachment type, scheduled email and batch job models, plus ZSendScheduledEmails and ZSendBatchJobs Tabler wrappers for table rendering (recipient truncation and count formatting).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as CLI (Cobra)
    participant API as ApiClient
    participant ZServer as Z‑Send REST

    User->>CLI: invoke command (send/list/get/...)
    CLI->>CLI: validate flags / prompt (interactive)
    CLI->>API: call method (e.g., SendZSendEmail / ListZSendEmails)
    API->>ZServer: HTTP request to /api/v1/zsend/...
    ZServer-->>API: HTTP response (200 or error)
    API-->>CLI: parsed model or error
    CLI->>User: print JSON or formatted table / error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: implementing a complete set of Zeabur Email CLI commands, covering send, scheduled, batch, and related operations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bruce/pla-933-cli-complete-zeabur-email-z-send-commands

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
internal/cmd/email/batch/get/get.go (1)

32-41: Extract API-key resolution/validation into a shared helper.

Lines 33-41 duplicate logic that now exists in multiple new email commands. Centralizing this in cmdutil would reduce drift and keep validation/error messaging consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cmd/email/batch/get/get.go` around lines 32 - 41, Extract the
API-key resolution and validation logic from runGet into a shared helper in
cmdutil (e.g., func ResolveAPIKey(provided string) (string, error) or
GetValidatedAPIKey) that: 1) uses the provided value or falls back to
os.Getenv("ZSEND_API_KEY"), 2) returns the same error text when empty ("Z-Send
API key is required (--api-key or ZSEND_API_KEY)"), and 3) enforces the "zs_"
prefix with the same error ("invalid API key format: must start with zs_"); then
replace the duplicated block in runGet to call this new helper (passing apiKey)
and handle the returned key/error.
internal/cmd/email/send/send.go (1)

168-183: Consider validating individual batch entries locally.

The batch validation checks for empty arrays and the 100-item limit, but doesn't validate that each email entry has required fields (from, to, subject, html/text). While the API will reject invalid entries, local validation would provide faster feedback and clearer error messages.

🔧 Optional: Add local validation for batch entries
 	if len(emails) > 100 {
 		return fmt.Errorf("batch file contains %d emails; maximum is 100", len(emails))
 	}
+	for i, e := range emails {
+		if e.From == "" || len(e.To) == 0 || e.Subject == "" || (e.HTML == "" && e.Text == "") {
+			return fmt.Errorf("email at index %d is missing required fields (from, to, subject, html/text)", i)
+		}
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cmd/email/send/send.go` around lines 168 - 183, The runBatch
function reads and unmarshals a batch file into []model.ZSendSendEmailRequest
but only checks array length; add per-item validation to fail fast with clear
errors: iterate the emails slice after unmarshalling (in runBatch, using the
existing emails variable) and for each model.ZSendSendEmailRequest validate
presence of required fields (from, to, subject, and at least one of html or
text), collecting or returning the first descriptive error that includes the
item index and missing fields; ensure the error messages reference
opts.batchFile and keep the existing size checks intact.
pkg/api/zsend_rest.go (1)

39-43: Consider reusing the HTTP client instead of creating a new one per request.

Creating a new http.Client for each request prevents connection reuse and increases latency. The *client receiver likely already has an HTTP client that could be reused, or a package-level client could be shared.

♻️ Suggested refactor: Use a shared HTTP client
+var zsendHTTPClient = &http.Client{Timeout: 30 * time.Second}
+
 func zsendDo(ctx context.Context, apiKey, method, path string, body any) ([]byte, int, error) {
 	// ... body construction ...

-	c := &http.Client{Timeout: 30 * time.Second}
-	resp, err := c.Do(req)
+	resp, err := zsendHTTPClient.Do(req)
 	if err != nil {
 		return nil, 0, fmt.Errorf("do request: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/api/zsend_rest.go` around lines 39 - 43, The code creates a new
http.Client with `c := &http.Client{Timeout: 30 * time.Second}` and calls
`c.Do(req)`, preventing connection reuse; instead reuse an existing shared
client on the receiver (the `*client` type) or a package-level client: remove
the per-request `&http.Client{...}` allocation and call `Do(req)` on the
receiver's HTTP client field (or a singleton `httpClient`) that was initialized
once with the desired Timeout to enable connection reuse and keep the same
`Do(req)` call site.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/cmd/email/batch/list/list.go`:
- Around line 37-38: The runList function should validate pagination inputs
before calling the API: check opts.page is >= 1 and opts.perPage is between 1
and 100 (inclusive), and return a user-facing error (or exit) if values are out
of range; update the validation in the same function that handles listing
(runList) and reference opts.page and opts.perPage so invalid values like 0 or
1000 are rejected before the API call (do not rely on server-side validation).

In `@internal/cmd/email/emails/get/get.go`:
- Around line 15-20: Replace the strict argument check and add an interactive
fallback: change the cobra.ExactArgs(1) usage to allow 0 or 1 args (e.g.,
cobra.MaximumNArgs(1) or cobra.RangeArgs(0,1)) and modify the RunE handler (the
anonymous func and/or runGet) to detect when args length == 0, prompt the user
to select or enter an ID interactively, then call runGet(f, id) with the chosen
value; ensure any provided flag values still skip the prompt so non-interactive
usage is unchanged.

In `@internal/cmd/email/scheduled/cancel/cancel.go`:
- Around line 18-24: The cancel command currently calls runCancel directly and
performs a destructive action without user confirmation; update the Cobra
command in cancel.go so interactive runs prompt the user before proceeding and
only the non-interactive path skips the prompt. Concretely: change RunE to
detect interactive vs non-interactive (terminal/TTY or a --yes/--force flag)
and, for interactive runs, prompt for confirmation and only call the
cancellation when confirmed; for non-interactive runs call a new
runCancelNonInteractive (or similarly named) that performs the operation without
prompting. Keep the existing runCancel logic but refactor so there is a clear
interactive confirmation flow that references runCancel (or delegates to
runCancelNonInteractive) to execute the cancellation.

In `@pkg/api/zsend_rest.go`:
- Around line 127-137: The GetZSendScheduledEmail function (and other similar
functions constructing paths like "/emails/scheduled/"+id) currently
concatenates the id into the URL path unescaped; update these call sites (e.g.,
GetZSendScheduledEmail, and the other functions around lines 139-142 and 171-181
that build paths with id) to call url.PathEscape(id) before concatenation so
special characters are percent-encoded, ensuring correct routing and preventing
path injection issues.

In `@pkg/model/zsend.go`:
- Around line 304-453: The new REST DTOs in pkg/model (ZSendAttachment,
ZSendSendEmailRequest, ZSendSendEmailReply, ZSendScheduleEmailRequest,
ZSendScheduleEmailReply, ZSendBatchEmailRequest, ZSendBatchEmailReply,
ZSendScheduledEmail, ZSendListScheduledEmailsReply, ZSendScheduledEmails,
ZSendBatchJob, ZSendListBatchJobsReply, ZSendBatchJobs) violate the pkg/model
guideline; move these REST-specific structs out of pkg/model into a
REST-specific package/file (e.g., pkg/zsend or pkg/api/rest) or into an existing
REST DTO package, update any imports/usages to point to the new package, and
ensure no graphql struct tags are added in pkg/model; if you instead must keep
representations in pkg/model then convert them to use graphql:"fieldName" tags
and only include fields that exist in the backend GraphQL schema (matching
names/types) before merging.

---

Nitpick comments:
In `@internal/cmd/email/batch/get/get.go`:
- Around line 32-41: Extract the API-key resolution and validation logic from
runGet into a shared helper in cmdutil (e.g., func ResolveAPIKey(provided
string) (string, error) or GetValidatedAPIKey) that: 1) uses the provided value
or falls back to os.Getenv("ZSEND_API_KEY"), 2) returns the same error text when
empty ("Z-Send API key is required (--api-key or ZSEND_API_KEY)"), and 3)
enforces the "zs_" prefix with the same error ("invalid API key format: must
start with zs_"); then replace the duplicated block in runGet to call this new
helper (passing apiKey) and handle the returned key/error.

In `@internal/cmd/email/send/send.go`:
- Around line 168-183: The runBatch function reads and unmarshals a batch file
into []model.ZSendSendEmailRequest but only checks array length; add per-item
validation to fail fast with clear errors: iterate the emails slice after
unmarshalling (in runBatch, using the existing emails variable) and for each
model.ZSendSendEmailRequest validate presence of required fields (from, to,
subject, and at least one of html or text), collecting or returning the first
descriptive error that includes the item index and missing fields; ensure the
error messages reference opts.batchFile and keep the existing size checks
intact.

In `@pkg/api/zsend_rest.go`:
- Around line 39-43: The code creates a new http.Client with `c :=
&http.Client{Timeout: 30 * time.Second}` and calls `c.Do(req)`, preventing
connection reuse; instead reuse an existing shared client on the receiver (the
`*client` type) or a package-level client: remove the per-request
`&http.Client{...}` allocation and call `Do(req)` on the receiver's HTTP client
field (or a singleton `httpClient`) that was initialized once with the desired
Timeout to enable connection reuse and keep the same `Do(req)` call site.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b7d4aa9b-0555-4ee8-b19f-8cc7b1ce1e7d

📥 Commits

Reviewing files that changed from the base of the PR and between 4f1eb83 and ca7f8ce.

📒 Files selected for processing (19)
  • internal/cmd/email/batch/batch.go
  • internal/cmd/email/batch/get/get.go
  • internal/cmd/email/batch/list/list.go
  • internal/cmd/email/email.go
  • internal/cmd/email/emails/emails.go
  • internal/cmd/email/emails/get/get.go
  • internal/cmd/email/emails/list/list.go
  • internal/cmd/email/keys/get/get.go
  • internal/cmd/email/keys/keys.go
  • internal/cmd/email/scheduled/cancel/cancel.go
  • internal/cmd/email/scheduled/get/get.go
  • internal/cmd/email/scheduled/list/list.go
  • internal/cmd/email/scheduled/scheduled.go
  • internal/cmd/email/send/send.go
  • internal/cmd/email/webhooks/get/get.go
  • internal/cmd/email/webhooks/webhooks.go
  • pkg/api/interface.go
  • pkg/api/zsend_rest.go
  • pkg/model/zsend.go

- Switch all get/cancel commands from ExactArgs(1) to --id flag with
  interactive/non-interactive split (matching existing domain/key patterns)
- Add confirmation prompt to scheduled cancel in interactive mode
- Add pagination bounds validation to emails/scheduled/batch list commands
- Apply url.PathEscape to all REST ID path segments

Made-with: Cursor
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/cmd/email/batch/list/list.go (1)

36-36: Consider validating the --status flag value client-side.

The flag description lists valid values (pending/processing/completed/failed), but invalid values will pass through to the API. Client-side validation would provide faster, clearer feedback.

♻️ Proposed validation
 func runList(f *cmdutil.Factory, opts Options) error {
 	if opts.page < 1 {
 		return fmt.Errorf("page must be >= 1")
 	}
 	if opts.perPage < 1 || opts.perPage > 100 {
 		return fmt.Errorf("per-page must be between 1 and 100")
 	}
+	if opts.status != "" {
+		validStatuses := map[string]bool{"pending": true, "processing": true, "completed": true, "failed": true}
+		if !validStatuses[opts.status] {
+			return fmt.Errorf("invalid status %q: must be one of pending, processing, completed, failed", opts.status)
+		}
+	}
 
 	if opts.apiKey == "" {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cmd/email/batch/list/list.go` at line 36, The CLI accepts any value
for the --status flag (set via cmd.Flags().StringVar(&opts.status, ...)) but the
API only supports pending/processing/completed/failed; add client-side
validation in the command's execution path (e.g., in the command's RunE or
PreRunE handler) to check opts.status against the allowed set
{"pending","processing","completed","failed"} and return a user-facing error if
it's not one of those values so invalid inputs are rejected before calling the
API.
internal/cmd/email/emails/list/list.go (1)

78-86: Consider adding feedback for empty results.

When there are no emails, the table output will display only headers with no rows. Consider printing a message like "No email records found" to provide clearer feedback to the user.

💡 Proposed enhancement
 	if f.JSON {
 		if len(emails) == 0 {
 			return f.Printer.JSON([]any{})
 		}
 		return f.Printer.JSON(emails)
 	}

+	if len(emails) == 0 {
+		fmt.Fprintln(f.Out, "No email records found")
+		return nil
+	}
+
 	f.Printer.Table(emails.Header(), emails.Rows())
 	return nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cmd/email/emails/list/list.go` around lines 78 - 86, When f.JSON is
false and the emails slice is empty, the current code calls
f.Printer.Table(emails.Header(), emails.Rows()) which shows only headers; modify
the non-JSON branch to detect len(emails) == 0 and emit a clear user-facing
message (e.g. "No email records found") instead of or in addition to the empty
table. Update the logic around f.JSON, f.Printer.Table, emails.Header(), and
emails.Rows() to either print the message via the printer (e.g. an info/notice
method) or render a single-row table with that message so users get explicit
feedback when no emails are returned.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/cmd/email/emails/get/get.go`:
- Around line 20-26: The cobra Command for the "get" subcommand ignores
positional args (RunE uses runGet(f, opts) and drops args), so add an argument
validator by setting the Command's Args field to cobra.NoArgs on the
cobra.Command literal (the same struct that defines Use, Short, RunE) to cause
the command to fail fast on unexpected positional arguments; keep RunE calling
runGet(f, opts) unchanged.

In `@internal/cmd/email/webhooks/get/get.go`:
- Around line 49-57: paramCheck is currently allowing whitespace-only webhook
IDs; update validation to trim the webhook ID before checking for emptiness so
strings like "   " are rejected. Specifically, inside paramCheck (the function
used by runGetNonInteractive and the interactive path that calls getWebhook),
call strings.TrimSpace on the Options.WebhookID (or the field name used for the
webhook identifier) and validate the trimmed result, returning an error when
empty; keep callers (runGetNonInteractive, getWebhook caller) unchanged.

---

Nitpick comments:
In `@internal/cmd/email/batch/list/list.go`:
- Line 36: The CLI accepts any value for the --status flag (set via
cmd.Flags().StringVar(&opts.status, ...)) but the API only supports
pending/processing/completed/failed; add client-side validation in the command's
execution path (e.g., in the command's RunE or PreRunE handler) to check
opts.status against the allowed set
{"pending","processing","completed","failed"} and return a user-facing error if
it's not one of those values so invalid inputs are rejected before calling the
API.

In `@internal/cmd/email/emails/list/list.go`:
- Around line 78-86: When f.JSON is false and the emails slice is empty, the
current code calls f.Printer.Table(emails.Header(), emails.Rows()) which shows
only headers; modify the non-JSON branch to detect len(emails) == 0 and emit a
clear user-facing message (e.g. "No email records found") instead of or in
addition to the empty table. Update the logic around f.JSON, f.Printer.Table,
emails.Header(), and emails.Rows() to either print the message via the printer
(e.g. an info/notice method) or render a single-row table with that message so
users get explicit feedback when no emails are returned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb4aed53-280c-4379-8b24-6dc50a173810

📥 Commits

Reviewing files that changed from the base of the PR and between ca7f8ce and da600d5.

📒 Files selected for processing (10)
  • internal/cmd/email/batch/get/get.go
  • internal/cmd/email/batch/list/list.go
  • internal/cmd/email/emails/get/get.go
  • internal/cmd/email/emails/list/list.go
  • internal/cmd/email/keys/get/get.go
  • internal/cmd/email/scheduled/cancel/cancel.go
  • internal/cmd/email/scheduled/get/get.go
  • internal/cmd/email/scheduled/list/list.go
  • internal/cmd/email/webhooks/get/get.go
  • pkg/api/zsend_rest.go
✅ Files skipped from review due to trivial changes (1)
  • pkg/api/zsend_rest.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/cmd/email/batch/get/get.go
  • internal/cmd/email/keys/get/get.go
  • internal/cmd/email/scheduled/cancel/cancel.go
  • internal/cmd/email/scheduled/list/list.go

- Add cobra.NoArgs to all new get commands to reject unexpected positional args
- Apply strings.TrimSpace in all paramCheck functions to reject whitespace-only IDs
- Ignored REST DTO in pkg/model finding: existing models (service.go etc.)
  already use json tags extensively, no pure-graphql constraint exists

Made-with: Cursor
@yuaanlin yuaanlin merged commit 76b8768 into main Mar 25, 2026
5 checks passed
@yuaanlin yuaanlin deleted the bruce/pla-933-cli-complete-zeabur-email-z-send-commands branch March 25, 2026 16:25
@BruceDu521 BruceDu521 requested a review from brian890809 March 26, 2026 10:01
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