Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ mailtrap tracking-opt-outs create --email "no-tracking@example.com" --domain-id
mailtrap tracking-opt-outs delete --id "0198f1c4-0c0f-7a1c-8b0e-3f5d2a1b4c6d"

# Templates
mailtrap templates list
mailtrap templates list --per-page 20
mailtrap templates create --name "Welcome" --subject "Hello {{name}}" --body-html '<h1>Hi!</h1>'

# Webhooks
Expand Down Expand Up @@ -181,7 +181,7 @@ mailtrap domains list --output text
export MAILTRAP_OUTPUT=json
```

With `--output json`, commands print the API response as returned. Paginated lists (`inbound messages list`, `inbound threads list`, `email-logs list`, `email-campaigns list`, `tracking-opt-outs list`) print the full response object, so the next-page cursor and total count are available to scripts:
With `--output json`, commands print the API response as returned. Paginated lists (`inbound messages list`, `inbound threads list`, `email-logs list`, `email-campaigns list`, `templates list`, `tracking-opt-outs list`) print the full response object, so the next-page cursor and, where the API provides one, the total count are available to scripts:

```bash
mailtrap inbound messages list --inbox-id 735 -o json | jq -r '.last_id // empty'
Expand Down
9 changes: 5 additions & 4 deletions docs/TEST_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ These were discovered during integration testing and are important for correct i
| `GET /api_tokens` | `{"errors": "Access forbidden"}` | May require admin-level token |
| `GET /contacts` | 404 (HTML page) | Endpoint may not exist or requires different path |
| `GET /suppressions` | `[...]` | Flat array |
| `GET /email_templates` | `[...]` | Flat array |
| `GET /templates` | `{"data": [...], "pagination": {...}}` | Page-token pagination, no total |
| `GET /contacts/lists` | `[...]` | Flat array |
| `GET /contacts/fields` | `[...]` | Flat array |
| `GET /account_accesses` | `[...]` | Flat array |
Expand Down Expand Up @@ -120,14 +120,15 @@ Tests are organized by endpoint group. Each test specifies:
| # | Test | Command | Expected |
|---|------|---------|----------|
| 4.1 | List templates | `mailtrap templates list` | Table with template entries |
| 4.2 | List templates (JSON) | `mailtrap templates list --output json` | Valid JSON array |
| 4.2 | List templates (JSON) | `mailtrap templates list --output json` | Valid JSON object with `data` and `pagination` |
| 4.3 | Get template | `mailtrap templates get --id <TEMPLATE_ID>` | Single template details |
| 4.4 | Create template | `mailtrap templates create --name "test-tpl" --subject "Test" --text "body"` | New template in output |
| 4.4 | Create template | `mailtrap templates create --name "test-tpl" --subject "Test" --body-text "body"` | New template in output (flat request body, category defaults to `General`) |
| 4.5 | Update template | `mailtrap templates update --id <NEW_ID> --name "test-tpl-updated"` | Updated template |
| 4.6 | Delete template | `mailtrap templates delete --id <NEW_ID>` | Success message |
| 4.7 | Get missing ID | `mailtrap templates get` | Error: `--id is required` |
| 4.8 | Next page keeps page size | Create `test-tpl-a` and `test-tpl-b` as in 4.4, run `mailtrap templates list --per-page 1`, then run the `Next page:` hint | Hint is `--token 2 --per-page 1`; it prints one template that is not on the first page |

**Cleanup:** Delete created template.
**Cleanup:** Delete the templates created in 4.4 and 4.8.

## 5. Suppressions

Expand Down
25 changes: 11 additions & 14 deletions internal/commands/templates/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,35 +34,32 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command {
return err
}

path := cmdutil.AccountPath("email_templates")
path := cmdutil.AccountPath("templates")

body := map[string]interface{}{
"email_template": map[string]interface{}{
"name": opts.Name,
"subject": opts.Subject,
"body_html": opts.BodyHTML,
"body_text": opts.BodyText,
"category": opts.Category,
},
"name": opts.Name,
"subject": opts.Subject,
"body_html": opts.BodyHTML,
"body_text": opts.BodyText,
"category": opts.Category,
}

var result Template
if err := c.Post(context.Background(), client.BaseGeneral, path, body, &result); err != nil {
var resp templateResponse
if err := c.Post(context.Background(), client.BaseGeneral, path, body, &resp); err != nil {
return err
}

format := cmdutil.GetOutputFormat()
output.Print(f.IOStreams.Out, format, result, templateColumns)

return nil
return output.Print(f.IOStreams.Out, format, resp.Data, templateColumns)
},
}

cmd.Flags().StringVar(&opts.Name, "name", "", "Template name (required)")
cmd.Flags().StringVar(&opts.Subject, "subject", "", "Template subject (required)")
cmd.Flags().StringVar(&opts.BodyHTML, "body-html", "", "HTML body content")
cmd.Flags().StringVar(&opts.BodyText, "body-text", "", "Plain text body content")
cmd.Flags().StringVar(&opts.Category, "category", "", "Template category")
// The API requires a category, but the CLI does not make the user pick one.
cmd.Flags().StringVar(&opts.Category, "category", "General", "Template category")

