Repository: mailgun/mailgun-go
Pull request #394 (2025-03-01 11:33:43)

DE-1393 Move public mocks to subfolder


Patch:
diff --git a/Makefile b/Makefile
index ff1afe5..2de3d91 100644
--- a/Makefile
+++ b/Makefile
@@ -27,7 +27,7 @@ godoc:
 # TODO(vtopc): fix mocks and enable nilaway for them too?
 .PHONY: nilaway
 nilaway: $(NILAWAY)
-	$(NILAWAY) -include-pkgs="$(PACKAGE)" -test=false -exclude-errors-in-files=mock_ ./...
+	$(NILAWAY) -include-pkgs="$(PACKAGE)" -test=false -exclude-errors-in-files=mocks/ ./...
 
 # linter:
 GOLINT = $(GOPATH)/bin/golangci-lint
diff --git a/README.md b/README.md
index 50c42d7..14c7dbe 100644
--- a/README.md
+++ b/README.md
@@ -83,7 +83,7 @@ func main() {
 
 	it := mg.ListEvents("your-domain.com", &mailgun.ListEventOptions{Limit: 100})
 
-	var page []mailgun.Event
+	var page []events.Event
 
 	// The entire operation should not take longer than 30 seconds
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
@@ -151,7 +151,7 @@ func main() {
 	defer cancel()
 
 	// Poll until our email event arrives
-	var page []mailgun.Event
+	var page []events.Event
 	for it.Poll(ctx, &page) {
 		for _, e := range page {
 			log.Printf("Got an event: %q (%q)", e.GetName(), e.GetID())
diff --git a/bounces.go b/bounces.go
index 3fdce1e..c83ed1d 100644
--- a/bounces.go
+++ b/bounces.go
@@ -7,11 +7,6 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-type bouncesListResponse struct {
-	Items  []mtypes.Bounce `json:"items"`
-	Paging mtypes.Paging   `json:"paging"`
-}
-
 // ListBounces returns a complete set of bounces logged against the sender's domain, if any.
 // The results include the total number of bounces (regardless of skip or limit settings),
 // and the slice of bounces specified, if successful.
@@ -28,13 +23,13 @@ func (mg *MailgunImpl) ListBounces(domain string, opts *ListOptions) *BouncesIte
 	url, err := r.generateUrlWithParameters()
 	return &BouncesIterator{
 		mg:                  mg,
-		bouncesListResponse: bouncesListResponse{Paging: mtypes.Paging{Next: url, First: url}},
+		BouncesListResponse: mtypes.BouncesListResponse{Paging: mtypes.Paging{Next: url, First: url}},
 		err:                 err,
 	}
 }
 
 type BouncesIterator struct {
-	bouncesListResponse
+	mtypes.BouncesListResponse
 	mg  Mailgun
 	err error
 }
@@ -124,7 +119,7 @@ func (ci *BouncesIterator) fetch(ctx context.Context, url string) error {
 	r.setClient(ci.mg.HTTPClient())
 	r.setBasicAuth(basicAuthUser, ci.mg.APIKey())
 
-	return getResponseFromJSON(ctx, r, &ci.bouncesListResponse)
+	return getResponseFromJSON(ctx, r, &ci.BouncesListResponse)
 }
 
 // GetBounce retrieves a single bounce record, if any exist, for the given recipient address.
diff --git a/credentials.go b/credentials.go
index 2616d95..88f35c6 100644
--- a/credentials.go
+++ b/credentials.go
@@ -8,12 +8,6 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-type credentialsListResponse struct {
-	// is -1 if Next() or First() have not been called
-	TotalCount int                 `json:"total_count"`
-	Items      []mtypes.Credential `json:"items"`
-}
-
 // ErrEmptyParam is returned when a required parameter is missing.
 var ErrEmptyParam = errors.New("empty or illegal parameter")
 
@@ -30,13 +24,13 @@ func (mg *MailgunImpl) ListCredentials(domain string, opts *ListOptions) *Creden
 	return &CredentialsIterator{
 		mg:                      mg,
 		url:                     generateCredentialsUrl(mg, domain, ""),
-		credentialsListResponse: credentialsListResponse{TotalCount: -1},
+		CredentialsListResponse: mtypes.CredentialsListResponse{TotalCount: -1},
 		limit:                   limit,
 	}
 }
 
 type CredentialsIterator struct {
-	credentialsListResponse
+	mtypes.CredentialsListResponse
 
 	limit  int
 	mg     Mailgun
@@ -165,7 +159,7 @@ func (ri *CredentialsIterator) fetch(ctx context.Context, skip, limit int) error
 		r.addParameter("limit", strconv.Itoa(limit))
 	}
 
-	return getResponseFromJSON(ctx, r, &ri.credentialsListResponse)
+	return getResponseFromJSON(ctx, r, &ri.CredentialsListResponse)
 }
 
 // CreateCredential attempts to create associate a new principle with your domain.
diff --git a/events.go b/events.go
index 2ec47cd..cb05587 100644
--- a/events.go
+++ b/events.go
@@ -84,7 +84,7 @@ func (ei *EventIterator) Err() error {
 // Next retrieves the next page of events from the api. Returns false when there
 // no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
 // the error
-func (ei *EventIterator) Next(ctx context.Context, ee *[]Event) bool {
+func (ei *EventIterator) Next(ctx context.Context, ee *[]events.Event) bool {
 	if ei.err != nil {
 		return false
 	}
@@ -92,7 +92,7 @@ func (ei *EventIterator) Next(ctx context.Context, ee *[]Event) bool {
 	if ei.err != nil {
 		return false
 	}
-	*ee, ei.err = ParseEvents(ei.Items)
+	*ee, ei.err = events.ParseEvents(ei.Items)
 	if ei.err != nil {
 		return false
 	}
@@ -105,7 +105,7 @@ func (ei *EventIterator) Next(ctx context.Context, ee *[]Event) bool {
 // First retrieves the first page of events from the api. Returns false if there
 // was an error. It also sets the iterator object to the first page.
 // Use `.Err()` to retrieve the error.
-func (ei *EventIterator) First(ctx context.Context, ee *[]Event) bool {
+func (ei *EventIterator) First(ctx context.Context, ee *[]events.Event) bool {
 	if ei.err != nil {
 		return false
 	}
@@ -113,7 +113,7 @@ func (ei *EventIterator) First(ctx context.Context, ee *[]Event) bool {
 	if ei.err != nil {
 		return false
 	}
-	*ee, ei.err = ParseEvents(ei.Items)
+	*ee, ei.err = events.ParseEvents(ei.Items)
 	return true
 }
 
@@ -121,7 +121,7 @@ func (ei *EventIterator) First(ctx context.Context, ee *[]Event) bool {
 // Calling Last() is invalid unless you first call First() or Next()
 // Returns false if there was an error. It also sets the iterator object
 // to the last page. Use `.Err()` to retrieve the error.
-func (ei *EventIterator) Last(ctx context.Context, ee *[]Event) bool {
+func (ei *EventIterator) Last(ctx context.Context, ee *[]events.Event) bool {
 	if ei.err != nil {
 		return false
 	}
@@ -129,14 +129,14 @@ func (ei *EventIterator) Last(ctx context.Context, ee *[]Event) bool {
 	if ei.err != nil {
 		return false
 	}
-	*ee, ei.err = ParseEvents(ei.Items)
+	*ee, ei.err = events.ParseEvents(ei.Items)
 	return true
 }
 
 // Previous retrieves the previous page of events from the api. Returns false when there
 // no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
 // the error if any
-func (ei *EventIterator) Previous(ctx context.Context, ee *[]Event) bool {
+func (ei *EventIterator) Previous(ctx context.Context, ee *[]events.Event) bool {
 	if ei.err != nil {
 		return false
 	}
@@ -147,7 +147,7 @@ func (ei *EventIterator) Previous(ctx context.Context, ee *[]Event) bool {
 	if ei.err != nil {
 		return false
 	}
-	*ee, ei.err = ParseEvents(ei.Items)
+	*ee, ei.err = events.ParseEvents(ei.Items)
 
 	return len(ei.Items) != 0
 }
@@ -228,9 +228,9 @@ func (ep *EventPoller) Err() error {
 	return ep.err
 }
 
-func (ep *EventPoller) Poll(ctx context.Context, ee *[]Event) bool {
+func (ep *EventPoller) Poll(ctx context.Context, ee *[]events.Event) bool {
 	var currentPage string
-	var results []Event
+	var results []events.Event
 
 	if ep.opts.Begin.IsZero() {
 		ep.beginTime = time.Now().UTC()
@@ -241,7 +241,7 @@ func (ep *EventPoller) Poll(ctx context.Context, ee *[]Event) bool {
 		currentPage = ep.it.Paging.Next
 
 		// Attempt to get a page of events
-		var page []Event
+		var page []events.Event
 		if !ep.it.Next(ctx, &page) {
 			if ep.it.Err() == nil && len(page) == 0 {
 				// No events, sleep for our poll interval
diff --git a/parse.go b/events/parse.go
similarity index 66%
rename from parse.go
rename to events/parse.go
index 09c8201..346ac43 100644
--- a/parse.go
+++ b/events/parse.go
@@ -1,6 +1,4 @@
-package mailgun
-
-// TODO(v5): move to events package?
+package events
 
 import (
 	"fmt"
@@ -8,7 +6,6 @@ import (
 	"time"
 
 	jsoniter "github.com/json-iterator/go"
-	"github.com/mailgun/mailgun-go/v4/events"
 )
 
 // All events returned by the EventIterator conform to this interface
@@ -23,31 +20,31 @@ type Event interface {
 
 // A list of all JSON event types returned by the /events API
 var EventNames = map[string]func() Event{
-	"accepted":                 new_(events.Accepted{}),
-	"clicked":                  new_(events.Clicked{}),
-	"complained":               new_(events.Complained{}),
-	"delivered":                new_(events.Delivered{}),
-	"failed":                   new_(events.Failed{}),
-	"opened":                   new_(events.Opened{}),
-	"rejected":                 new_(events.Rejected{}),
-	"stored":                   new_(events.Stored{}),
-	"unsubscribed":             new_(events.Unsubscribed{}),
-	"list_member_uploaded":     new_(events.ListMemberUploaded{}),
-	"list_member_upload_error": new_(events.ListMemberUploadError{}),
-	"list_uploaded":            new_(events.ListUploaded{}),
+	"accepted":                 new_(Accepted{}),
+	"clicked":                  new_(Clicked{}),
+	"complained":               new_(Complained{}),
+	"delivered":                new_(Delivered{}),
+	"failed":                   new_(Failed{}),
+	"opened":                   new_(Opened{}),
+	"rejected":                 new_(Rejected{}),
+	"stored":                   new_(Stored{}),
+	"unsubscribed":             new_(Unsubscribed{}),
+	"list_member_uploaded":     new_(ListMemberUploaded{}),
+	"list_member_upload_error": new_(ListMemberUploadError{}),
+	"list_uploaded":            new_(ListUploaded{}),
 }
 
 // new_ is a universal event "constructor".
 func new_(e any) func() Event {
 	typ := reflect.TypeOf(e)
 	return func() Event {
-		//nolint:revive // unchecked-type-assertion: this func is called on init only, so there should be no runtime panics:
+		//nolint:revive // unchecked-type-assertion: TODO: return error?
 		return reflect.New(typ).Interface().(Event)
 	}
 }
 
 func parseResponse(raw []byte) ([]Event, error) {
-	var resp events.Response
+	var resp Response
 	if err := jsoniter.Unmarshal(raw, &resp); err != nil {
 		return nil, fmt.Errorf("failed to un-marshall event.Response: %s", err)
 	}
@@ -64,7 +61,7 @@ func parseResponse(raw []byte) ([]Event, error) {
 }
 
 // Given a slice of events.RawJSON events return a slice of Event for each parsed event
-func ParseEvents(raw []events.RawJSON) ([]Event, error) {
+func ParseEvents(raw []RawJSON) ([]Event, error) {
 	var result []Event
 	for _, value := range raw {
 		event, err := ParseEvent(value)
@@ -79,7 +76,7 @@ func ParseEvents(raw []events.RawJSON) ([]Event, error) {
 // Parse converts raw bytes data into an event struct. Can accept events.RawJSON as input
 func ParseEvent(raw []byte) (Event, error) {
 	// Try to recognize the event first.
-	var e events.EventName
+	var e EventName
 	if err := jsoniter.Unmarshal(raw, &e); err != nil {
 		return nil, fmt.Errorf("failed to recognize event: %v", err)
 	}
diff --git a/examples/examples.go b/examples/examples.go
index 79ba1da..dcf82fe 100644
--- a/examples/examples.go
+++ b/examples/examples.go
@@ -258,7 +258,7 @@ func PrintEventLog(domain, apiKey string) error {
 	defer cancel()
 
 	// Iterate through all the pages of events
-	var page []mailgun.Event
+	var page []events.Event
 	for it.Next(ctx, &page) {
 		for _, event := range page {
 			fmt.Printf("%+v\n", event)
@@ -287,7 +287,7 @@ func PrintFailedEvents(domain, apiKey string) error {
 	defer cancel()
 
 	// Iterate through all the pages of events
-	var page []mailgun.Event
+	var page []events.Event
 	for it.Next(ctx, &page) {
 		for _, event := range page {
 			switch e := event.(type) {
@@ -316,7 +316,7 @@ func PrintEvents(domain, apiKey string) error {
 	defer cancel()
 
 	// Iterate through all the pages of events
-	var page []mailgun.Event
+	var page []events.Event
 	for it.Next(ctx, &page) {
 		for _, event := range page {
 			switch e := event.(type) {
diff --git a/mailgun.go b/mailgun.go
index d851851..484dfe2 100644
--- a/mailgun.go
+++ b/mailgun.go
@@ -95,6 +95,8 @@ const (
 	APIBaseUS = APIBase
 	APIBaseEU = "https://api.eu.mailgun.net"
 
+	basicAuthUser = "api"
+
 	messagesEndpoint     = "messages"
 	mimeMessagesEndpoint = "messages.mime"
 	bouncesEndpoint      = "bounces"
@@ -108,7 +110,6 @@ const (
 	exportsEndpoint      = "exports"
 	webhooksEndpoint     = "webhooks"
 	listsEndpoint        = "lists"
-	basicAuthUser        = "api"
 	templatesEndpoint    = "templates"
 	accountsEndpoint     = "accounts"
 	subaccountsEndpoint  = "subaccounts"
diff --git a/mock.go b/mocks/mock.go
similarity index 81%
rename from mock.go
rename to mocks/mock.go
index 4b5f413..d0c5697 100644
--- a/mock.go
+++ b/mocks/mock.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"crypto/rand"
@@ -13,25 +13,12 @@ import (
 	"sync"
 
 	"github.com/go-chi/chi/v5"
+	"github.com/mailgun/mailgun-go/v4/events"
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-type MockServer interface {
-	Stop()
-	URL() string
-	DomainIPS() []string
-	DomainList() []DomainContainer
-	ExportList() []mtypes.Export
-	MailingList() []MailingListContainer
-	RouteList() []mtypes.Route
-	Events() []Event
-	Webhooks() mtypes.WebHooksListResponse
-	Templates() []mtypes.Template
-	SubaccountList() []mtypes.Subaccount
-}
-
-// A mailgun api mock suitable for testing
-type mockServer struct {
+// Server is a Mailgun API mock suitable for testing
+type Server struct {
 	srv *httptest.Server
 
 	domainIPS        []string
@@ -39,7 +26,7 @@ type mockServer struct {
 	exportList       []mtypes.Export
 	mailingList      []MailingListContainer
 	routeList        []mtypes.Route
-	events           []Event
+	events           []events.Event
 	templates        []mtypes.Template
 	templateVersions map[string][]mtypes.TemplateVersion
 	unsubscribes     []mtypes.Unsubscribe
@@ -52,69 +39,69 @@ type mockServer struct {
 	mutex            sync.Mutex
 }
 
-func (ms *mockServer) DomainIPS() []string {
+func (ms *Server) DomainIPS() []string {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	return ms.domainIPS
 }
 
-func (ms *mockServer) DomainList() []DomainContainer {
+func (ms *Server) DomainList() []DomainContainer {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	return ms.domainList
 }
 
-func (ms *mockServer) ExportList() []mtypes.Export {
+func (ms *Server) ExportList() []mtypes.Export {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	return ms.exportList
 }
 
-func (ms *mockServer) MailingList() []MailingListContainer {
+func (ms *Server) MailingList() []MailingListContainer {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	return ms.mailingList
 }
 
-func (ms *mockServer) RouteList() []mtypes.Route {
+func (ms *Server) RouteList() []mtypes.Route {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	return ms.routeList
 }
 
-func (ms *mockServer) Events() []Event {
+func (ms *Server) Events() []events.Event {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	return ms.events
 }
 
-func (ms *mockServer) Webhooks() mtypes.WebHooksListResponse {
+func (ms *Server) Webhooks() mtypes.WebHooksListResponse {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	return ms.webhooks
 }
 
-func (ms *mockServer) Templates() []mtypes.Template {
+func (ms *Server) Templates() []mtypes.Template {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	return ms.templates
 }
 
-func (ms *mockServer) Unsubscribes() []mtypes.Unsubscribe {
+func (ms *Server) Unsubscribes() []mtypes.Unsubscribe {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	return ms.unsubscribes
 }
 
-func (ms *mockServer) SubaccountList() []mtypes.Subaccount {
+func (ms *Server) SubaccountList() []mtypes.Subaccount {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	return ms.subaccountList
 }
 
-// Create a new instance of the mailgun API mock server
-func NewMockServer() MockServer {
-	ms := mockServer{}
+// NewServer creates a new instance of the mailgun API mock server
+func NewServer() *Server {
+	ms := Server{}
 
 	// Add all our handlers
 	r := chi.NewRouter()
@@ -148,11 +135,11 @@ func NewMockServer() MockServer {
 }
 
 // Stop the server
-func (ms *mockServer) Stop() {
+func (ms *Server) Stop() {
 	ms.srv.Close()
 }
 
-func (ms *mockServer) URL() string {
+func (ms *Server) URL() string {
 	return ms.srv.URL
 }
 
@@ -292,3 +279,7 @@ type okResp struct {
 	ID      string `json:"id,omitempty"`
 	Message string `json:"message"`
 }
+
+func ptr[T any](v T) *T {
+	return &v
+}
diff --git a/mock_analytics.go b/mocks/mock_analytics.go
similarity index 84%
rename from mock_analytics.go
rename to mocks/mock_analytics.go
index bf51a0a..8ea0e1a 100644
--- a/mock_analytics.go
+++ b/mocks/mock_analytics.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"net/http"
@@ -7,11 +7,13 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addAnalyticsRoutes(r chi.Router) {
+const metricsEndpoint = "analytics/metrics"
+
+func (ms *Server) addAnalyticsRoutes(r chi.Router) {
 	r.Post("/v1/"+metricsEndpoint, ms.listMetrics)
 }
 
-func (ms *mockServer) listMetrics(w http.ResponseWriter, _ *http.Request) {
+func (ms *Server) listMetrics(w http.ResponseWriter, _ *http.Request) {
 	start, _ := mtypes.NewRFC2822Time("Tue, 24 Sep 2024 00:00:00 +0000")
 	end, _ := mtypes.NewRFC2822Time("Tue, 24 Oct 2024 00:00:00 +0000")
 
diff --git a/mock_bounces.go b/mocks/mock_bounces.go
similarity index 89%
rename from mock_bounces.go
rename to mocks/mock_bounces.go
index 54d1c4d..f2ccdb9 100644
--- a/mock_bounces.go
+++ b/mocks/mock_bounces.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"encoding/json"
@@ -12,7 +12,7 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addBouncesRoutes(r chi.Router) {
+func (ms *Server) addBouncesRoutes(r chi.Router) {
 	r.Get("/{domain}/bounces", ms.listBounces)
 	r.Get("/{domain}/bounces/{address}", ms.getBounce)
 	r.Delete("/{domain}/bounces/{address}", ms.deleteBounce)
@@ -34,7 +34,7 @@ func (ms *mockServer) addBouncesRoutes(r chi.Router) {
 	})
 }
 
-func (ms *mockServer) listBounces(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listBounces(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -72,7 +72,7 @@ func (ms *mockServer) listBounces(w http.ResponseWriter, r *http.Request) {
 		prevAddress = pivot
 	}
 
-	toJSON(w, bouncesListResponse{
+	toJSON(w, mtypes.BouncesListResponse{
 		Paging: mtypes.Paging{
 			First: getPageURL(r, url.Values{
 				"page": []string{"first"},
@@ -93,7 +93,7 @@ func (ms *mockServer) listBounces(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) getBounce(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getBounce(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -107,7 +107,7 @@ func (ms *mockServer) getBounce(w http.ResponseWriter, r *http.Request) {
 	w.Write([]byte("{\"message\": \"Address not found in bounces table\"}"))
 }
 
-func (ms *mockServer) createBounce(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createBounce(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -165,7 +165,7 @@ func (ms *mockServer) createBounce(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) deleteBounce(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteBounce(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -185,7 +185,7 @@ func (ms *mockServer) deleteBounce(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) deleteBouncesList(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteBouncesList(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
diff --git a/mock_complaints.go b/mocks/mock_complaints.go
similarity index 91%
rename from mock_complaints.go
rename to mocks/mock_complaints.go
index 2920dcd..aab25cd 100644
--- a/mock_complaints.go
+++ b/mocks/mock_complaints.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"encoding/json"
@@ -12,7 +12,7 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addComplaintsRoutes(r chi.Router) {
+func (ms *Server) addComplaintsRoutes(r chi.Router) {
 	r.Get("/{domain}/complaints", ms.listComplaints)
 	r.Get("/{domain}/complaints/{address}", ms.getComplaint)
 	r.Delete("/{domain}/complaints/{address}", ms.deleteComplaint)
@@ -29,7 +29,7 @@ func (ms *mockServer) addComplaintsRoutes(r chi.Router) {
 	})
 }
 
-func (ms *mockServer) listComplaints(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listComplaints(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -88,7 +88,7 @@ func (ms *mockServer) listComplaints(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) getComplaint(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getComplaint(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -102,7 +102,7 @@ func (ms *mockServer) getComplaint(w http.ResponseWriter, r *http.Request) {
 	w.Write([]byte("{\"message\": \"Address not found in complaints table\"}"))
 }
 
-func (ms *mockServer) createComplaint(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createComplaint(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -158,7 +158,7 @@ func (ms *mockServer) createComplaint(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) deleteComplaint(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteComplaint(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
diff --git a/mock_credentials.go b/mocks/mock_credentials.go
similarity index 89%
rename from mock_credentials.go
rename to mocks/mock_credentials.go
index 4fdb459..1c4794a 100644
--- a/mock_credentials.go
+++ b/mocks/mock_credentials.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"net/http"
@@ -8,7 +8,7 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addCredentialsRoutes(r chi.Router) {
+func (ms *Server) addCredentialsRoutes(r chi.Router) {
 	r.Get("/domains/{domain}/credentials", ms.listCredentials)
 	r.Put("/domains/{domain}/credentials/{login}", ms.updateCredential)
 	r.Delete("/domains/{domain}/credentials/{login}", ms.deleteCredential)
@@ -27,7 +27,7 @@ func (ms *mockServer) addCredentialsRoutes(r chi.Router) {
 	})
 }
 
-func (ms *mockServer) listCredentials(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listCredentials(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -54,13 +54,13 @@ func (ms *mockServer) listCredentials(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	toJSON(w, credentialsListResponse{
+	toJSON(w, mtypes.CredentialsListResponse{
 		Items:      results,
 		TotalCount: len(results),
 	})
 }
 
-func (ms *mockServer) createCredential(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createCredential(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -100,7 +100,7 @@ func (ms *mockServer) createCredential(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) deleteCredential(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteCredential(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -124,7 +124,7 @@ func (ms *mockServer) deleteCredential(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, map[string]string{"message": "Credentials not found"})
 }
 
-func (ms *mockServer) updateCredential(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateCredential(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
diff --git a/mock_domains.go b/mocks/mock_domains.go
similarity index 88%
rename from mock_domains.go
rename to mocks/mock_domains.go
index 7065c2d..3c268f6 100644
--- a/mock_domains.go
+++ b/mocks/mock_domains.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"net/http"
@@ -17,7 +17,7 @@ type DomainContainer struct {
 	TagLimits           *mtypes.TagLimits        `json:"limits,omitempty"`
 }
 
-func (ms *mockServer) addDomainRoutes(r chi.Router) {
+func (ms *Server) addDomainRoutes(r chi.Router) {
 	ms.domainList = append(ms.domainList, DomainContainer{
 		Domain: mtypes.Domain{
 			CreatedAt:    mtypes.RFC2822Time(time.Now().UTC()),
@@ -101,7 +101,7 @@ func (ms *mockServer) addDomainRoutes(r chi.Router) {
 	r.Put("/v3/domains/{domain}/dkim_selector", ms.updateDKIMSelector)
 }
 
-func (ms *mockServer) listDomains(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listDomains(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -140,7 +140,7 @@ func (ms *mockServer) listDomains(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) getDomain(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getDomain(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -155,7 +155,7 @@ func (ms *mockServer) getDomain(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "domain not found"})
 }
 
-func (ms *mockServer) createDomain(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createDomain(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -174,7 +174,7 @@ func (ms *mockServer) createDomain(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "Domain has been created"})
 }
 
-func (ms *mockServer) updateDomain(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateDomain(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -187,7 +187,7 @@ func (ms *mockServer) updateDomain(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "Domain has been updated"})
 }
 
-func (ms *mockServer) deleteDomain(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteDomain(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -209,7 +209,7 @@ func (ms *mockServer) deleteDomain(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "domain not found"})
 }
 
-func (ms *mockServer) getConnection(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getConnection(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -226,7 +226,7 @@ func (ms *mockServer) getConnection(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "domain not found"})
 }
 
-func (ms *mockServer) updateConnection(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateConnection(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -244,7 +244,7 @@ func (ms *mockServer) updateConnection(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "domain not found"})
 }
 
-func (ms *mockServer) getTracking(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getTracking(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -261,7 +261,7 @@ func (ms *mockServer) getTracking(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "domain not found"})
 }
 
-func (ms *mockServer) updateClickTracking(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateClickTracking(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -276,7 +276,7 @@ func (ms *mockServer) updateClickTracking(w http.ResponseWriter, r *http.Request
 	toJSON(w, okResp{Message: "domain not found"})
 }
 
-func (ms *mockServer) updateOpenTracking(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateOpenTracking(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -291,7 +291,7 @@ func (ms *mockServer) updateOpenTracking(w http.ResponseWriter, r *http.Request)
 	toJSON(w, okResp{Message: "domain not found"})
 }
 
-func (ms *mockServer) updateUnsubTracking(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateUnsubTracking(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -312,7 +312,7 @@ func (ms *mockServer) updateUnsubTracking(w http.ResponseWriter, r *http.Request
 	toJSON(w, okResp{Message: "domain not found"})
 }
 
-func (ms *mockServer) getTagLimits(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getTagLimits(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -331,7 +331,7 @@ func (ms *mockServer) getTagLimits(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "domain not found"})
 }
 
-func (ms *mockServer) updateDKIMSelector(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateDKIMSelector(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -349,7 +349,7 @@ func (ms *mockServer) updateDKIMSelector(w http.ResponseWriter, r *http.Request)
 	toJSON(w, okResp{Message: "domain not found"})
 }
 
-func (ms *mockServer) updateWebPrefix(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateWebPrefix(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
diff --git a/mock_events.go b/mocks/mock_events.go
similarity index 94%
rename from mock_events.go
rename to mocks/mock_events.go
index f0a91bc..a65ae0f 100644
--- a/mock_events.go
+++ b/mocks/mock_events.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"net/http"
@@ -6,18 +6,19 @@ import (
 	"time"
 
 	"github.com/go-chi/chi/v5"
+	"github.com/mailgun/mailgun-go/v4"
 	"github.com/mailgun/mailgun-go/v4/events"
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addEventRoutes(r chi.Router) {
+func (ms *Server) addEventRoutes(r chi.Router) {
 	r.Get("/{domain}/events", ms.listEvents)
 
 	var (
 		tags            = []string{"tag1", "tag2"}
 		recipients      = []string{"one@mailgun.test", "two@mailgun.test"}
 		recipientDomain = "mailgun.test"
-		timeStamp       = TimeToFloat(time.Now().UTC())
+		timeStamp       = mailgun.TimeToFloat(time.Now().UTC())
 		ipAddress       = "192.168.1.1"
 		message         = events.Message{Headers: events.MessageHeaders{MessageID: "1234"}}
 		clientInfo      = events.ClientInfo{
@@ -191,11 +192,11 @@ func (ms *mockServer) addEventRoutes(r chi.Router) {
 }
 
 type eventsResponse struct {
-	Items  []Event       `json:"items"`
-	Paging mtypes.Paging `json:"paging"`
+	Items  []events.Event `json:"items"`
+	Paging mtypes.Paging  `json:"paging"`
 }
 
-func (ms *mockServer) listEvents(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listEvents(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	var idx []string
@@ -211,14 +212,14 @@ func (ms *mockServer) listEvents(w http.ResponseWriter, r *http.Request) {
 	start, end := pageOffsets(idx, r.FormValue("page"), r.FormValue("address"), limit)
 
 	var nextAddress, prevAddress string
-	var results []Event
+	var results []events.Event
 
 	if start != end {
 		results = ms.events[start:end]
 		nextAddress = results[len(results)-1].GetID()
 		prevAddress = results[0].GetID()
 	} else {
-		results = []Event{}
+		results = []events.Event{}
 		nextAddress = r.FormValue("address")
 		prevAddress = r.FormValue("address")
 	}
diff --git a/mock_exports.go b/mocks/mock_exports.go
similarity index 72%
rename from mock_exports.go
rename to mocks/mock_exports.go
index 9865246..ddef635 100644
--- a/mock_exports.go
+++ b/mocks/mock_exports.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"net/http"
@@ -8,14 +8,14 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addExportRoutes(r chi.Router) {
+func (ms *Server) addExportRoutes(r chi.Router) {
 	r.Post("/exports", ms.postExports)
 	r.Get("/exports", ms.listExports)
 	r.Get("/exports/{id}", ms.getExport)
 	r.Get("/exports/{id}/download_url", ms.getExportLink)
 }
 
-func (ms *mockServer) postExports(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) postExports(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	e := mtypes.Export{
@@ -28,7 +28,7 @@ func (ms *mockServer) postExports(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "success"})
 }
 
-func (ms *mockServer) listExports(w http.ResponseWriter, _ *http.Request) {
+func (ms *Server) listExports(w http.ResponseWriter, _ *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	toJSON(w, mtypes.ExportList{
@@ -36,7 +36,7 @@ func (ms *mockServer) listExports(w http.ResponseWriter, _ *http.Request) {
 	})
 }
 
-func (ms *mockServer) getExport(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getExport(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	for _, export := range ms.exportList {
@@ -49,7 +49,7 @@ func (ms *mockServer) getExport(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "export not found"})
 }
 
-func (ms *mockServer) getExportLink(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getExportLink(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Location", "/some/s3/url")
 	w.WriteHeader(http.StatusFound)
 }
diff --git a/mock_ips.go b/mocks/mock_ips.go
similarity index 74%
rename from mock_ips.go
rename to mocks/mock_ips.go
index 6a31d41..1ed32c5 100644
--- a/mock_ips.go
+++ b/mocks/mock_ips.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"net/http"
@@ -7,7 +7,7 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addIPRoutes(r chi.Router) {
+func (ms *Server) addIPRoutes(r chi.Router) {
 	r.Get("/ips", ms.listIPS)
 	r.Get("/ips/{ip}", ms.getIPAddress)
 	r.Route("/domains/{domain}/ips", func(r chi.Router) {
@@ -18,14 +18,14 @@ func (ms *mockServer) addIPRoutes(r chi.Router) {
 	})
 }
 
-func (ms *mockServer) listIPS(w http.ResponseWriter, _ *http.Request) {
+func (ms *Server) listIPS(w http.ResponseWriter, _ *http.Request) {
 	toJSON(w, mtypes.IPAddressListResponse{
 		TotalCount: 2,
 		Items:      []string{"172.0.0.1", "192.168.1.1"},
 	})
 }
 
-func (ms *mockServer) getIPAddress(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getIPAddress(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, mtypes.IPAddress{
 		IP:        chi.URLParam(r, "ip"),
 		RDNS:      "luna.mailgun.net",
@@ -33,7 +33,7 @@ func (ms *mockServer) getIPAddress(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) listDomainIPS(w http.ResponseWriter, _ *http.Request) {
+func (ms *Server) listDomainIPS(w http.ResponseWriter, _ *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	toJSON(w, mtypes.IPAddressListResponse{
@@ -42,14 +42,14 @@ func (ms *mockServer) listDomainIPS(w http.ResponseWriter, _ *http.Request) {
 	})
 }
 
-func (ms *mockServer) postDomainIPS(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) postDomainIPS(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	ms.domainIPS = append(ms.domainIPS, r.FormValue("ip"))
 	toJSON(w, okResp{Message: "success"})
 }
 
-func (ms *mockServer) deleteDomainIPS(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteDomainIPS(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	result := ms.domainIPS[:0]
diff --git a/mock_mailing_list.go b/mocks/mock_mailing_list.go
similarity index 92%
rename from mock_mailing_list.go
rename to mocks/mock_mailing_list.go
index 1a33c64..2f37d0d 100644
--- a/mock_mailing_list.go
+++ b/mocks/mock_mailing_list.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"encoding/json"
@@ -15,7 +15,7 @@ type MailingListContainer struct {
 	Members     []mtypes.Member
 }
 
-func (ms *mockServer) addMailingListRoutes(r chi.Router) {
+func (ms *Server) addMailingListRoutes(r chi.Router) {
 	r.Get("/lists/pages", ms.listMailingLists)
 	r.Get("/lists/{address}", ms.getMailingList)
 	r.Post("/lists", ms.createMailingList)
@@ -48,7 +48,7 @@ func (ms *mockServer) addMailingListRoutes(r chi.Router) {
 	})
 }
 
-func (ms *mockServer) listMailingLists(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listMailingLists(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -94,7 +94,7 @@ func (ms *mockServer) listMailingLists(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, resp)
 }
 
-func (ms *mockServer) getMailingList(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getMailingList(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -108,7 +108,7 @@ func (ms *mockServer) getMailingList(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "mailing list not found"})
 }
 
-func (ms *mockServer) deleteMailingList(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteMailingList(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -130,7 +130,7 @@ func (ms *mockServer) deleteMailingList(w http.ResponseWriter, r *http.Request)
 	toJSON(w, okResp{Message: "mailing list not found"})
 }
 
-func (ms *mockServer) updateMailingList(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateMailingList(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -159,7 +159,7 @@ func (ms *mockServer) updateMailingList(w http.ResponseWriter, r *http.Request)
 	toJSON(w, okResp{Message: "mailing list not found"})
 }
 
-func (ms *mockServer) createMailingList(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createMailingList(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -176,7 +176,7 @@ func (ms *mockServer) createMailingList(w http.ResponseWriter, r *http.Request)
 	toJSON(w, okResp{Message: "Mailing list has been created"})
 }
 
-func (ms *mockServer) listMembers(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listMembers(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -234,7 +234,7 @@ func (ms *mockServer) listMembers(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, resp)
 }
 
-func (ms *mockServer) getMember(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getMember(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -261,7 +261,7 @@ func (ms *mockServer) getMember(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "member not found"})
 }
 
-func (ms *mockServer) deleteMember(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteMember(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -296,7 +296,7 @@ func (ms *mockServer) deleteMember(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "member not found"})
 }
 
-func (ms *mockServer) updateMember(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateMember(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -336,7 +336,7 @@ func (ms *mockServer) updateMember(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "member not found"})
 }
 
-func (ms *mockServer) createMember(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createMember(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -382,7 +382,7 @@ func (ms *mockServer) createMember(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "Mailing list member has been created"})
 }
 
-func (ms *mockServer) bulkCreate(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) bulkCreate(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
diff --git a/mock_messages.go b/mocks/mock_messages.go
similarity index 89%
rename from mock_messages.go
rename to mocks/mock_messages.go
index 26b35b1..0637f6b 100644
--- a/mock_messages.go
+++ b/mocks/mock_messages.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"net/http"
@@ -7,11 +7,12 @@ import (
 	"time"
 
 	"github.com/go-chi/chi/v5"
+	"github.com/mailgun/mailgun-go/v4"
 	"github.com/mailgun/mailgun-go/v4/events"
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addMessagesRoutes(r chi.Router) {
+func (ms *Server) addMessagesRoutes(r chi.Router) {
 	r.Post("/{domain}/messages", ms.createMessages)
 
 	// This path is made up; it could be anything as the storage url could change over time
@@ -20,7 +21,7 @@ func (ms *mockServer) addMessagesRoutes(r chi.Router) {
 }
 
 // TODO: This implementation doesn't support multiple recipients
-func (ms *mockServer) createMessages(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createMessages(w http.ResponseWriter, r *http.Request) {
 	to, err := mail.ParseAddress(r.FormValue("to"))
 	if err != nil {
 		w.WriteHeader(http.StatusBadRequest)
@@ -33,7 +34,7 @@ func (ms *mockServer) createMessages(w http.ResponseWriter, r *http.Request) {
 	case "stored@mailgun.test":
 		stored := new(events.Stored)
 		stored.Name = events.EventStored
-		stored.Timestamp = TimeToFloat(time.Now().UTC())
+		stored.Timestamp = mailgun.TimeToFloat(time.Now().UTC())
 		stored.ID = id
 		stored.Storage.URL = ms.URL() + "/v3/se.storage.url/messages/" + id
 		stored.Storage.Key = id
@@ -57,7 +58,7 @@ func (ms *mockServer) createMessages(w http.ResponseWriter, r *http.Request) {
 		accepted := new(events.Accepted)
 		accepted.Name = events.EventAccepted
 		accepted.ID = id
-		accepted.Timestamp = TimeToFloat(time.Now().UTC())
+		accepted.Timestamp = mailgun.TimeToFloat(time.Now().UTC())
 		accepted.Message.Headers.From = r.FormValue("from")
 		accepted.Message.Headers.To = r.FormValue("to")
 		accepted.Message.Headers.MessageID = accepted.ID
@@ -102,7 +103,7 @@ func (ms *mockServer) createMessages(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{ID: "<" + id + ">", Message: "Queued. Thank you."})
 }
 
-func (ms *mockServer) getStoredMessages(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getStoredMessages(w http.ResponseWriter, r *http.Request) {
 	id := chi.URLParam(r, "id")
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
@@ -134,7 +135,7 @@ func (ms *mockServer) getStoredMessages(w http.ResponseWriter, r *http.Request)
 	})
 }
 
-func (ms *mockServer) sendStoredMessages(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) sendStoredMessages(w http.ResponseWriter, r *http.Request) {
 	id := chi.URLParam(r, "id")
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
diff --git a/mock_routes.go b/mocks/mock_routes.go
similarity index 87%
rename from mock_routes.go
rename to mocks/mock_routes.go
index 1a59f95..dc5c7ca 100644
--- a/mock_routes.go
+++ b/mocks/mock_routes.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"fmt"
@@ -13,7 +13,7 @@ type routeResponse struct {
 	Route mtypes.Route `json:"route"`
 }
 
-func (ms *mockServer) addRoutes(r chi.Router) {
+func (ms *Server) addRoutes(r chi.Router) {
 	r.Post("/routes", ms.createRoute)
 	r.Get("/routes", ms.listRoutes)
 	r.Get("/routes/{id}", ms.getRoute)
@@ -34,7 +34,7 @@ func (ms *mockServer) addRoutes(r chi.Router) {
 	}
 }
 
-func (ms *mockServer) listRoutes(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listRoutes(w http.ResponseWriter, r *http.Request) {
 	skip := stringToInt(r.FormValue("skip"))
 	limit := stringToInt(r.FormValue("limit"))
 	if limit == 0 {
@@ -65,7 +65,7 @@ func (ms *mockServer) listRoutes(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) getRoute(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getRoute(w http.ResponseWriter, r *http.Request) {
 	for _, item := range ms.routeList {
 		if item.Id == chi.URLParam(r, "id") {
 			toJSON(w, routeResponse{Route: item})
@@ -76,7 +76,7 @@ func (ms *mockServer) getRoute(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "route not found"})
 }
 
-func (ms *mockServer) createRoute(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createRoute(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -100,7 +100,7 @@ func (ms *mockServer) createRoute(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) updateRoute(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateRoute(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -127,7 +127,7 @@ func (ms *mockServer) updateRoute(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "route not found"})
 }
 
-func (ms *mockServer) deleteRoute(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteRoute(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
diff --git a/mock_subaccounts.go b/mocks/mock_subaccounts.go
similarity index 86%
rename from mock_subaccounts.go
rename to mocks/mock_subaccounts.go
index 357ce96..f5f5f38 100644
--- a/mock_subaccounts.go
+++ b/mocks/mock_subaccounts.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"net/http"
@@ -7,7 +7,7 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addSubaccountRoutes(r chi.Router) {
+func (ms *Server) addSubaccountRoutes(r chi.Router) {
 	ms.subaccountList = append(ms.subaccountList, mtypes.Subaccount{
 		ID:     "enabled.subaccount",
 		Name:   "mailgun.test",
@@ -26,7 +26,7 @@ func (ms *mockServer) addSubaccountRoutes(r chi.Router) {
 	r.Post("/accounts/subaccounts/{subaccountID}/disable", ms.disableSubaccount)
 }
 
-func (ms *mockServer) listSubaccounts(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listSubaccounts(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -65,7 +65,7 @@ func (ms *mockServer) listSubaccounts(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) getSubaccount(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getSubaccount(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -79,7 +79,7 @@ func (ms *mockServer) getSubaccount(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "Not Found"})
 }
 
-func (ms *mockServer) createSubaccount(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createSubaccount(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -91,7 +91,7 @@ func (ms *mockServer) createSubaccount(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "Subaccount has been created"})
 }
 
-func (ms *mockServer) enableSubaccount(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) enableSubaccount(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -109,7 +109,7 @@ func (ms *mockServer) enableSubaccount(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "Not Found"})
 }
 
-func (ms *mockServer) disableSubaccount(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) disableSubaccount(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
diff --git a/mock_tags.go b/mocks/mock_tags.go
similarity index 89%
rename from mock_tags.go
rename to mocks/mock_tags.go
index 5082506..1068e84 100644
--- a/mock_tags.go
+++ b/mocks/mock_tags.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"net/http"
@@ -9,7 +9,7 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addTagsRoutes(r chi.Router) {
+func (ms *Server) addTagsRoutes(r chi.Router) {
 	r.Get("/{domain}/tags", ms.listTags)
 	r.Get("/{domain}/tags/{tag}", ms.getTags)
 	r.Delete("/{domain}/tags/{tag}", ms.deleteTags)
@@ -32,7 +32,7 @@ func (ms *mockServer) addTagsRoutes(r chi.Router) {
 	})
 }
 
-func (ms *mockServer) listTags(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listTags(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -91,7 +91,7 @@ func (ms *mockServer) listTags(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) getTags(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getTags(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 	for _, tag := range ms.tags {
@@ -104,7 +104,7 @@ func (ms *mockServer) getTags(w http.ResponseWriter, r *http.Request) {
 	w.Write([]byte("{\"message\": \"Tag not found\"}"))
 }
 
-func (ms *mockServer) createUpdateTags(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createUpdateTags(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -134,7 +134,7 @@ func (ms *mockServer) createUpdateTags(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) deleteTags(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteTags(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
diff --git a/mock_template_versions.go b/mocks/mock_template_versions.go
similarity index 92%
rename from mock_template_versions.go
rename to mocks/mock_template_versions.go
index 757e9f1..f555f2d 100644
--- a/mock_template_versions.go
+++ b/mocks/mock_template_versions.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"fmt"
@@ -11,7 +11,7 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addTemplateVersionRoutes(r chi.Router) {
+func (ms *Server) addTemplateVersionRoutes(r chi.Router) {
 	r.Get("/{domain}/templates/{template}/versions", ms.listTemplateVersions)
 	r.Get("/{domain}/templates/{template}/versions/{tag}", ms.getTemplateVersion)
 	r.Post("/{domain}/templates/{template}/versions", ms.createTemplateVersion)
@@ -19,7 +19,7 @@ func (ms *mockServer) addTemplateVersionRoutes(r chi.Router) {
 	r.Delete("/{domain}/templates/{template}/versions/{tag}", ms.deleteTemplateVersion)
 }
 
-func (ms *mockServer) listTemplateVersions(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listTemplateVersions(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -94,7 +94,7 @@ func (ms *mockServer) listTemplateVersions(w http.ResponseWriter, r *http.Reques
 	})
 }
 
-func (ms *mockServer) getTemplateVersion(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getTemplateVersion(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -124,7 +124,7 @@ func (ms *mockServer) getTemplateVersion(w http.ResponseWriter, r *http.Request)
 	})
 }
 
-func (ms *mockServer) createTemplateVersion(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createTemplateVersion(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -196,7 +196,7 @@ func (ms *mockServer) createTemplateVersion(w http.ResponseWriter, r *http.Reque
 	})
 }
 
-func (ms *mockServer) updateTemplateVersion(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateTemplateVersion(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -275,7 +275,7 @@ func (ms *mockServer) updateTemplateVersion(w http.ResponseWriter, r *http.Reque
 	})
 }
 
-func (ms *mockServer) deleteTemplateVersion(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteTemplateVersion(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -309,7 +309,7 @@ func (ms *mockServer) deleteTemplateVersion(w http.ResponseWriter, r *http.Reque
 	})
 }
 
-func (ms *mockServer) fetchTemplate(name string) (template mtypes.Template, found bool) {
+func (ms *Server) fetchTemplate(name string) (template mtypes.Template, found bool) {
 	for _, existingTemplate := range ms.templates {
 		if existingTemplate.Name == name {
 			template = existingTemplate
@@ -320,7 +320,7 @@ func (ms *mockServer) fetchTemplate(name string) (template mtypes.Template, foun
 	return mtypes.Template{}, false
 }
 
-func (ms *mockServer) fetchTemplateVersion(templateName string, templateVersionTag string) (mtypes.TemplateVersion, bool) {
+func (ms *Server) fetchTemplateVersion(templateName string, templateVersionTag string) (mtypes.TemplateVersion, bool) {
 	for _, existingTemplate := range ms.templateVersions[templateName] {
 		if existingTemplate.Tag == templateVersionTag {
 			return existingTemplate, true
diff --git a/mock_templates.go b/mocks/mock_templates.go
similarity index 91%
rename from mock_templates.go
rename to mocks/mock_templates.go
index 357378d..61f10a2 100644
--- a/mock_templates.go
+++ b/mocks/mock_templates.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"fmt"
@@ -11,7 +11,7 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addTemplateRoutes(r chi.Router) {
+func (ms *Server) addTemplateRoutes(r chi.Router) {
 	r.Get("/{domain}/templates", ms.listTemplates)
 	r.Get("/{domain}/templates/{name}", ms.getTemplate)
 
@@ -73,7 +73,7 @@ func (ms *mockServer) addTemplateRoutes(r chi.Router) {
 	}
 }
 
-func (ms *mockServer) listTemplates(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listTemplates(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -132,7 +132,7 @@ func (ms *mockServer) listTemplates(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) getTemplate(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getTemplate(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -166,7 +166,7 @@ func (ms *mockServer) getTemplate(w http.ResponseWriter, r *http.Request) {
 	w.Write([]byte("{\"message\": \"template not found\"}"))
 }
 
-func (ms *mockServer) createTemplate(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createTemplate(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -230,7 +230,7 @@ func (ms *mockServer) createTemplate(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) updateTemplate(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) updateTemplate(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -269,7 +269,7 @@ func (ms *mockServer) updateTemplate(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) deleteTemplate(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteTemplate(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -293,7 +293,7 @@ func (ms *mockServer) deleteTemplate(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, map[string]string{"message": "template not found"})
 }
 
-func (ms *mockServer) deleteAllTemplates(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteAllTemplates(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -303,7 +303,7 @@ func (ms *mockServer) deleteAllTemplates(w http.ResponseWriter, r *http.Request)
 	toJSON(w, map[string]string{"message": "templates have been deleted"})
 }
 
-func (ms *mockServer) getActiveTemplateVersion(templateName string) mtypes.TemplateVersion {
+func (ms *Server) getActiveTemplateVersion(templateName string) mtypes.TemplateVersion {
 	for _, templateVersion := range ms.templateVersions[templateName] {
 		if templateVersion.Active {
 			return templateVersion
diff --git a/mock_unsubscribes.go b/mocks/mock_unsubscribes.go
similarity index 92%
rename from mock_unsubscribes.go
rename to mocks/mock_unsubscribes.go
index 7827825..4f7ee9a 100644
--- a/mock_unsubscribes.go
+++ b/mocks/mock_unsubscribes.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"encoding/json"
@@ -12,7 +12,7 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addUnsubscribesRoutes(r chi.Router) {
+func (ms *Server) addUnsubscribesRoutes(r chi.Router) {
 	r.Get("/{domain}/unsubscribes", ms.listUnsubscribes)
 	r.Get("/{domain}/unsubscribes/{address}", ms.getUnsubscribe)
 	r.Delete("/{domain}/unsubscribes/{address}", ms.deleteUnsubscribe)
@@ -33,7 +33,7 @@ func (ms *mockServer) addUnsubscribesRoutes(r chi.Router) {
 	})
 }
 
-func (ms *mockServer) listUnsubscribes(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) listUnsubscribes(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -92,7 +92,7 @@ func (ms *mockServer) listUnsubscribes(w http.ResponseWriter, r *http.Request) {
 	})
 }
 
-func (ms *mockServer) getUnsubscribe(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getUnsubscribe(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -106,7 +106,7 @@ func (ms *mockServer) getUnsubscribe(w http.ResponseWriter, r *http.Request) {
 	w.Write([]byte("{\"message\": \"Address not found in unsubscribers table\"}"))
 }
 
-func (ms *mockServer) createUnsubscribe(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) createUnsubscribe(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -164,7 +164,7 @@ func (ms *mockServer) createUnsubscribe(w http.ResponseWriter, r *http.Request)
 	})
 }
 
-func (ms *mockServer) deleteUnsubscribe(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteUnsubscribe(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
diff --git a/mock_validation.go b/mocks/mock_validation.go
similarity index 81%
rename from mock_validation.go
rename to mocks/mock_validation.go
index 9a0a13c..9e34108 100644
--- a/mock_validation.go
+++ b/mocks/mock_validation.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"net/http"
@@ -8,11 +8,11 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addValidationRoutes(r chi.Router) {
+func (ms *Server) addValidationRoutes(r chi.Router) {
 	r.Get("/v4/address/validate", ms.validateEmailV4)
 }
 
-func (ms *mockServer) validateEmailV4(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) validateEmailV4(w http.ResponseWriter, r *http.Request) {
 	if r.FormValue("address") == "" {
 		w.WriteHeader(http.StatusBadRequest)
 		toJSON(w, okResp{Message: "'address' parameter is required"})
diff --git a/mock_webhooks.go b/mocks/mock_webhooks.go
similarity index 80%
rename from mock_webhooks.go
rename to mocks/mock_webhooks.go
index d6a5742..37c1265 100644
--- a/mock_webhooks.go
+++ b/mocks/mock_webhooks.go
@@ -1,4 +1,4 @@
-package mailgun
+package mocks
 
 import (
 	"net/http"
@@ -7,7 +7,7 @@ import (
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 )
 
-func (ms *mockServer) addWebhookRoutes(r chi.Router) {
+func (ms *Server) addWebhookRoutes(r chi.Router) {
 	r.Route("/domains/{domain}/webhooks", func(sr chi.Router) {
 		sr.Get("/", ms.listWebHooks)
 		sr.Post("/", ms.postWebHook)
@@ -28,14 +28,14 @@ func (ms *mockServer) addWebhookRoutes(r chi.Router) {
 	}
 }
 
-func (ms *mockServer) listWebHooks(w http.ResponseWriter, _ *http.Request) {
+func (ms *Server) listWebHooks(w http.ResponseWriter, _ *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
 	toJSON(w, ms.webhooks)
 }
 
-func (ms *mockServer) getWebHook(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) getWebHook(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -47,7 +47,7 @@ func (ms *mockServer) getWebHook(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, resp)
 }
 
-func (ms *mockServer) postWebHook(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) postWebHook(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -66,7 +66,7 @@ func (ms *mockServer) postWebHook(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "success"})
 }
 
-func (ms *mockServer) putWebHook(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) putWebHook(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
@@ -85,7 +85,7 @@ func (ms *mockServer) putWebHook(w http.ResponseWriter, r *http.Request) {
 	toJSON(w, okResp{Message: "success"})
 }
 
-func (ms *mockServer) deleteWebHook(w http.ResponseWriter, r *http.Request) {
+func (ms *Server) deleteWebHook(w http.ResponseWriter, r *http.Request) {
 	defer ms.mutex.Unlock()
 	ms.mutex.Lock()
 
diff --git a/mtypes/bounces.go b/mtypes/bounces.go
index 1f20178..9b38795 100644
--- a/mtypes/bounces.go
+++ b/mtypes/bounces.go
@@ -12,3 +12,8 @@ type Bounce struct {
 	// human readable reason why
 	Error string `json:"error"`
 }
+
+type BouncesListResponse struct {
+	Items  []Bounce `json:"items"`
+	Paging Paging   `json:"paging"`
+}
diff --git a/mtypes/credentials.go b/mtypes/credentials.go
index 552915f..2aea608 100644
--- a/mtypes/credentials.go
+++ b/mtypes/credentials.go
@@ -6,3 +6,9 @@ type Credential struct {
 	Login     string      `json:"login"`
 	Password  string      `json:"password"`
 }
+
+type CredentialsListResponse struct {
+	// is -1 if Next() or First() have not been called
+	TotalCount int          `json:"total_count"`
+	Items      []Credential `json:"items"`
+}


Test patch:
diff --git a/attachments_test.go b/attachments_test.go
index 9df5fb3..620900e 100644
--- a/attachments_test.go
+++ b/attachments_test.go
@@ -59,7 +59,7 @@ func TestMultipleAttachments(t *testing.T) {
 func findAcceptedMessage(mg mailgun.Mailgun, id string) (*events.Accepted, error) {
 	it := mg.ListEvents(testDomain, nil)
 
-	var page []mailgun.Event
+	var page []events.Event
 	for it.Next(context.Background(), &page) {
 		for _, event := range page {
 			if event.GetName() == events.EventAccepted && event.GetID() == id {
diff --git a/parse_test.go b/events/parse_test.go
similarity index 83%
rename from parse_test.go
rename to events/parse_test.go
index 0ee54cc..cfa8d07 100644
--- a/parse_test.go
+++ b/events/parse_test.go
@@ -1,4 +1,4 @@
-package mailgun
+package events
 
 import (
 	"fmt"
@@ -6,7 +6,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/mailgun/mailgun-go/v4/events"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -84,9 +83,9 @@ func TestParseSuccess(t *testing.T) {
 	}`))
 	require.NoError(t, err)
 	require.Equal(t, reflect.TypeOf(event).String(), "*events.Accepted")
-	subject := event.(*events.Accepted).Message.Headers.Subject
+	subject := event.(*Accepted).Message.Headers.Subject
 	assert.Equal(t, "Test message going through the bus.", subject)
-	assert.Equal(t, "AgEASDSGFB8y4--TSDGxvccvmQB==", event.(*events.Accepted).Storage.Key)
+	assert.Equal(t, "AgEASDSGFB8y4--TSDGxvccvmQB==", event.(*Accepted).Storage.Key)
 
 	// Make sure the next event parsing attempt will zero the fields.
 	event2, err := ParseEvent([]byte(`{
@@ -97,14 +96,14 @@ func TestParseSuccess(t *testing.T) {
 	require.NoError(t, err)
 
 	assert.Equal(t, time.Date(2018, 8, 10, 17, 35, 16, 538978048, time.UTC), event2.GetTimestamp())
-	assert.Equal(t, "", event2.(*events.Accepted).Message.Headers.Subject)
+	assert.Equal(t, "", event2.(*Accepted).Message.Headers.Subject)
 	// Make sure the second attempt of Parse doesn't overwrite the first event struct.
-	assert.Equal(t, "dude@example.com", event.(*events.Accepted).Recipient)
+	assert.Equal(t, "dude@example.com", event.(*Accepted).Recipient)
 
-	assert.Equal(t, "value", event.(*events.Accepted).UserVariables.(map[string]any)["custom"])
-	child := event.(*events.Accepted).UserVariables.(map[string]any)["parent"].(map[string]any)["child"]
+	assert.Equal(t, "value", event.(*Accepted).UserVariables.(map[string]any)["custom"])
+	child := event.(*Accepted).UserVariables.(map[string]any)["parent"].(map[string]any)["child"]
 	assert.Equal(t, "user defined variable", child)
-	aList := event.(*events.Accepted).UserVariables.(map[string]any)["a-list"].([]any)
+	aList := event.(*Accepted).UserVariables.(map[string]any)["a-list"].([]any)
 	assert.Equal(t, []any{1.0, 2.0, 3.0, 4.0, 5.0}, aList)
 }
 
@@ -117,7 +116,7 @@ func TestParseSuccessInvalidUserVariables(t *testing.T) {
 	require.NoError(t, err)
 	require.Equal(t, "*events.Accepted", reflect.TypeOf(event).String())
 	assert.Equal(t, "Could not load user-variables. They were either truncated or invalid JSON",
-		event.(*events.Accepted).UserVariables)
+		event.(*Accepted).UserVariables)
 }
 
 func TestParseResponse(t *testing.T) {
@@ -145,14 +144,14 @@ func TestParseResponse(t *testing.T) {
 	require.NoError(t, err)
 
 	assert.Equal(t, "accepted", evnts[0].GetName())
-	assert.Equal(t, "someone@example.com", evnts[0].(*events.Accepted).Recipient)
+	assert.Equal(t, "someone@example.com", evnts[0].(*Accepted).Recipient)
 
 	assert.Equal(t, "delivered", evnts[1].GetName())
-	assert.Equal(t, "test@mailgun.test", evnts[1].(*events.Delivered).Recipient)
+	assert.Equal(t, "test@mailgun.test", evnts[1].(*Delivered).Recipient)
 }
 
 func TestTimeStamp(t *testing.T) {
-	var event events.Generic
+	var event Generic
 	ts := time.Date(2018, 8, 10, 17, 35, 16, 538978048, time.UTC)
 	event.SetTimestamp(ts)
 	assert.Equal(t, ts, event.GetTimestamp())
@@ -184,7 +183,7 @@ func TestEventMessageWithAttachment(t *testing.T) {
                 "size": 142698}}`)
 	event, err := ParseEvent(body)
 	require.NoError(t, err)
-	assert.Equal(t, "doc.pdf", event.(*events.Delivered).Message.Attachments[0].FileName)
+	assert.Equal(t, "doc.pdf", event.(*Delivered).Message.Attachments[0].FileName)
 }
 
 func TestStored(t *testing.T) {
@@ -198,6 +197,6 @@ func TestStored(t *testing.T) {
         }}`, key, url))
 	event, err := ParseEvent(body)
 	require.NoError(t, err)
-	assert.Equal(t, key, event.(*events.Stored).Storage.Key)
-	assert.Equal(t, url, event.(*events.Stored).Storage.URL)
+	assert.Equal(t, key, event.(*Stored).Storage.Key)
+	assert.Equal(t, url, event.(*Stored).Storage.URL)
 }
diff --git a/events_test.go b/events_test.go
index b81d22e..ca7379b 100644
--- a/events_test.go
+++ b/events_test.go
@@ -19,7 +19,7 @@ func TestEventIteratorGetNext(t *testing.T) {
 
 	it := mg.ListEvents(testDomain, &mailgun.ListEventOptions{Limit: 5})
 
-	var firstPage, secondPage, previousPage []mailgun.Event
+	var firstPage, secondPage, previousPage []events.Event
 	var ctx = context.Background()
 
 	require.True(t, it.Next(ctx, &firstPage))
@@ -53,7 +53,7 @@ func TestEventIteratorGetNext(t *testing.T) {
 	require.NotEqual(t, firstPage, secondPage)
 
 	// Last()
-	var lastPage []mailgun.Event
+	var lastPage []events.Event
 	require.True(t, it.Next(ctx, &firstPage))
 	require.True(t, len(firstPage) != 0)
 
@@ -74,12 +74,12 @@ func TestEventPoller(t *testing.T) {
 		// How often we poll the api for new events
 		PollInterval: time.Second * 4})
 
-	eventChan := make(chan mailgun.Event, 1)
+	eventChan := make(chan events.Event, 1)
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 	go func() {
 		// Poll until our email event arrives
-		var page []mailgun.Event
+		var page []events.Event
 		for it.Poll(ctx, &page) {
 			for _, e := range page {
 				eventChan <- e
@@ -119,7 +119,7 @@ func ExampleMailgunImpl_ListEvents() {
 
 	it := mg.ListEvents("your-domain.com", &mailgun.ListEventOptions{Limit: 100})
 
-	var page []mailgun.Event
+	var page []events.Event
 
 	// The entire operation should not take longer than 30 seconds
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
diff --git a/examples_test.go b/examples_test.go
index 02d5ce2..eacc5b3 100644
--- a/examples_test.go
+++ b/examples_test.go
@@ -144,7 +144,7 @@ func ExampleMailgunImpl_VerifyWebhookSignature() {
 
 		// Parse the raw event to extract the
 
-		e, err := mailgun.ParseEvent(payload.EventData)
+		e, err := events.ParseEvent(payload.EventData)
 		if err != nil {
 			fmt.Printf("parse event error: %s\n", err)
 			return
diff --git a/ips_test.go b/ips_test.go
index 6e28208..cb9fe6f 100644
--- a/ips_test.go
+++ b/ips_test.go
@@ -2,7 +2,6 @@ package mailgun_test
 
 import (
 	"context"
-	"os"
 	"testing"
 
 	"github.com/mailgun/mailgun-go/v4"
@@ -10,15 +9,6 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-var server mailgun.MockServer
-
-// Setup and shutdown the mailgun mock server for the entire test suite
-func TestMain(m *testing.M) {
-	server = mailgun.NewMockServer()
-	defer server.Stop()
-	os.Exit(m.Run())
-}
-
 func TestListIPS(t *testing.T) {
 	mg := mailgun.NewMailgun(testKey)
 	err := mg.SetAPIBase(server.URL())
diff --git a/mailgun_test.go b/mailgun_test.go
index a38d836..ed62131 100644
--- a/mailgun_test.go
+++ b/mailgun_test.go
@@ -4,9 +4,11 @@ import (
 	"encoding/json"
 	"net/http"
 	"net/http/httptest"
+	"os"
 	"testing"
 
 	"github.com/mailgun/mailgun-go/v4"
+	"github.com/mailgun/mailgun-go/v4/mocks"
 	"github.com/mailgun/mailgun-go/v4/mtypes"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -15,6 +17,15 @@ import (
 const domain = "valid-mailgun-domain"
 const apiKey = "valid-mailgun-api-key" //nolint:gosec // This is a test
 
+var server *mocks.Server
+
+// Setup and shutdown the mailgun mock server for the entire test suite
+func TestMain(m *testing.M) {
+	server = mocks.NewServer()
+	defer server.Stop()
+	os.Exit(m.Run())
+}
+
 func TestMailgun(t *testing.T) {
 	m := mailgun.NewMailgun(apiKey)
 
diff --git a/storage_test.go b/storage_test.go
index bcf0f75..934a493 100644
--- a/storage_test.go
+++ b/storage_test.go
@@ -43,7 +43,7 @@ func TestStorage(t *testing.T) {
 func findStoredMessageURL(mg mailgun.Mailgun, id string) (string, error) {
 	it := mg.ListEvents(testDomain, nil)
 
-	var page []mailgun.Event
+	var page []events.Event
 	for it.Next(context.Background(), &page) {
 		for _, event := range page {
 			if event.GetName() == events.EventStored && event.GetID() == id {