From 6146f648b54f4214cb24601bd14f7b7df3f7a52a Mon Sep 17 00:00:00 2001 From: Lorenc326 Date: Thu, 9 Jul 2026 12:24:44 +0300 Subject: [PATCH] Contact lists and contact fields resources --- README.md | 2 + client.go | 6 +++ contact_fields.go | 82 ++++++++++++++++++++++++++++ contact_fields_test.go | 94 +++++++++++++++++++++++++++++++++ contact_lists.go | 56 ++++++++++++++++++++ contact_lists_test.go | 84 +++++++++++++++++++++++++++++ examples/contact-fields/main.go | 48 +++++++++++++++++ examples/contact-lists/main.go | 40 ++++++++++++++ 8 files changed, 412 insertions(+) create mode 100644 contact_fields.go create mode 100644 contact_fields_test.go create mode 100644 contact_lists.go create mode 100644 contact_lists_test.go create mode 100644 examples/contact-fields/main.go create mode 100644 examples/contact-lists/main.go diff --git a/README.md b/README.md index a95ff9d..6b6b92f 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ Contact management: - Contacts — create, get, update (upsert) & delete by ID or email — [`examples/contacts`](examples/contacts) - Contact events — record custom events for a contact — [`examples/contact-events`](examples/contact-events) +- Contact lists — create, list, get, rename & delete — [`examples/contact-lists`](examples/contact-lists) +- Contact fields — manage custom fields (text, number, boolean, date) — [`examples/contact-fields`](examples/contact-fields) ## Errors diff --git a/client.go b/client.go index 14caa13..008dac6 100644 --- a/client.go +++ b/client.go @@ -90,6 +90,10 @@ type Client struct { Contacts *ContactsService // ContactEvents records custom events for contacts. ContactEvents *ContactEventsService + // ContactLists manages contact lists. + ContactLists *ContactListsService + // ContactFields manages custom contact fields. + ContactFields *ContactFieldsService } // Ptr returns a pointer to v, for setting optional pointer request fields such @@ -149,6 +153,8 @@ func NewClient(token string, opts ...Option) (*Client, error) { c.Permissions = &PermissionsService{client: c} c.Contacts = &ContactsService{client: c} c.ContactEvents = &ContactEventsService{client: c} + c.ContactLists = &ContactListsService{client: c} + c.ContactFields = &ContactFieldsService{client: c} return c, nil } diff --git a/contact_fields.go b/contact_fields.go new file mode 100644 index 0000000..43cc3ba --- /dev/null +++ b/contact_fields.go @@ -0,0 +1,82 @@ +package mailtrap + +import ( + "context" + "fmt" + "net/http" +) + +// ContactFieldsService manages custom contact fields. +type ContactFieldsService struct { + client *Client +} + +// Contact field data types for ContactField.DataType. +const ( + ContactFieldTypeText = "text" + ContactFieldTypeInteger = "integer" + ContactFieldTypeFloat = "float" + ContactFieldTypeBoolean = "boolean" + ContactFieldTypeDate = "date" +) + +// ContactField is a custom field that can be set on contacts. +type ContactField struct { + ID int64 `json:"id"` + Name string `json:"name"` + // DataType is the field's value type. + DataType string `json:"data_type"` + // MergeTag personalizes campaigns with the field's per-contact value. + MergeTag string `json:"merge_tag"` +} + +// CreateContactFieldRequest is the payload for creating a contact field. All +// fields are required. +type CreateContactFieldRequest struct { + Name string `json:"name"` + DataType string `json:"data_type"` + MergeTag string `json:"merge_tag"` +} + +// UpdateContactFieldRequest changes a contact field's name and merge tag; the +// data type is immutable. +type UpdateContactFieldRequest struct { + Name string `json:"name"` + MergeTag string `json:"merge_tag"` +} + +// List returns all contact fields. +func (s *ContactFieldsService) List(ctx context.Context) ([]*ContactField, *Response, error) { + var fields []*ContactField + resp, err := s.client.do(ctx, HostGeneral, http.MethodGet, "/api/contacts/fields", nil, nil, &fields) + return fields, resp, err +} + +// Get returns a contact field by ID. +func (s *ContactFieldsService) Get(ctx context.Context, fieldID int64) (*ContactField, *Response, error) { + path := fmt.Sprintf("/api/contacts/fields/%d", fieldID) + field := new(ContactField) + resp, err := s.client.do(ctx, HostGeneral, http.MethodGet, path, nil, nil, field) + return field, resp, err +} + +// Create adds a contact field. +func (s *ContactFieldsService) Create(ctx context.Context, req *CreateContactFieldRequest) (*ContactField, *Response, error) { + field := new(ContactField) + resp, err := s.client.do(ctx, HostGeneral, http.MethodPost, "/api/contacts/fields", nil, req, field) + return field, resp, err +} + +// Update changes a contact field's name and merge tag. +func (s *ContactFieldsService) Update(ctx context.Context, fieldID int64, req *UpdateContactFieldRequest) (*ContactField, *Response, error) { + path := fmt.Sprintf("/api/contacts/fields/%d", fieldID) + field := new(ContactField) + resp, err := s.client.do(ctx, HostGeneral, http.MethodPatch, path, nil, req, field) + return field, resp, err +} + +// Delete removes a contact field by ID. +func (s *ContactFieldsService) Delete(ctx context.Context, fieldID int64) (*Response, error) { + path := fmt.Sprintf("/api/contacts/fields/%d", fieldID) + return s.client.do(ctx, HostGeneral, http.MethodDelete, path, nil, nil, nil) +} diff --git a/contact_fields_test.go b/contact_fields_test.go new file mode 100644 index 0000000..118895f --- /dev/null +++ b/contact_fields_test.go @@ -0,0 +1,94 @@ +package mailtrap_test + +import ( + "context" + "net/http" + "testing" + + "github.com/mailtrap/mailtrap-go" +) + +func TestContactFields_List(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("GET /api/contacts/fields", func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(`[{"id":6730,"name":"First name","data_type":"text","merge_tag":"first_name"}]`)) + }) + + fields, _, err := client.ContactFields.List(context.Background()) + if err != nil { + t.Fatalf("List: %v", err) + } + if len(fields) != 1 || fields[0].DataType != mailtrap.ContactFieldTypeText { + t.Fatalf("fields = %+v", fields) + } +} + +func TestContactFields_Get(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("GET /api/contacts/fields/6730", func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(`{"id":6730,"name":"First name","data_type":"text","merge_tag":"first_name"}`)) + }) + + field, _, err := client.ContactFields.Get(context.Background(), 6730) + if err != nil { + t.Fatalf("Get: %v", err) + } + if field.MergeTag != "first_name" { + t.Errorf("field = %+v", field) + } +} + +func TestContactFields_Create(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("POST /api/contacts/fields", func(w http.ResponseWriter, r *http.Request) { + wantJSONBody(t, r, `{"name":"Age","data_type":"integer","merge_tag":"age"}`) + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte(`{"id":6731,"name":"Age","data_type":"integer","merge_tag":"age"}`)) + }) + + field, _, err := client.ContactFields.Create(context.Background(), &mailtrap.CreateContactFieldRequest{ + Name: "Age", + DataType: mailtrap.ContactFieldTypeInteger, + MergeTag: "age", + }) + if err != nil { + t.Fatalf("Create: %v", err) + } + if field.ID != 6731 || field.DataType != mailtrap.ContactFieldTypeInteger { + t.Errorf("field = %+v", field) + } +} + +func TestContactFields_Update(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("PATCH /api/contacts/fields/6731", func(w http.ResponseWriter, r *http.Request) { + wantJSONBody(t, r, `{"name":"Years","merge_tag":"years"}`) + _, _ = w.Write([]byte(`{"id":6731,"name":"Years","data_type":"integer","merge_tag":"years"}`)) + }) + + field, _, err := client.ContactFields.Update(context.Background(), 6731, &mailtrap.UpdateContactFieldRequest{ + Name: "Years", + MergeTag: "years", + }) + if err != nil { + t.Fatalf("Update: %v", err) + } + if field.Name != "Years" || field.MergeTag != "years" { + t.Errorf("field = %+v", field) + } +} + +func TestContactFields_Delete(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("DELETE /api/contacts/fields/6731", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }) + + resp, err := client.ContactFields.Delete(context.Background(), 6731) + if err != nil { + t.Fatalf("Delete: %v", err) + } + if resp.StatusCode != http.StatusNoContent { + t.Errorf("status = %d", resp.StatusCode) + } +} diff --git a/contact_lists.go b/contact_lists.go new file mode 100644 index 0000000..47a5128 --- /dev/null +++ b/contact_lists.go @@ -0,0 +1,56 @@ +package mailtrap + +import ( + "context" + "fmt" + "net/http" +) + +// ContactListsService manages contact lists. +type ContactListsService struct { + client *Client +} + +// ContactList is a named list that groups contacts. +type ContactList struct { + ID int64 `json:"id"` + Name string `json:"name"` +} + +// List returns all contact lists. +func (s *ContactListsService) List(ctx context.Context) ([]*ContactList, *Response, error) { + var lists []*ContactList + resp, err := s.client.do(ctx, HostGeneral, http.MethodGet, "/api/contacts/lists", nil, nil, &lists) + return lists, resp, err +} + +// Get returns a contact list by ID. +func (s *ContactListsService) Get(ctx context.Context, listID int64) (*ContactList, *Response, error) { + path := fmt.Sprintf("/api/contacts/lists/%d", listID) + list := new(ContactList) + resp, err := s.client.do(ctx, HostGeneral, http.MethodGet, path, nil, nil, list) + return list, resp, err +} + +// Create adds a contact list with the given name. +func (s *ContactListsService) Create(ctx context.Context, name string) (*ContactList, *Response, error) { + body := map[string]string{"name": name} + list := new(ContactList) + resp, err := s.client.do(ctx, HostGeneral, http.MethodPost, "/api/contacts/lists", nil, body, list) + return list, resp, err +} + +// Update renames a contact list. +func (s *ContactListsService) Update(ctx context.Context, listID int64, name string) (*ContactList, *Response, error) { + path := fmt.Sprintf("/api/contacts/lists/%d", listID) + body := map[string]string{"name": name} + list := new(ContactList) + resp, err := s.client.do(ctx, HostGeneral, http.MethodPatch, path, nil, body, list) + return list, resp, err +} + +// Delete removes a contact list by ID. +func (s *ContactListsService) Delete(ctx context.Context, listID int64) (*Response, error) { + path := fmt.Sprintf("/api/contacts/lists/%d", listID) + return s.client.do(ctx, HostGeneral, http.MethodDelete, path, nil, nil, nil) +} diff --git a/contact_lists_test.go b/contact_lists_test.go new file mode 100644 index 0000000..b78a1ac --- /dev/null +++ b/contact_lists_test.go @@ -0,0 +1,84 @@ +package mailtrap_test + +import ( + "context" + "net/http" + "testing" +) + +func TestContactLists_List(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("GET /api/contacts/lists", func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(`[{"id":26730,"name":"Customers"},{"id":26731,"name":"Old Contacts"}]`)) + }) + + lists, _, err := client.ContactLists.List(context.Background()) + if err != nil { + t.Fatalf("List: %v", err) + } + if len(lists) != 2 || lists[0].ID != 26730 || lists[0].Name != "Customers" { + t.Fatalf("lists = %+v", lists) + } +} + +func TestContactLists_Get(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("GET /api/contacts/lists/26730", func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(`{"id":26730,"name":"Customers"}`)) + }) + + list, _, err := client.ContactLists.Get(context.Background(), 26730) + if err != nil { + t.Fatalf("Get: %v", err) + } + if list.Name != "Customers" { + t.Errorf("list = %+v", list) + } +} + +func TestContactLists_Create(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("POST /api/contacts/lists", func(w http.ResponseWriter, r *http.Request) { + wantJSONBody(t, r, `{"name":"Customers"}`) + _, _ = w.Write([]byte(`{"id":26730,"name":"Customers"}`)) + }) + + list, _, err := client.ContactLists.Create(context.Background(), "Customers") + if err != nil { + t.Fatalf("Create: %v", err) + } + if list.ID != 26730 { + t.Errorf("list = %+v", list) + } +} + +func TestContactLists_Update(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("PATCH /api/contacts/lists/26730", func(w http.ResponseWriter, r *http.Request) { + wantJSONBody(t, r, `{"name":"Former Customers"}`) + _, _ = w.Write([]byte(`{"id":26730,"name":"Former Customers"}`)) + }) + + list, _, err := client.ContactLists.Update(context.Background(), 26730, "Former Customers") + if err != nil { + t.Fatalf("Update: %v", err) + } + if list.Name != "Former Customers" { + t.Errorf("list = %+v", list) + } +} + +func TestContactLists_Delete(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("DELETE /api/contacts/lists/26730", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }) + + resp, err := client.ContactLists.Delete(context.Background(), 26730) + if err != nil { + t.Fatalf("Delete: %v", err) + } + if resp.StatusCode != http.StatusNoContent { + t.Errorf("status = %d", resp.StatusCode) + } +} diff --git a/examples/contact-fields/main.go b/examples/contact-fields/main.go new file mode 100644 index 0000000..11e134d --- /dev/null +++ b/examples/contact-fields/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/mailtrap/mailtrap-go" +) + +func main() { + client, err := mailtrap.NewClient(os.Getenv("MAILTRAP_API_TOKEN")) + if err != nil { + log.Fatal(err) + } + + ctx := context.Background() + + field, _, err := client.ContactFields.Create(ctx, &mailtrap.CreateContactFieldRequest{ + Name: "Age", + DataType: mailtrap.ContactFieldTypeInteger, + MergeTag: "age", + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("created field %d (%s, %s)\n", field.ID, field.Name, field.DataType) + + fields, _, err := client.ContactFields.List(ctx) + if err != nil { + log.Fatal(err) + } + fmt.Printf("account has %d field(s)\n", len(fields)) + + // The data type is immutable; only name and merge tag can change. + if _, _, err = client.ContactFields.Update(ctx, field.ID, &mailtrap.UpdateContactFieldRequest{ + Name: "Years", + MergeTag: "years", + }); err != nil { + log.Fatal(err) + } + + if _, err = client.ContactFields.Delete(ctx, field.ID); err != nil { + log.Fatal(err) + } + fmt.Println("deleted field") +} diff --git a/examples/contact-lists/main.go b/examples/contact-lists/main.go new file mode 100644 index 0000000..1047192 --- /dev/null +++ b/examples/contact-lists/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/mailtrap/mailtrap-go" +) + +func main() { + client, err := mailtrap.NewClient(os.Getenv("MAILTRAP_API_TOKEN")) + if err != nil { + log.Fatal(err) + } + + ctx := context.Background() + + list, _, err := client.ContactLists.Create(ctx, "Customers") + if err != nil { + log.Fatal(err) + } + fmt.Printf("created list %d (%s)\n", list.ID, list.Name) + + lists, _, err := client.ContactLists.List(ctx) + if err != nil { + log.Fatal(err) + } + fmt.Printf("account has %d list(s)\n", len(lists)) + + if _, _, err = client.ContactLists.Update(ctx, list.ID, "Former Customers"); err != nil { + log.Fatal(err) + } + + if _, err = client.ContactLists.Delete(ctx, list.ID); err != nil { + log.Fatal(err) + } + fmt.Println("deleted list") +}