_ = cmd.MarkFlagRequired("name")
_ = cmd.MarkFlagRequired("subject")
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/templates/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func NewCmdDelete(f *cmdutil.Factory) *cobra.Command {
return err
}

path := cmdutil.AccountPath("email_templates", fmt.Sprintf("%d", opts.ID))
path := cmdutil.AccountPath("templates", fmt.Sprintf("%d", opts.ID))

if err := c.Delete(context.Background(), client.BaseGeneral, path, nil); err != nil {
return err
Expand Down
10 changes: 4 additions & 6 deletions internal/commands/templates/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,15 @@ func NewCmdGet(f *cmdutil.Factory) *cobra.Command {
return err
}

path := cmdutil.AccountPath("email_templates", fmt.Sprintf("%d", opts.ID))
path := cmdutil.AccountPath("templates", fmt.Sprintf("%d", opts.ID))

var result Template
if err := c.Get(context.Background(), client.BaseGeneral, path, nil, &result); err != nil {
var resp templateResponse
if err := c.Get(context.Background(), client.BaseGeneral, path, nil, &resp); err != nil {
return err
}

format := cmdutil.GetOutputFormat()
output.Print(f.IOStreams.Out, format, result, templateColumns)

return nil
return output.Print(f.IOStreams.Out, format, resp.Data, templateColumns)
},
}

Expand Down
49 changes: 34 additions & 15 deletions internal/commands/templates/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package templates

import (
"context"
"encoding/json"
"fmt"
"net/url"

"github.com/mailtrap/mailtrap-cli/internal/client"
"github.com/mailtrap/mailtrap-cli/internal/cmdutil"
Expand All @@ -10,13 +13,9 @@ import (
"github.com/spf13/cobra"
)

type Template struct {
ID int `json:"id"`
UUID string `json:"uuid"`
Name string `json:"name"`
Subject string `json:"subject"`
Category string `json:"category"`
CreatedAt string `json:"created_at"`
// templateResponse keeps the template as raw JSON so --output json prints every field.
type templateResponse struct {
Data json.RawMessage `json:"data"`
}

var templateColumns = []output.Column{
Expand All @@ -28,10 +27,21 @@ var templateColumns = []output.Column{
{Header: "CREATED AT", Field: "created_at"},
}

var templatesPage = output.Page{
Items: "data",
Cursor: []string{"pagination", "next_token"},
CursorFlag: "token",
}

func NewCmdList(f *cmdutil.Factory) *cobra.Command {
var (
perPage int
token int
)

cmd := &cobra.Command{
Use: "list",
Short: "List all email templates",
Short: "List email templates, one page at a time",
RunE: func(cmd *cobra.Command, args []string) error {
c, err := f.NewClient()
if err != nil {
Expand All @@ -42,19 +52,28 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command {
return err
}

path := cmdutil.AccountPath("email_templates")
query := url.Values{}
page := templatesPage
if cmd.Flags().Changed("per-page") {
query.Set("per_page", fmt.Sprintf("%d", perPage))
// Without per_page the API reads the next token at its default page size.
page.NextArgs = fmt.Sprintf("--per-page %d", perPage)
}
if cmd.Flags().Changed("token") {
query.Set("token", fmt.Sprintf("%d", token))
}

var result []Template
if err := c.Get(context.Background(), client.BaseGeneral, path, nil, &result); err != nil {
var resp json.RawMessage
if err := c.Get(context.Background(), client.BaseGeneral, cmdutil.AccountPath("templates"), query, &resp); err != nil {
return err
}

format := cmdutil.GetOutputFormat()
output.Print(f.IOStreams.Out, format, result, templateColumns)

return nil
return output.PrintPage(f.IOStreams.Out, cmdutil.GetOutputFormat(), resp, page, templateColumns)
},
}

cmd.Flags().IntVar(&perPage, "per-page", 50, "Number of templates per page (max 100)")
cmd.Flags().IntVar(&token, "token", 0, "Page number to retrieve (page-token pagination)")

return cmd
}
7 changes: 7 additions & 0 deletions internal/commands/templates/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import (
"github.com/spf13/cobra"
)

const experimentalNote = "Uses the experimental /api/templates endpoints; their request and response shapes may change before general availability."

func NewCmdTemplates(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "templates",
Short: "Manage email templates",
Long: "Manage email templates.\n\n" + experimentalNote,
}

cmd.AddCommand(NewCmdList(f))
Expand All @@ -17,5 +20,9 @@ func NewCmdTemplates(f *cmdutil.Factory) *cobra.Command {
cmd.AddCommand(NewCmdUpdate(f))
cmd.AddCommand(NewCmdDelete(f))

for _, sub := range cmd.Commands() {
sub.Long = sub.Short + ".\n\n" + experimentalNote
}

return cmd
}
Loading
Loading