Skip to content

Commit 117ed89

Browse files
committed
transactional-emails
1 parent ae88166 commit 117ed89

4 files changed

Lines changed: 922 additions & 5 deletions

File tree

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ client := loops.NewClient("YOUR_API_KEY",
5555
- Contact properties — `ListContactProperties`, `CreateContactProperty`
5656
- Mailing lists — `ListMailingLists`
5757
- Events — `SendEvent`
58-
- Transactional — `SendTransactional`, `ListTransactional`
58+
- Transactional — `SendTransactional`, `ListTransactionals`, `CreateTransactional`, `GetTransactional`, `UpdateTransactional`, `EnsureTransactionalDraft`, `PublishTransactional`
5959
- Email messages — `GetEmailMessage`, `UpdateEmailMessage`
6060
- Campaigns — `CreateCampaign`, `UpdateCampaign`, `GetCampaign`, `ListCampaigns`
6161
- Components — `GetComponent`, `ListComponents`
@@ -88,10 +88,10 @@ Requests are automatically retried with exponential backoff and jitter on `429`
8888

8989
## Pagination
9090

91-
`ListTransactional` and `ListCampaigns` return a single page of results along with a `*Pagination` value. Pass a `PaginationParams` to control page size and cursor:
91+
`ListTransactionals` and `ListCampaigns` return a single page of results along with a `*Pagination` value. Pass a `PaginationParams` to control page size and cursor:
9292

9393
```go
94-
items, page, err := client.ListTransactional(loops.PaginationParams{PerPage: "50"})
94+
items, page, err := client.ListTransactionals(loops.PaginationParams{PerPage: "50"})
9595
if err != nil {
9696
log.Fatal(err)
9797
}
@@ -101,8 +101,8 @@ if err != nil {
101101
To fetch every page, use the generic `Paginate` helper:
102102

103103
```go
104-
all, err := loops.Paginate(func(cursor string) ([]loops.TransactionalEmail, *loops.Pagination, error) {
105-
return client.ListTransactional(loops.PaginationParams{Cursor: cursor})
104+
all, err := loops.Paginate(func(cursor string) ([]loops.Transactional, *loops.Pagination, error) {
105+
return client.ListTransactionals(loops.PaginationParams{Cursor: cursor})
106106
})
107107
```
108108

transactional.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ func (c *Client) SendTransactional(req SendTransactionalRequest) error {
7777
// all, err := loops.Paginate(func(cursor string) ([]loops.TransactionalEmail, *loops.Pagination, error) {
7878
// return client.ListTransactional(loops.PaginationParams{Cursor: cursor})
7979
// })
80+
//
81+
// Deprecated: use [Client.ListTransactionals], which returns the richer
82+
// [Transactional] shape (draft and published email message IDs, separate
83+
// created/updated timestamps).
8084
func (c *Client) ListTransactional(params PaginationParams) ([]TransactionalEmail, *Pagination, error) {
8185
q := url.Values{}
8286
if params.PerPage != "" {

transactional_emails.go

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package loops
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"net/url"
9+
)
10+
11+
// Transactional describes a transactional email template managed via the
12+
// content API. A transactional email can have a draft and/or a published
13+
// [EmailMessage], linked via DraftEmailMessageID and PublishedEmailMessageID.
14+
type Transactional struct {
15+
ID string `json:"id"`
16+
Name string `json:"name"`
17+
DraftEmailMessageID *string `json:"draftEmailMessageId"`
18+
PublishedEmailMessageID *string `json:"publishedEmailMessageId"`
19+
CreatedAt string `json:"createdAt"`
20+
UpdatedAt string `json:"updatedAt"`
21+
DataVariables []string `json:"dataVariables"`
22+
}
23+
24+
// TransactionalDraft is returned by [Client.CreateTransactional] and
25+
// [Client.EnsureTransactionalDraft]. It embeds the [Transactional] and adds
26+
// DraftEmailMessageContentRevisionID, which should be passed as
27+
// ExpectedRevisionID on the first call to [Client.UpdateEmailMessage] for
28+
// optimistic concurrency control.
29+
type TransactionalDraft struct {
30+
Transactional
31+
DraftEmailMessageContentRevisionID *string `json:"draftEmailMessageContentRevisionId"`
32+
}
33+
34+
// CreateTransactionalRequest is the request body for [Client.CreateTransactional].
35+
type CreateTransactionalRequest struct {
36+
Name string `json:"name"`
37+
}
38+
39+
// UpdateTransactionalRequest is the request body for [Client.UpdateTransactional].
40+
type UpdateTransactionalRequest struct {
41+
Name string `json:"name"`
42+
}
43+
44+
// CreateTransactional creates a new transactional email with an empty draft
45+
// email message. Use [Client.UpdateEmailMessage] on the returned
46+
// DraftEmailMessageID to set the subject, sender, and LMX content, then call
47+
// [Client.PublishTransactional] to publish.
48+
func (c *Client) CreateTransactional(req CreateTransactionalRequest) (*TransactionalDraft, error) {
49+
b, err := json.Marshal(req)
50+
if err != nil {
51+
return nil, fmt.Errorf("failed to encode request: %w", err)
52+
}
53+
54+
httpReq, err := c.newRequest(http.MethodPost, "/transactional-emails", bytes.NewReader(b))
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
resp, err := c.do(httpReq)
60+
if err != nil {
61+
return nil, err
62+
}
63+
defer resp.Body.Close()
64+
65+
if resp.StatusCode != http.StatusCreated {
66+
return nil, errorFromResponse(resp)
67+
}
68+
69+
var result TransactionalDraft
70+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
71+
return nil, fmt.Errorf("failed to decode response: %w", err)
72+
}
73+
74+
return &result, nil
75+
}
76+
77+
// GetTransactional returns the transactional email identified by id.
78+
func (c *Client) GetTransactional(id string) (*Transactional, error) {
79+
req, err := c.newRequest(http.MethodGet, "/transactional-emails/"+id, nil)
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
resp, err := c.do(req)
85+
if err != nil {
86+
return nil, err
87+
}
88+
defer resp.Body.Close()
89+
90+
if resp.StatusCode != http.StatusOK {
91+
return nil, errorFromResponse(resp)
92+
}
93+
94+
var result Transactional
95+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
96+
return nil, fmt.Errorf("failed to decode response: %w", err)
97+
}
98+
99+
return &result, nil
100+
}
101+
102+
// UpdateTransactional updates the transactional email identified by id and
103+
// returns its new state.
104+
func (c *Client) UpdateTransactional(id string, req UpdateTransactionalRequest) (*Transactional, error) {
105+
b, err := json.Marshal(req)
106+
if err != nil {
107+
return nil, fmt.Errorf("failed to encode request: %w", err)
108+
}
109+
110+
httpReq, err := c.newRequest(http.MethodPost, "/transactional-emails/"+id, bytes.NewReader(b))
111+
if err != nil {
112+
return nil, err
113+
}
114+
115+
resp, err := c.do(httpReq)
116+
if err != nil {
117+
return nil, err
118+
}
119+
defer resp.Body.Close()
120+
121+
if resp.StatusCode != http.StatusOK {
122+
return nil, errorFromResponse(resp)
123+
}
124+
125+
var result Transactional
126+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
127+
return nil, fmt.Errorf("failed to decode response: %w", err)
128+
}
129+
130+
return &result, nil
131+
}
132+
133+
// EnsureTransactionalDraft ensures the transactional email has a draft email
134+
// message. If a draft already exists it is returned unchanged; otherwise a
135+
// new empty draft is created (seeded from the most recent published version
136+
// when present).
137+
func (c *Client) EnsureTransactionalDraft(id string) (*TransactionalDraft, error) {
138+
req, err := c.newRequest(http.MethodPost, "/transactional-emails/"+id+"/draft", nil)
139+
if err != nil {
140+
return nil, err
141+
}
142+
143+
resp, err := c.do(req)
144+
if err != nil {
145+
return nil, err
146+
}
147+
defer resp.Body.Close()
148+
149+
if resp.StatusCode != http.StatusOK {
150+
return nil, errorFromResponse(resp)
151+
}
152+
153+
var result TransactionalDraft
154+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
155+
return nil, fmt.Errorf("failed to decode response: %w", err)
156+
}
157+
158+
return &result, nil
159+
}
160+
161+
// PublishTransactional publishes the transactional email's current draft
162+
// email message. The draft becomes the published version and the draft is
163+
// cleared.
164+
func (c *Client) PublishTransactional(id string) (*Transactional, error) {
165+
req, err := c.newRequest(http.MethodPost, "/transactional-emails/"+id+"/publish", nil)
166+
if err != nil {
167+
return nil, err
168+
}
169+
170+
resp, err := c.do(req)
171+
if err != nil {
172+
return nil, err
173+
}
174+
defer resp.Body.Close()
175+
176+
if resp.StatusCode != http.StatusOK {
177+
return nil, errorFromResponse(resp)
178+
}
179+
180+
var result Transactional
181+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
182+
return nil, fmt.Errorf("failed to decode response: %w", err)
183+
}
184+
185+
return &result, nil
186+
}
187+
188+
// ListTransactionals returns a single page of transactional emails along
189+
// with pagination information. To iterate every page, use [Paginate].
190+
func (c *Client) ListTransactionals(params PaginationParams) ([]Transactional, *Pagination, error) {
191+
q := url.Values{}
192+
if params.PerPage != "" {
193+
q.Set("perPage", params.PerPage)
194+
}
195+
if params.Cursor != "" {
196+
q.Set("cursor", params.Cursor)
197+
}
198+
199+
path := "/transactional-emails"
200+
if len(q) > 0 {
201+
path += "?" + q.Encode()
202+
}
203+
204+
req, err := c.newRequest(http.MethodGet, path, nil)
205+
if err != nil {
206+
return nil, nil, err
207+
}
208+
209+
resp, err := c.do(req)
210+
if err != nil {
211+
return nil, nil, err
212+
}
213+
defer resp.Body.Close()
214+
215+
if resp.StatusCode != http.StatusOK {
216+
return nil, nil, errorFromResponse(resp)
217+
}
218+
219+
var result struct {
220+
Pagination Pagination `json:"pagination"`
221+
Data []Transactional `json:"data"`
222+
}
223+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
224+
return nil, nil, fmt.Errorf("failed to decode response: %w", err)
225+
}
226+
227+
return result.Data, &result.Pagination, nil
228+
}

0 commit comments

Comments
 (0)