Skip to content
Merged
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
3 changes: 3 additions & 0 deletions cmd/transactional.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,12 @@ func transactionalSendRunE(cmd *cobra.Command, args []string) error {
email, _ := cmd.Flags().GetString("email")
id, _ := cmd.Flags().GetString("id")
dataRaw, _ := cmd.Flags().GetString("data")
idempotencyKey, _ := cmd.Flags().GetString("idempotency-key")

req := api.SendTransactionalRequest{
Email: email,
TransactionalID: id,
IdempotencyKey: idempotencyKey,
}

if cmd.Flags().Changed("add-to-audience") {
Expand Down Expand Up @@ -155,6 +157,7 @@ func addTransactionalSendFlags(cmd *cobra.Command) {
cmd.Flags().BoolP("add-to-audience", "a", false, "Create a contact if one doesn't exist")
cmd.Flags().String("data", "", "Data variables as a JSON object")
cmd.Flags().StringArrayP("attachment", "A", nil, "Path to a file to attach (repeatable)")
cmd.Flags().String("idempotency-key", "", "Idempotency key to prevent duplicate sends")
cmd.MarkFlagRequired("email")
cmd.MarkFlagRequired("id")
}
Expand Down
4 changes: 4 additions & 0 deletions internal/api/transactional.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type SendTransactionalRequest struct {
AddToAudience *bool `json:"addToAudience,omitempty"`
DataVariables map[string]any `json:"dataVariables,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
IdempotencyKey string `json:"-"`
}

func (c *Client) SendTransactional(req SendTransactionalRequest) error {
Expand All @@ -39,6 +40,9 @@ func (c *Client) SendTransactional(req SendTransactionalRequest) error {
if err != nil {
return err
}
if req.IdempotencyKey != "" {
httpReq.Header.Set("Idempotency-Key", req.IdempotencyKey)
}

resp, err := c.do(httpReq)
if err != nil {
Expand Down
48 changes: 48 additions & 0 deletions internal/api/transactional_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,54 @@ func TestSendTransactional(t *testing.T) {
}
}

func TestSendTransactional_IdempotencyKey(t *testing.T) {
tests := []struct {
name string
idempotencyKey string
wantHeader string
}{
{
name: "sets header when provided",
idempotencyKey: "my-key-123",
wantHeader: "my-key-123",
},
{
name: "omits header when empty",
idempotencyKey: "",
wantHeader: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var gotHeader string
var body map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotHeader = r.Header.Get("Idempotency-Key")
b, _ := io.ReadAll(r.Body)
json.Unmarshal(b, &body)
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"success":true}`))
}))
defer server.Close()

client := NewClient(server.URL, "test-key")
client.SendTransactional(SendTransactionalRequest{
Email: "a@b.com",
TransactionalID: "abc",
IdempotencyKey: tt.idempotencyKey,
})

if gotHeader != tt.wantHeader {
t.Errorf("Idempotency-Key header = %q, want %q", gotHeader, tt.wantHeader)
}
if _, ok := body["idempotencyKey"]; ok {
t.Error("idempotencyKey should not appear in request body")
}
})
}
}

func TestSendTransactional_RequestBody(t *testing.T) {
addToAudience := true
tests := []struct {
Expand Down
Loading