├── .github └── workflows │ └── go.yml ├── .gitignore ├── Dockerfile ├── LICENSE.md ├── README.md ├── api_keys.go ├── api_keys_test.go ├── audiences.go ├── audiences_test.go ├── batch.go ├── batch_test.go ├── broadcasts.go ├── broadcasts_test.go ├── contacts.go ├── contacts_test.go ├── docker-compose.yml ├── domains.go ├── domains_test.go ├── emails.go ├── emails_test.go ├── errors.go ├── examples ├── api_keys.go ├── audiences.go ├── broadcasts.go ├── contacts.go ├── domains.go ├── schedule_email.go ├── send_batch_email.go ├── send_email.go └── with_attachments.go ├── go.mod ├── go.sum ├── resend.go ├── resend_test.go ├── resources └── invoice.pdf ├── utils.go └── utils_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | go: 19 | - "1.23" 20 | - "1.22" 21 | - "1.21" 22 | name: "Test: go v${{ matrix.go }}" 23 | steps: 24 | - uses: actions/checkout@v3 25 | 26 | - name: Setup go 27 | uses: actions/setup-go@v1 28 | with: 29 | go-version: ${{ matrix.go }} 30 | 31 | - name: Build 32 | run: go build -v ./... 33 | 34 | - name: Test 35 | run: go test -v ./... -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23 2 | 3 | WORKDIR /app 4 | 5 | COPY go.mod go.sum ./ 6 | RUN go mod download 7 | 8 | COPY *.go ./ 9 | 10 | RUN go install github.com/rakyll/gotest@latest 11 | 12 | # RUN CGO_ENABLED=0 GOOS=linux go build -o /resend 13 | 14 | # CMD ["/resend"] -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Derich Pacheco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Resend Go SDK 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 4 | ![Build](https://github.com/resend/resend-go/actions/workflows/go.yml/badge.svg) 5 | ![Release](https://img.shields.io/github/release/resend/resend-go.svg?style=flat-square) 6 | [![Go Reference](https://pkg.go.dev/badge/github.com/resend/resend-go/v2.svg)](https://pkg.go.dev/github.com/resend/resend-go/v2) 7 | --- 8 | 9 | ## Installation 10 | 11 | To install the Go SDK, simply execute the following command on a terminal: 12 | 13 | ``` 14 | go get github.com/resend/resend-go/v2 15 | ``` 16 | 17 | ## Setup 18 | 19 | First, you need to get an API key, which is available in the [Resend Dashboard](https://resend.com). 20 | 21 | ## Example 22 | 23 | ```go 24 | import ( 25 | "fmt" 26 | "github.com/resend/resend-go/v2" 27 | ) 28 | 29 | func main() { 30 | apiKey := "re_123" 31 | 32 | client := resend.NewClient(apiKey) 33 | 34 | params := &resend.SendEmailRequest{ 35 | To: []string{"to@example", "you@example.com"}, 36 | From: "me@exemple.io", 37 | Text: "hello world", 38 | Subject: "Hello from Golang", 39 | Cc: []string{"cc@example.com"}, 40 | Bcc: []string{"cc@example.com"}, 41 | ReplyTo: "replyto@example.com", 42 | } 43 | 44 | sent, err := client.Emails.Send(params) 45 | if err != nil { 46 | panic(err) 47 | } 48 | fmt.Println(sent.Id) 49 | } 50 | 51 | ``` 52 | 53 | You can view all the examples in the [examples folder](https://github.com/resend/resend-go/tree/main/examples) 54 | -------------------------------------------------------------------------------- /api_keys.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | ) 7 | 8 | type CreateApiKeyRequest struct { 9 | Name string `json:"name"` 10 | Permission string `json:"permission,omitempty"` // TODO: update permission to type 11 | DomainId string `json:"domain_id,omitempty"` 12 | } 13 | 14 | type CreateApiKeyResponse struct { 15 | Id string `json:"id"` 16 | Token string `json:"token"` 17 | } 18 | 19 | type ListApiKeysResponse struct { 20 | Data []ApiKey `json:"data"` 21 | } 22 | 23 | type ApiKey struct { 24 | Id string `json:"id"` 25 | Name string `json:"name"` 26 | CreatedAt string `json:"created_at"` 27 | } 28 | 29 | type ApiKeysSvc interface { 30 | CreateWithContext(ctx context.Context, params *CreateApiKeyRequest) (CreateApiKeyResponse, error) 31 | Create(params *CreateApiKeyRequest) (CreateApiKeyResponse, error) 32 | ListWithContext(ctx context.Context) (ListApiKeysResponse, error) 33 | List() (ListApiKeysResponse, error) 34 | RemoveWithContext(ctx context.Context, apiKeyId string) (bool, error) 35 | Remove(apiKeyId string) (bool, error) 36 | } 37 | 38 | type ApiKeysSvcImpl struct { 39 | client *Client 40 | } 41 | 42 | // CreateWithContext creates a new API Key based on the given params 43 | // https://resend.com/docs/api-reference/api-keys/create-api-key 44 | func (s *ApiKeysSvcImpl) CreateWithContext(ctx context.Context, params *CreateApiKeyRequest) (CreateApiKeyResponse, error) { 45 | path := "api-keys" 46 | 47 | // Prepare request 48 | req, err := s.client.NewRequest(ctx, http.MethodPost, path, params) 49 | if err != nil { 50 | return CreateApiKeyResponse{}, ErrFailedToCreateApiKeysCreateRequest 51 | } 52 | 53 | // Build response recipient obj 54 | apiKeysResp := new(CreateApiKeyResponse) 55 | 56 | // Send Request 57 | _, err = s.client.Perform(req, apiKeysResp) 58 | 59 | if err != nil { 60 | return CreateApiKeyResponse{}, err 61 | } 62 | 63 | return *apiKeysResp, nil 64 | } 65 | 66 | // Create creates a new API Key based on the given params 67 | func (s *ApiKeysSvcImpl) Create(params *CreateApiKeyRequest) (CreateApiKeyResponse, error) { 68 | return s.CreateWithContext(context.Background(), params) 69 | } 70 | 71 | // ListWithContext list all API Keys in the project 72 | // https://resend.com/docs/api-reference/api-keys/list-api-keys 73 | func (s *ApiKeysSvcImpl) ListWithContext(ctx context.Context) (ListApiKeysResponse, error) { 74 | path := "api-keys" 75 | 76 | // Prepare request 77 | req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 78 | if err != nil { 79 | return ListApiKeysResponse{}, ErrFailedToCreateApiKeysListRequest 80 | } 81 | 82 | // Build response recipient obj 83 | apiKeysResp := new(ListApiKeysResponse) 84 | 85 | // Send Request 86 | _, err = s.client.Perform(req, apiKeysResp) 87 | 88 | if err != nil { 89 | return ListApiKeysResponse{}, err 90 | } 91 | 92 | return *apiKeysResp, nil 93 | } 94 | 95 | // List all API Keys in the project 96 | func (s *ApiKeysSvcImpl) List() (ListApiKeysResponse, error) { 97 | return s.ListWithContext(context.Background()) 98 | } 99 | 100 | // RemoveWithContext deletes a given api key by id 101 | // https://resend.com/docs/api-reference/api-keys/delete-api-key 102 | func (s *ApiKeysSvcImpl) RemoveWithContext(ctx context.Context, apiKeyId string) (bool, error) { 103 | path := "api-keys/" + apiKeyId 104 | 105 | // Prepare request 106 | req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) 107 | if err != nil { 108 | return false, ErrFailedToCreateApiKeysRemoveRequest 109 | } 110 | 111 | // Send Request 112 | _, err = s.client.Perform(req, nil) 113 | 114 | if err != nil { 115 | return false, err 116 | } 117 | 118 | return true, nil 119 | } 120 | 121 | // Remove deletes a given api key by id 122 | func (s *ApiKeysSvcImpl) Remove(apiKeyId string) (bool, error) { 123 | return s.RemoveWithContext(context.Background(), apiKeyId) 124 | } 125 | -------------------------------------------------------------------------------- /api_keys_test.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCreateApiKey(t *testing.T) { 12 | setup() 13 | defer teardown() 14 | 15 | mux.HandleFunc("/api-keys", func(w http.ResponseWriter, r *http.Request) { 16 | testMethod(t, r, http.MethodPost) 17 | w.Header().Set("Content-Type", "application/json") 18 | w.WriteHeader(http.StatusOK) 19 | 20 | ret := ` 21 | { 22 | "id": "dacf4072-4119-4d88-932f-6202748ac7c8", 23 | "token": "re_c1tpEyD8_NKFusih9vKVQknRAQfmFcWCv" 24 | }` 25 | fmt.Fprintf(w, ret) 26 | }) 27 | 28 | req := &CreateApiKeyRequest{ 29 | Name: "new api key", 30 | } 31 | resp, err := client.ApiKeys.Create(req) 32 | if err != nil { 33 | t.Errorf("ApiKeys.Create returned error: %v", err) 34 | } 35 | assert.Equal(t, resp.Id, "dacf4072-4119-4d88-932f-6202748ac7c8") 36 | assert.Equal(t, resp.Token, "re_c1tpEyD8_NKFusih9vKVQknRAQfmFcWCv") 37 | } 38 | 39 | func TestListApiKeys(t *testing.T) { 40 | setup() 41 | defer teardown() 42 | 43 | mux.HandleFunc("/api-keys", func(w http.ResponseWriter, r *http.Request) { 44 | testMethod(t, r, http.MethodGet) 45 | w.Header().Set("Content-Type", "application/json") 46 | w.WriteHeader(http.StatusOK) 47 | 48 | ret := ` 49 | { 50 | "data": [ 51 | { 52 | "id": "91f3200a-df72-4654-b0cd-f202395f5354", 53 | "name": "Production", 54 | "created_at": "2023-04-08T00:11:13.110779+00:00" 55 | } 56 | ] 57 | }` 58 | fmt.Fprintf(w, ret) 59 | }) 60 | 61 | resp, err := client.ApiKeys.List() 62 | if err != nil { 63 | t.Errorf("ApiKeys.List returned error: %v", err) 64 | } 65 | assert.Equal(t, len(resp.Data), 1) 66 | assert.Equal(t, resp.Data[0].Name, "Production") 67 | } 68 | 69 | func TestRemoveApiKey(t *testing.T) { 70 | setup() 71 | defer teardown() 72 | 73 | mux.HandleFunc("/api-keys/keyid", func(w http.ResponseWriter, r *http.Request) { 74 | testMethod(t, r, http.MethodDelete) 75 | w.WriteHeader(http.StatusOK) 76 | w.Header().Set("Content-Length", "0") 77 | fmt.Fprint(w, nil) 78 | }) 79 | 80 | deleted, err := client.ApiKeys.Remove("keyid") 81 | if err != nil { 82 | t.Errorf("ApiKeys.Remove returned error: %v", err) 83 | } 84 | assert.True(t, deleted) 85 | } 86 | -------------------------------------------------------------------------------- /audiences.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net/http" 7 | ) 8 | 9 | type AudiencesSvc interface { 10 | CreateWithContext(ctx context.Context, params *CreateAudienceRequest) (CreateAudienceResponse, error) 11 | Create(params *CreateAudienceRequest) (CreateAudienceResponse, error) 12 | ListWithContext(ctx context.Context) (ListAudiencesResponse, error) 13 | List() (ListAudiencesResponse, error) 14 | GetWithContext(ctx context.Context, audienceId string) (Audience, error) 15 | Get(audienceId string) (Audience, error) 16 | RemoveWithContext(ctx context.Context, audienceId string) (RemoveAudienceResponse, error) 17 | Remove(audienceId string) (RemoveAudienceResponse, error) 18 | } 19 | 20 | type AudiencesSvcImpl struct { 21 | client *Client 22 | } 23 | 24 | type CreateAudienceRequest struct { 25 | Name string `json:"name"` 26 | } 27 | 28 | type CreateAudienceResponse struct { 29 | Id string `json:"id"` 30 | Name string `json:"name"` 31 | Object string `json:"object"` 32 | } 33 | 34 | type RemoveAudienceResponse struct { 35 | Id string `json:"id"` 36 | Object string `json:"object"` 37 | Deleted bool `json:"deleted"` 38 | } 39 | 40 | type ListAudiencesResponse struct { 41 | Object string `json:"object"` 42 | Data []Audience `json:"data"` 43 | } 44 | 45 | type Audience struct { 46 | Id string `json:"id"` 47 | Name string `json:"name"` 48 | Object string `json:"object"` 49 | CreatedAt string `json:"created_at"` 50 | } 51 | 52 | // CreateWithContext creates a new Audience entry based on the given params 53 | // https://resend.com/docs/api-reference/audiences/create-audience 54 | func (s *AudiencesSvcImpl) CreateWithContext(ctx context.Context, params *CreateAudienceRequest) (CreateAudienceResponse, error) { 55 | path := "audiences" 56 | 57 | // Prepare request 58 | req, err := s.client.NewRequest(ctx, http.MethodPost, path, params) 59 | if err != nil { 60 | return CreateAudienceResponse{}, errors.New("[ERROR]: Failed to create Audiences.Create request") 61 | } 62 | 63 | // Build response recipient obj 64 | audiencesResp := new(CreateAudienceResponse) 65 | 66 | // Send Request 67 | _, err = s.client.Perform(req, audiencesResp) 68 | 69 | if err != nil { 70 | return CreateAudienceResponse{}, err 71 | } 72 | 73 | return *audiencesResp, nil 74 | } 75 | 76 | // Create creates a new Audience entry based on the given params 77 | // https://resend.com/docs/api-reference/audiences/create-audience 78 | func (s *AudiencesSvcImpl) Create(params *CreateAudienceRequest) (CreateAudienceResponse, error) { 79 | return s.CreateWithContext(context.Background(), params) 80 | } 81 | 82 | // ListWithContext returns the list of all audiences 83 | // https://resend.com/docs/api-reference/audiences/list-audiences 84 | func (s *AudiencesSvcImpl) ListWithContext(ctx context.Context) (ListAudiencesResponse, error) { 85 | path := "audiences" 86 | 87 | // Prepare request 88 | req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 89 | if err != nil { 90 | return ListAudiencesResponse{}, errors.New("[ERROR]: Failed to create Audiences.List request") 91 | } 92 | 93 | audiences := new(ListAudiencesResponse) 94 | 95 | // Send Request 96 | _, err = s.client.Perform(req, audiences) 97 | 98 | if err != nil { 99 | return ListAudiencesResponse{}, err 100 | } 101 | 102 | return *audiences, nil 103 | } 104 | 105 | // List returns the list of all audiences 106 | // https://resend.com/docs/api-reference/audiences/list-audiences 107 | func (s *AudiencesSvcImpl) List() (ListAudiencesResponse, error) { 108 | return s.ListWithContext(context.Background()) 109 | } 110 | 111 | // RemoveWithContext removes a given audience by id 112 | // https://resend.com/docs/api-reference/audiences/delete-audience 113 | func (s *AudiencesSvcImpl) RemoveWithContext(ctx context.Context, audienceId string) (RemoveAudienceResponse, error) { 114 | path := "audiences/" + audienceId 115 | 116 | // Prepare request 117 | req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) 118 | if err != nil { 119 | return RemoveAudienceResponse{}, errors.New("[ERROR]: Failed to create Audience.Remove request") 120 | } 121 | 122 | resp := new(RemoveAudienceResponse) 123 | 124 | // Send Request 125 | _, err = s.client.Perform(req, resp) 126 | 127 | if err != nil { 128 | return RemoveAudienceResponse{}, err 129 | } 130 | 131 | return *resp, nil 132 | } 133 | 134 | // Remove removes a given audience entry by id 135 | // https://resend.com/docs/api-reference/audiences/delete-audience 136 | func (s *AudiencesSvcImpl) Remove(audienceId string) (RemoveAudienceResponse, error) { 137 | return s.RemoveWithContext(context.Background(), audienceId) 138 | } 139 | 140 | // GetWithContext Retrieve a single audience. 141 | // https://resend.com/docs/api-reference/audiences/get-audience 142 | func (s *AudiencesSvcImpl) GetWithContext(ctx context.Context, audienceId string) (Audience, error) { 143 | path := "audiences/" + audienceId 144 | 145 | // Prepare request 146 | req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 147 | if err != nil { 148 | return Audience{}, errors.New("[ERROR]: Failed to create Audience.Get request") 149 | } 150 | 151 | audience := new(Audience) 152 | 153 | // Send Request 154 | _, err = s.client.Perform(req, audience) 155 | 156 | if err != nil { 157 | return Audience{}, err 158 | } 159 | 160 | return *audience, nil 161 | } 162 | 163 | // Get Retrieve a single audience. 164 | // https://resend.com/docs/api-reference/audiences/get-audience 165 | func (s *AudiencesSvcImpl) Get(audienceId string) (Audience, error) { 166 | return s.GetWithContext(context.Background(), audienceId) 167 | } 168 | -------------------------------------------------------------------------------- /audiences_test.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCreateAudience(t *testing.T) { 12 | setup() 13 | defer teardown() 14 | 15 | mux.HandleFunc("/audiences", func(w http.ResponseWriter, r *http.Request) { 16 | testMethod(t, r, http.MethodPost) 17 | w.Header().Set("Content-Type", "application/json") 18 | w.WriteHeader(http.StatusCreated) 19 | 20 | var ret interface{} 21 | ret = ` 22 | { 23 | "object": "audience", 24 | "id": "78261eea-8f8b-4381-83c6-79fa7120f1c", 25 | "name": "Registered Users" 26 | }` 27 | 28 | fmt.Fprint(w, ret) 29 | }) 30 | 31 | req := &CreateAudienceRequest{ 32 | Name: "New Audience", 33 | } 34 | resp, err := client.Audiences.Create(req) 35 | if err != nil { 36 | t.Errorf("Audiences.Create returned error: %v", err) 37 | } 38 | assert.Equal(t, resp.Id, "78261eea-8f8b-4381-83c6-79fa7120f1c") 39 | assert.Equal(t, resp.Object, "audience") 40 | assert.Equal(t, resp.Name, "Registered Users") 41 | } 42 | 43 | func TestListAudiences(t *testing.T) { 44 | setup() 45 | defer teardown() 46 | 47 | mux.HandleFunc("/audiences", func(w http.ResponseWriter, r *http.Request) { 48 | testMethod(t, r, http.MethodGet) 49 | w.WriteHeader(http.StatusOK) 50 | 51 | ret := ` 52 | { 53 | "object": "list", 54 | "data": [ 55 | { 56 | "id": "d91cd9bd-1176-453e-8fc1-35364d380206", 57 | "name": "Registered Users", 58 | "created_at": "2023-04-26T20:21:26.347412+00:00" 59 | } 60 | ] 61 | }` 62 | 63 | fmt.Fprint(w, ret) 64 | }) 65 | 66 | audiences, err := client.Audiences.List() 67 | if err != nil { 68 | t.Errorf("Audiences.List returned error: %v", err) 69 | } 70 | 71 | assert.Equal(t, len(audiences.Data), 1) 72 | assert.Equal(t, audiences.Object, "list") 73 | assert.Equal(t, audiences.Data[0].Id, "d91cd9bd-1176-453e-8fc1-35364d380206") 74 | assert.Equal(t, audiences.Data[0].Name, "Registered Users") 75 | assert.Equal(t, audiences.Data[0].CreatedAt, "2023-04-26T20:21:26.347412+00:00") 76 | } 77 | 78 | func TestRemoveAudience(t *testing.T) { 79 | setup() 80 | defer teardown() 81 | 82 | mux.HandleFunc("/audiences/b6d24b8e-af0b-4c3c-be0c-359bbd97381e", func(w http.ResponseWriter, r *http.Request) { 83 | testMethod(t, r, http.MethodDelete) 84 | w.WriteHeader(http.StatusOK) 85 | 86 | var ret interface{} 87 | ret = ` 88 | { 89 | "object": "audience", 90 | "id": "b6d24b8e-af0b-4c3c-be0c-359bbd97381e", 91 | "deleted": true 92 | }` 93 | 94 | fmt.Fprint(w, ret) 95 | }) 96 | 97 | deleted, err := client.Audiences.Remove("b6d24b8e-af0b-4c3c-be0c-359bbd97381e") 98 | if err != nil { 99 | t.Errorf("Audiences.Remove returned error: %v", err) 100 | } 101 | assert.True(t, deleted.Deleted) 102 | assert.Equal(t, deleted.Id, "b6d24b8e-af0b-4c3c-be0c-359bbd97381e") 103 | assert.Equal(t, deleted.Object, "audience") 104 | } 105 | 106 | func TestGetAudience(t *testing.T) { 107 | setup() 108 | defer teardown() 109 | 110 | mux.HandleFunc("/audiences/d91cd9bd-1176-453e-8fc1-35364d380206", func(w http.ResponseWriter, r *http.Request) { 111 | testMethod(t, r, http.MethodGet) 112 | w.Header().Set("Content-Type", "application/json") 113 | w.WriteHeader(http.StatusOK) 114 | 115 | ret := ` 116 | { 117 | "object": "audience", 118 | "id": "d91cd9bd-1176-453e-8fc1-35364d380206", 119 | "name": "Registered Users", 120 | "created_at": "2023-10-06T22:59:55.977Z" 121 | }` 122 | 123 | fmt.Fprint(w, ret) 124 | }) 125 | 126 | audience, err := client.Audiences.Get("d91cd9bd-1176-453e-8fc1-35364d380206") 127 | if err != nil { 128 | t.Errorf("Audience.Get returned error: %v", err) 129 | } 130 | 131 | assert.Equal(t, audience.Id, "d91cd9bd-1176-453e-8fc1-35364d380206") 132 | assert.Equal(t, audience.Object, "audience") 133 | assert.Equal(t, audience.Name, "Registered Users") 134 | assert.Equal(t, audience.CreatedAt, "2023-10-06T22:59:55.977Z") 135 | } 136 | -------------------------------------------------------------------------------- /batch.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net/http" 7 | ) 8 | 9 | // BatchSendEmailOptions is the additional options struct for the Batch.SendEmail call. 10 | type BatchSendEmailOptions struct { 11 | IdempotencyKey string `json:"idempotency_key,omitempty"` 12 | } 13 | 14 | // GetIdempotencyKey returns the idempotency key for the batch send email request. 15 | func (o BatchSendEmailOptions) GetIdempotencyKey() string { 16 | return o.IdempotencyKey 17 | } 18 | 19 | // BatchEmailResponse is the response from the BatchSendEmail call. 20 | // see https://resend.com/docs/api-reference/emails/send-batch-emails 21 | type BatchEmailResponse struct { 22 | Data []SendEmailResponse `json:"data"` 23 | } 24 | 25 | type BatchSvc interface { 26 | Send([]*SendEmailRequest) (*BatchEmailResponse, error) 27 | SendWithContext(ctx context.Context, params []*SendEmailRequest) (*BatchEmailResponse, error) 28 | SendWithOptions(ctx context.Context, params []*SendEmailRequest, options *BatchSendEmailOptions) (*BatchEmailResponse, error) 29 | } 30 | 31 | type BatchSvcImpl struct { 32 | client *Client 33 | } 34 | 35 | // Send send a batch of emails 36 | // https://resend.com/docs/api-reference/emails/send-batch-emails 37 | func (s *BatchSvcImpl) Send(params []*SendEmailRequest) (*BatchEmailResponse, error) { 38 | return s.SendWithContext(context.Background(), params) 39 | } 40 | 41 | // SendWithContext is the same as Send but accepts a ctx as argument 42 | func (s *BatchSvcImpl) SendWithContext(ctx context.Context, params []*SendEmailRequest) (*BatchEmailResponse, error) { 43 | path := "emails/batch" 44 | 45 | // Prepare request 46 | req, err := s.client.NewRequest(ctx, http.MethodPost, path, params) 47 | if err != nil { 48 | return nil, errors.New("[ERROR]: Failed to create BatchEmail request") 49 | } 50 | 51 | // Build response recipient obj 52 | batchSendEmailResponse := new(BatchEmailResponse) 53 | 54 | // Send Request 55 | _, err = s.client.Perform(req, batchSendEmailResponse) 56 | 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | return batchSendEmailResponse, nil 62 | } 63 | 64 | // SendWithOptions is the same as Send but accepts a ctx and options as arguments 65 | func (s *BatchSvcImpl) SendWithOptions(ctx context.Context, params []*SendEmailRequest, options *BatchSendEmailOptions) (*BatchEmailResponse, error) { 66 | path := "emails/batch" 67 | 68 | // Prepare request 69 | req, err := s.client.NewRequestWithOptions(ctx, http.MethodPost, path, params, options) 70 | if err != nil { 71 | return nil, errors.New("[ERROR]: Failed to create BatchEmail request") 72 | } 73 | 74 | // Build response recipient obj 75 | batchSendEmailResponse := new(BatchEmailResponse) 76 | 77 | // Send Request 78 | _, err = s.client.Perform(req, batchSendEmailResponse) 79 | 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | return batchSendEmailResponse, nil 85 | } 86 | -------------------------------------------------------------------------------- /batch_test.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestBatchSendEmail(t *testing.T) { 13 | setup() 14 | defer teardown() 15 | 16 | mux.HandleFunc("/emails/batch", func(w http.ResponseWriter, r *http.Request) { 17 | testMethod(t, r, http.MethodPost) 18 | w.Header().Set("Content-Type", "application/json") 19 | w.WriteHeader(http.StatusOK) 20 | 21 | ret := &BatchEmailResponse{ 22 | Data: []SendEmailResponse{ 23 | {Id: "1"}, 24 | {Id: "2"}, 25 | }, 26 | } 27 | err := json.NewEncoder(w).Encode(&ret) 28 | if err != nil { 29 | panic(err) 30 | } 31 | }) 32 | 33 | req := []*SendEmailRequest{ 34 | { 35 | To: []string{"d@e.com"}, 36 | }, 37 | { 38 | To: []string{"d@e.com"}, 39 | }, 40 | } 41 | resp, err := client.Batch.Send(req) 42 | if err != nil { 43 | t.Errorf("BatchEmail.Send returned error: %v", err) 44 | } 45 | assert.Equal(t, resp.Data[0].Id, "1") 46 | assert.Equal(t, resp.Data[1].Id, "2") 47 | } 48 | 49 | func TestBatchSendWithOptionsEmail(t *testing.T) { 50 | setup() 51 | defer teardown() 52 | ctx := context.Background() 53 | 54 | mux.HandleFunc("/emails/batch", func(w http.ResponseWriter, r *http.Request) { 55 | testMethod(t, r, http.MethodPost) 56 | w.Header().Set("Content-Type", "application/json") 57 | w.WriteHeader(http.StatusOK) 58 | 59 | ret := &BatchEmailResponse{ 60 | Data: []SendEmailResponse{ 61 | {Id: "1"}, 62 | {Id: "2"}, 63 | }, 64 | } 65 | err := json.NewEncoder(w).Encode(&ret) 66 | if err != nil { 67 | panic(err) 68 | } 69 | }) 70 | 71 | req := []*SendEmailRequest{ 72 | { 73 | To: []string{"d@e.com"}, 74 | }, 75 | { 76 | To: []string{"d@e.com"}, 77 | }, 78 | } 79 | 80 | options := &BatchSendEmailOptions{ 81 | IdempotencyKey: "1234567890", 82 | } 83 | 84 | resp, err := client.Batch.SendWithOptions(ctx, req, options) 85 | if err != nil { 86 | t.Errorf("BatchEmail.SendWithOptions returned error: %v", err) 87 | } 88 | assert.Equal(t, resp.Data[0].Id, "1") 89 | assert.Equal(t, resp.Data[1].Id, "2") 90 | } 91 | -------------------------------------------------------------------------------- /broadcasts.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net/http" 7 | ) 8 | 9 | type SendBroadcastRequest struct { 10 | BroadcastId string `json:"broadcast_id"` 11 | 12 | //Schedule email to be sent later. The date should be in language natural (e.g.: in 1 min) 13 | // or ISO 8601 format (e.g: 2024-08-05T11:52:01.858Z). 14 | ScheduledAt string `json:"scheduled_at"` 15 | } 16 | 17 | type CreateBroadcastRequest struct { 18 | AudienceId string `json:"audience_id,omitempty"` 19 | From string `json:"from,omitempty"` 20 | Subject string `json:"subject,omitempty"` 21 | ReplyTo []string `json:"reply_to,omitempty"` 22 | Html string `json:"html,omitempty"` 23 | Text string `json:"text,omitempty"` 24 | Name string `json:"name,omitempty""` 25 | } 26 | 27 | type UpdateBroadcastRequest struct { 28 | BroadcastId string `json:"broadcast_id,omitempty"` 29 | AudienceId string `json:"audience_id,omitempty"` 30 | From string `json:"from,omitempty"` 31 | Subject string `json:"subject,omitempty"` 32 | ReplyTo []string `json:"reply_to,omitempty"` 33 | Html string `json:"html,omitempty"` 34 | Text string `json:"text,omitempty"` 35 | Name string `json:"name,omitempty"` 36 | } 37 | 38 | type CreateBroadcastResponse struct { 39 | Id string `json:"id"` 40 | } 41 | 42 | type UpdateBroadcastResponse struct { 43 | Id string `json:"id"` 44 | } 45 | 46 | type SendBroadcastResponse struct { 47 | Id string `json:"id"` 48 | } 49 | 50 | type RemoveBroadcastResponse struct { 51 | Object string `json:"object"` 52 | Id string `json:"id"` 53 | Deleted bool `json:"deleted"` 54 | } 55 | 56 | type ListBroadcastsResponse struct { 57 | Object string `json:"object"` 58 | Data []Broadcast `json:"data"` 59 | } 60 | 61 | type Broadcast struct { 62 | Object string `json:"object"` 63 | Id string `json:"id"` 64 | Name string `json:"name"` 65 | AudienceId string `json:"audience_id"` 66 | From string `json:"from"` 67 | Subject string `json:"subject"` 68 | ReplyTo []string `json:"reply_to"` 69 | PreviewText string `json:"preview_text"` 70 | Status string `json:"status"` 71 | CreatedAt string `json:"created_at"` 72 | ScheduledAt string `json:"scheduled_at"` 73 | SentAt string `json:"sent_at"` 74 | } 75 | 76 | type BroadcastsSvc interface { 77 | CreateWithContext(ctx context.Context, params *CreateBroadcastRequest) (CreateBroadcastResponse, error) 78 | Create(params *CreateBroadcastRequest) (CreateBroadcastResponse, error) 79 | 80 | UpdateWithContext(ctx context.Context, params *UpdateBroadcastRequest) (UpdateBroadcastResponse, error) 81 | Update(params *UpdateBroadcastRequest) (UpdateBroadcastResponse, error) 82 | 83 | ListWithContext(ctx context.Context) (ListBroadcastsResponse, error) 84 | List() (ListBroadcastsResponse, error) 85 | 86 | GetWithContext(ctx context.Context, broadcastId string) (Broadcast, error) 87 | Get(broadcastId string) (Broadcast, error) 88 | 89 | SendWithContext(ctx context.Context, params *SendBroadcastRequest) (SendBroadcastResponse, error) 90 | Send(params *SendBroadcastRequest) (SendBroadcastResponse, error) 91 | 92 | RemoveWithContext(ctx context.Context, broadcastId string) (RemoveBroadcastResponse, error) 93 | Remove(broadcastId string) (RemoveBroadcastResponse, error) 94 | } 95 | 96 | type BroadcastsSvcImpl struct { 97 | client *Client 98 | } 99 | 100 | // CreateWithContext creates a new Broadcast based on the given params 101 | // https://resend.com/docs/api-reference/broadcasts/create-broadcast 102 | func (s *BroadcastsSvcImpl) CreateWithContext(ctx context.Context, params *CreateBroadcastRequest) (CreateBroadcastResponse, error) { 103 | path := "/broadcasts" 104 | 105 | if params.AudienceId == "" { 106 | return CreateBroadcastResponse{}, errors.New("[ERROR]: AudienceId cannot be empty") 107 | } 108 | 109 | if params.From == "" { 110 | return CreateBroadcastResponse{}, errors.New("[ERROR]: From cannot be empty") 111 | } 112 | 113 | if params.Subject == "" { 114 | return CreateBroadcastResponse{}, errors.New("[ERROR]: Subject cannot be empty") 115 | } 116 | 117 | // Prepare request 118 | req, err := s.client.NewRequest(ctx, http.MethodPost, path, params) 119 | if err != nil { 120 | return CreateBroadcastResponse{}, ErrFailedToCreateBroadcastCreateRequest 121 | } 122 | 123 | // Build response recipient obj 124 | broadcastResp := new(CreateBroadcastResponse) 125 | 126 | // Send Request 127 | _, err = s.client.Perform(req, broadcastResp) 128 | 129 | if err != nil { 130 | return CreateBroadcastResponse{}, err 131 | } 132 | 133 | return *broadcastResp, nil 134 | } 135 | 136 | // Create creates a new Broadcast based on the given params 137 | func (s *BroadcastsSvcImpl) Create(params *CreateBroadcastRequest) (CreateBroadcastResponse, error) { 138 | return s.CreateWithContext(context.Background(), params) 139 | } 140 | 141 | // UpdateWithContext updates a given broadcast entry 142 | // https://resend.com/docs/api-reference/broadcasts/update-broadcast 143 | func (s *BroadcastsSvcImpl) UpdateWithContext(ctx context.Context, params *UpdateBroadcastRequest) (UpdateBroadcastResponse, error) { 144 | if params.BroadcastId == "" { 145 | return UpdateBroadcastResponse{}, errors.New("[ERROR]: BroadcastId cannot be empty") 146 | } 147 | 148 | path := "/broadcasts/" + params.BroadcastId 149 | 150 | // Prepare request 151 | req, err := s.client.NewRequest(ctx, http.MethodPatch, path, params) 152 | if err != nil { 153 | return UpdateBroadcastResponse{}, ErrFailedToCreateBroadcastUpdateRequest 154 | } 155 | 156 | // Build response recipient obj 157 | broadcastResp := new(UpdateBroadcastResponse) 158 | 159 | // Send Request 160 | _, err = s.client.Perform(req, broadcastResp) 161 | 162 | if err != nil { 163 | return UpdateBroadcastResponse{}, err 164 | } 165 | 166 | return *broadcastResp, nil 167 | } 168 | 169 | func (s *BroadcastsSvcImpl) Update(params *UpdateBroadcastRequest) (UpdateBroadcastResponse, error) { 170 | return s.UpdateWithContext(context.Background(), params) 171 | } 172 | 173 | // GetWithContext Retrieve a single broadcast. 174 | // https://resend.com/docs/api-reference/broadcasts/get-broadcast 175 | func (s *BroadcastsSvcImpl) GetWithContext(ctx context.Context, broadcastId string) (Broadcast, error) { 176 | 177 | if broadcastId == "" { 178 | return Broadcast{}, errors.New("[ERROR]: broadcastId cannot be empty") 179 | } 180 | 181 | path := "broadcasts/" + broadcastId 182 | 183 | // Prepare request 184 | req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 185 | if err != nil { 186 | return Broadcast{}, errors.New("[ERROR]: Failed to create Broadcast.Get request") 187 | } 188 | 189 | broadcast := new(Broadcast) 190 | 191 | // Send Request 192 | _, err = s.client.Perform(req, broadcast) 193 | 194 | if err != nil { 195 | return Broadcast{}, err 196 | } 197 | 198 | return *broadcast, nil 199 | } 200 | 201 | // Get retrieves a single broadcast. 202 | func (s *BroadcastsSvcImpl) Get(broadcastId string) (Broadcast, error) { 203 | return s.GetWithContext(context.Background(), broadcastId) 204 | } 205 | 206 | // SendWithContext Sends broadcasts to your audience. 207 | // https://resend.com/docs/api-reference/broadcasts/send-broadcast 208 | func (s *BroadcastsSvcImpl) SendWithContext(ctx context.Context, params *SendBroadcastRequest) (SendBroadcastResponse, error) { 209 | if params.BroadcastId == "" { 210 | return SendBroadcastResponse{}, errors.New("[ERROR]: BroadcastId cannot be empty") 211 | } 212 | 213 | path := "/broadcasts/" + params.BroadcastId + "/send" 214 | 215 | // Prepare request 216 | req, err := s.client.NewRequest(ctx, http.MethodPost, path, params) 217 | if err != nil { 218 | return SendBroadcastResponse{}, ErrFailedToCreateBroadcastSendRequest 219 | } 220 | 221 | // Build response recipient obj 222 | broadcastResp := new(SendBroadcastResponse) 223 | 224 | // Send Request 225 | _, err = s.client.Perform(req, broadcastResp) 226 | 227 | if err != nil { 228 | return SendBroadcastResponse{}, err 229 | } 230 | 231 | return *broadcastResp, nil 232 | } 233 | 234 | // Send sends broadcasts to your audience. 235 | func (s *BroadcastsSvcImpl) Send(params *SendBroadcastRequest) (SendBroadcastResponse, error) { 236 | return s.SendWithContext(context.Background(), params) 237 | } 238 | 239 | // RemoveWithContext removes a given broadcast by id 240 | // https://resend.com/docs/api-reference/broadcasts/delete-broadcast 241 | func (s *BroadcastsSvcImpl) RemoveWithContext(ctx context.Context, broadcastId string) (RemoveBroadcastResponse, error) { 242 | path := "broadcasts/" + broadcastId 243 | 244 | // Prepare request 245 | req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) 246 | if err != nil { 247 | return RemoveBroadcastResponse{}, errors.New("[ERROR]: Failed to create Broadcast.Remove request") 248 | } 249 | 250 | resp := new(RemoveBroadcastResponse) 251 | 252 | // Send Request 253 | _, err = s.client.Perform(req, resp) 254 | 255 | if err != nil { 256 | return RemoveBroadcastResponse{}, err 257 | } 258 | 259 | return *resp, nil 260 | } 261 | 262 | // Remove removes a given broadcast entry by id 263 | func (s *BroadcastsSvcImpl) Remove(broadcastId string) (RemoveBroadcastResponse, error) { 264 | return s.RemoveWithContext(context.Background(), broadcastId) 265 | } 266 | 267 | // ListWithContext returns the list of all broadcasts 268 | // https://resend.com/docs/api-reference/broadcasts/list-broadcasts 269 | func (s *BroadcastsSvcImpl) ListWithContext(ctx context.Context) (ListBroadcastsResponse, error) { 270 | path := "broadcasts" 271 | 272 | // Prepare request 273 | req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 274 | if err != nil { 275 | return ListBroadcastsResponse{}, errors.New("[ERROR]: Failed to create Broadcasts.List request") 276 | } 277 | 278 | broadcasts := new(ListBroadcastsResponse) 279 | 280 | // Send Request 281 | _, err = s.client.Perform(req, broadcasts) 282 | 283 | if err != nil { 284 | return ListBroadcastsResponse{}, err 285 | } 286 | 287 | return *broadcasts, nil 288 | } 289 | 290 | // List returns the list of all broadcasts 291 | func (s *BroadcastsSvcImpl) List() (ListBroadcastsResponse, error) { 292 | return s.ListWithContext(context.Background()) 293 | } 294 | -------------------------------------------------------------------------------- /broadcasts_test.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCreateBroadcast(t *testing.T) { 12 | setup() 13 | defer teardown() 14 | 15 | mux.HandleFunc("/broadcasts", func(w http.ResponseWriter, r *http.Request) { 16 | testMethod(t, r, http.MethodPost) 17 | w.Header().Set("Content-Type", "application/json") 18 | w.WriteHeader(http.StatusCreated) 19 | 20 | var ret interface{} 21 | ret = ` 22 | { 23 | "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" 24 | }` 25 | 26 | fmt.Fprint(w, ret) 27 | }) 28 | 29 | req := &CreateBroadcastRequest{ 30 | Name: "New Broadcast", 31 | AudienceId: "709d076c-2bb1-4be6-94ed-3f8f32622db6", 32 | From: "hi@example.com", 33 | Subject: "Hello, world!", 34 | } 35 | resp, err := client.Broadcasts.Create(req) 36 | if err != nil { 37 | t.Errorf("Broadcasts.Create returned error: %v", err) 38 | } 39 | assert.Equal(t, resp.Id, "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794") 40 | } 41 | 42 | func TestUpdateBroadcast(t *testing.T) { 43 | setup() 44 | defer teardown() 45 | 46 | mux.HandleFunc("/broadcasts/559ac32e-9ef5-46fb-82a1-b76b840c0f7b", func(w http.ResponseWriter, r *http.Request) { 47 | testMethod(t, r, http.MethodPatch) 48 | w.Header().Set("Content-Type", "application/json") 49 | w.WriteHeader(http.StatusOK) 50 | 51 | ret := ` 52 | { 53 | "id": "559ac32e-9ef5-46fb-82a1-b76b840c0f7b" 54 | }` 55 | 56 | fmt.Fprint(w, ret) 57 | }) 58 | 59 | req := &UpdateBroadcastRequest{ 60 | BroadcastId: "559ac32e-9ef5-46fb-82a1-b76b840c0f7b", 61 | Name: "Updated Broadcast", 62 | } 63 | resp, err := client.Broadcasts.Update(req) 64 | if err != nil { 65 | t.Errorf("Broadcasts.Update returned error: %v", err) 66 | } 67 | assert.Equal(t, resp.Id, "559ac32e-9ef5-46fb-82a1-b76b840c0f7b") 68 | } 69 | 70 | func TestCreateBroadcastValidations(t *testing.T) { 71 | setup() 72 | defer teardown() 73 | 74 | req1 := &CreateBroadcastRequest{ 75 | Name: "New Broadcast", 76 | AudienceId: "709d076c-2bb1-4be6-94ed-3f8f32622db6", 77 | From: "", 78 | } 79 | _, err := client.Broadcasts.Create(req1) 80 | assert.NotNil(t, err) 81 | if err != nil { 82 | assert.Equal(t, err.Error(), "[ERROR]: From cannot be empty") 83 | } 84 | 85 | req2 := &CreateBroadcastRequest{ 86 | Name: "New Broadcast", 87 | From: "hi@example.com", 88 | } 89 | _, err = client.Broadcasts.Create(req2) 90 | assert.NotNil(t, err) 91 | if err != nil { 92 | assert.Equal(t, err.Error(), "[ERROR]: AudienceId cannot be empty") 93 | } 94 | 95 | req3 := &CreateBroadcastRequest{ 96 | Name: "New Broadcast", 97 | From: "hi@example.com", 98 | AudienceId: "709d076c-2bb1-4be6-94ed-3f8f32622db6", 99 | Subject: "", 100 | } 101 | _, err = client.Broadcasts.Create(req3) 102 | assert.NotNil(t, err) 103 | if err != nil { 104 | assert.Equal(t, err.Error(), "[ERROR]: Subject cannot be empty") 105 | } 106 | } 107 | 108 | func TestGetBroadcast(t *testing.T) { 109 | setup() 110 | defer teardown() 111 | 112 | mux.HandleFunc("/broadcasts/559ac32e-9ef5-46fb-82a1-b76b840c0f7b", func(w http.ResponseWriter, r *http.Request) { 113 | testMethod(t, r, http.MethodGet) 114 | w.Header().Set("Content-Type", "application/json") 115 | w.WriteHeader(http.StatusOK) 116 | 117 | ret := ` 118 | { 119 | "object": "broadcast", 120 | "id": "559ac32e-9ef5-46fb-82a1-b76b840c0f7b", 121 | "name": "Announcements", 122 | "audience_id": "78261eea-8f8b-4381-83c6-79fa7120f1cf", 123 | "from": "Acme ", 124 | "subject": "hello world", 125 | "reply_to": null, 126 | "preview_text": "Check out our latest announcements", 127 | "status": "draft", 128 | "created_at": "2024-12-01T19:32:22.980Z", 129 | "scheduled_at": null, 130 | "sent_at": null 131 | }` 132 | 133 | fmt.Fprint(w, ret) 134 | }) 135 | 136 | b, err := client.Broadcasts.Get("559ac32e-9ef5-46fb-82a1-b76b840c0f7b") 137 | if err != nil { 138 | t.Errorf("Broadcast.Get returned error: %v", err) 139 | } 140 | 141 | assert.Equal(t, b.Id, "559ac32e-9ef5-46fb-82a1-b76b840c0f7b") 142 | assert.Equal(t, b.Object, "broadcast") 143 | assert.Equal(t, b.Name, "Announcements") 144 | assert.Equal(t, b.AudienceId, "78261eea-8f8b-4381-83c6-79fa7120f1cf") 145 | assert.Equal(t, b.From, "Acme ") 146 | assert.Equal(t, b.Subject, "hello world") 147 | assert.Equal(t, b.PreviewText, "Check out our latest announcements") 148 | assert.Equal(t, b.Status, "draft") 149 | assert.Equal(t, b.CreatedAt, "2024-12-01T19:32:22.980Z") 150 | } 151 | 152 | func TestGetBroadcastValidations(t *testing.T) { 153 | setup() 154 | defer teardown() 155 | 156 | _, err := client.Broadcasts.Get("") 157 | assert.NotNil(t, err) 158 | if err != nil { 159 | assert.Equal(t, err.Error(), "[ERROR]: broadcastId cannot be empty") 160 | } 161 | } 162 | 163 | func TestSendBroadcast(t *testing.T) { 164 | setup() 165 | defer teardown() 166 | 167 | mux.HandleFunc("/broadcasts/559ac32e-9ef5-46fb-82a1-b76b840c0f7b/send", func(w http.ResponseWriter, r *http.Request) { 168 | testMethod(t, r, http.MethodPost) 169 | w.Header().Set("Content-Type", "application/json") 170 | w.WriteHeader(http.StatusOK) 171 | 172 | ret := ` 173 | { 174 | "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" 175 | }` 176 | 177 | fmt.Fprint(w, ret) 178 | }) 179 | 180 | req := &SendBroadcastRequest{ 181 | BroadcastId: "559ac32e-9ef5-46fb-82a1-b76b840c0f7b", 182 | } 183 | 184 | b, err := client.Broadcasts.Send(req) 185 | if err != nil { 186 | t.Errorf("Broadcast.Send returned error: %v", err) 187 | } 188 | 189 | assert.Equal(t, b.Id, "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794") 190 | } 191 | 192 | func TestSendBroadcastValidations(t *testing.T) { 193 | setup() 194 | defer teardown() 195 | 196 | req1 := &SendBroadcastRequest{ 197 | BroadcastId: "", 198 | } 199 | 200 | _, err := client.Broadcasts.Send(req1) 201 | assert.NotNil(t, err) 202 | if err != nil { 203 | assert.Equal(t, err.Error(), "[ERROR]: BroadcastId cannot be empty") 204 | } 205 | } 206 | 207 | func TestRemoveBroadcast(t *testing.T) { 208 | setup() 209 | defer teardown() 210 | 211 | mux.HandleFunc("/broadcasts/b6d24b8e-af0b-4c3c-be0c-359bbd97381e", func(w http.ResponseWriter, r *http.Request) { 212 | testMethod(t, r, http.MethodDelete) 213 | w.WriteHeader(http.StatusOK) 214 | 215 | var ret interface{} 216 | ret = ` 217 | { 218 | "object": "broadcast", 219 | "id": "b6d24b8e-af0b-4c3c-be0c-359bbd97381e", 220 | "deleted": true 221 | }` 222 | 223 | fmt.Fprint(w, ret) 224 | }) 225 | 226 | deleted, err := client.Broadcasts.Remove("b6d24b8e-af0b-4c3c-be0c-359bbd97381e") 227 | if err != nil { 228 | t.Errorf("Broadcasts.Remove returned error: %v", err) 229 | } 230 | assert.True(t, deleted.Deleted) 231 | assert.Equal(t, deleted.Id, "b6d24b8e-af0b-4c3c-be0c-359bbd97381e") 232 | assert.Equal(t, deleted.Object, "broadcast") 233 | } 234 | 235 | func TestListBroadcasts(t *testing.T) { 236 | setup() 237 | defer teardown() 238 | 239 | mux.HandleFunc("/broadcasts", func(w http.ResponseWriter, r *http.Request) { 240 | testMethod(t, r, http.MethodGet) 241 | w.WriteHeader(http.StatusOK) 242 | 243 | ret := ` 244 | { 245 | "object": "list", 246 | "data": [ 247 | { 248 | "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794", 249 | "audience_id": "78261eea-8f8b-4381-83c6-79fa7120f1cf", 250 | "status": "draft", 251 | "created_at": "2024-11-01T15:13:31.723Z", 252 | "scheduled_at": null, 253 | "sent_at": null 254 | }, 255 | { 256 | "id": "559ac32e-9ef5-46fb-82a1-b76b840c0f7b", 257 | "audience_id": "78261eea-8f8b-4381-83c6-79fa7120f1cf", 258 | "status": "sent", 259 | "created_at": "2024-12-01T19:32:22.980Z", 260 | "scheduled_at": "2024-12-02T19:32:22.980Z", 261 | "sent_at": "2024-12-02T19:32:22.980Z" 262 | } 263 | ] 264 | }` 265 | 266 | fmt.Fprint(w, ret) 267 | }) 268 | 269 | broadcasts, err := client.Broadcasts.List() 270 | if err != nil { 271 | t.Errorf("Broadcasts.List returned error: %v", err) 272 | } 273 | 274 | assert.Equal(t, len(broadcasts.Data), 2) 275 | assert.Equal(t, broadcasts.Object, "list") 276 | 277 | } 278 | -------------------------------------------------------------------------------- /contacts.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net/http" 7 | ) 8 | 9 | type ContactsSvc interface { 10 | CreateWithContext(ctx context.Context, params *CreateContactRequest) (CreateContactResponse, error) 11 | Create(params *CreateContactRequest) (CreateContactResponse, error) 12 | ListWithContext(ctx context.Context, audienceId string) (ListContactsResponse, error) 13 | List(audienceId string) (ListContactsResponse, error) 14 | GetWithContext(ctx context.Context, audienceId, id string) (Contact, error) 15 | Get(audienceId, id string) (Contact, error) 16 | RemoveWithContext(ctx context.Context, audienceId, id string) (RemoveContactResponse, error) 17 | Remove(audienceId, id string) (RemoveContactResponse, error) 18 | UpdateWithContext(ctx context.Context, params *UpdateContactRequest) (UpdateContactResponse, error) 19 | Update(params *UpdateContactRequest) (UpdateContactResponse, error) 20 | } 21 | 22 | type ContactsSvcImpl struct { 23 | client *Client 24 | } 25 | 26 | type CreateContactRequest struct { 27 | Email string `json:"email"` 28 | AudienceId string `json:"audience_id"` 29 | Unsubscribed bool `json:"unsubscribed,omitempty"` 30 | FirstName string `json:"first_name,omitempty"` 31 | LastName string `json:"last_name,omitempty"` 32 | } 33 | 34 | type UpdateContactRequest struct { 35 | Id string `json:"id"` 36 | Email string `json:"email,omitempty"` 37 | AudienceId string `json:"audience_id"` 38 | FirstName string `json:"first_name,omitempty"` 39 | LastName string `json:"last_name,omitempty"` 40 | Unsubscribed bool `json:"unsubscribed,omitempty"` 41 | } 42 | 43 | type UpdateContactResponse struct { 44 | Data Contact `json:"data"` 45 | Error struct{} `json:"error"` // Fix this 46 | } 47 | 48 | type CreateContactResponse struct { 49 | Object string `json:"object"` 50 | Id string `json:"id"` 51 | } 52 | 53 | type RemoveContactResponse struct { 54 | Id string `json:"id"` 55 | Object string `json:"object"` 56 | Deleted bool `json:"deleted"` 57 | } 58 | 59 | type ListContactsResponse struct { 60 | Object string `json:"object"` 61 | Data []Contact `json:"data"` 62 | } 63 | 64 | type Contact struct { 65 | Id string `json:"id"` 66 | Email string `json:"email"` 67 | Object string `json:"object"` 68 | FirstName string `json:"first_name"` 69 | LastName string `json:"last_name"` 70 | CreatedAt string `json:"created_at"` 71 | Unsubscribed bool `json:"unsubscribed"` 72 | } 73 | 74 | // CreateWithContext creates a new Contact based on the given params 75 | // https://resend.com/docs/api-reference/contacts/create-contact 76 | func (s *ContactsSvcImpl) CreateWithContext(ctx context.Context, params *CreateContactRequest) (CreateContactResponse, error) { 77 | if params.AudienceId == "" { 78 | return CreateContactResponse{}, errors.New("[ERROR]: AudienceId is missing") 79 | } 80 | 81 | path := "audiences/" + params.AudienceId + "/contacts" 82 | 83 | // Prepare request 84 | req, err := s.client.NewRequest(ctx, http.MethodPost, path, params) 85 | if err != nil { 86 | return CreateContactResponse{}, errors.New("[ERROR]: Failed to create Contacts.Create request") 87 | } 88 | 89 | // Build response recipient obj 90 | contactsResp := new(CreateContactResponse) 91 | 92 | // Send Request 93 | _, err = s.client.Perform(req, contactsResp) 94 | 95 | if err != nil { 96 | return CreateContactResponse{}, err 97 | } 98 | 99 | return *contactsResp, nil 100 | } 101 | 102 | // Create creates a new Contact entry based on the given params 103 | // https://resend.com/docs/api-reference/contacts/create-contact 104 | func (s *ContactsSvcImpl) Create(params *CreateContactRequest) (CreateContactResponse, error) { 105 | return s.CreateWithContext(context.Background(), params) 106 | } 107 | 108 | // ListWithContext returns the list of all contacts in an audience 109 | // https://resend.com/docs/api-reference/contacts/list-contacts 110 | func (s *ContactsSvcImpl) ListWithContext(ctx context.Context, audienceId string) (ListContactsResponse, error) { 111 | path := "audiences/" + audienceId + "/contacts" 112 | 113 | // Prepare request 114 | req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 115 | if err != nil { 116 | return ListContactsResponse{}, errors.New("[ERROR]: Failed to create Contacts.List request") 117 | } 118 | 119 | contacts := new(ListContactsResponse) 120 | 121 | // Send Request 122 | _, err = s.client.Perform(req, contacts) 123 | 124 | if err != nil { 125 | return ListContactsResponse{}, err 126 | } 127 | 128 | return *contacts, nil 129 | } 130 | 131 | // List returns the list of all contacts in an audience 132 | // https://resend.com/docs/api-reference/contacts/list-contacts 133 | func (s *ContactsSvcImpl) List(audienceId string) (ListContactsResponse, error) { 134 | return s.ListWithContext(context.Background(), audienceId) 135 | } 136 | 137 | // RemoveWithContext same as Remove but with context 138 | // https://resend.com/docs/api-reference/contacts/delete-contact 139 | func (s *ContactsSvcImpl) RemoveWithContext(ctx context.Context, audienceId, id string) (RemoveContactResponse, error) { 140 | path := "audiences/" + audienceId + "/contacts/" + id 141 | 142 | // Prepare request 143 | req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) 144 | if err != nil { 145 | return RemoveContactResponse{}, errors.New("[ERROR]: Failed to create Contact.Remove request") 146 | } 147 | 148 | resp := new(RemoveContactResponse) 149 | 150 | // Send Request 151 | _, err = s.client.Perform(req, resp) 152 | 153 | if err != nil { 154 | return RemoveContactResponse{}, err 155 | } 156 | 157 | return *resp, nil 158 | } 159 | 160 | // Remove removes a given contact entry by id or email 161 | // 162 | // @param [id] - can be either a contact id or email 163 | // 164 | // https://resend.com/docs/api-reference/contacts/delete-contact 165 | func (s *ContactsSvcImpl) Remove(audienceId, id string) (RemoveContactResponse, error) { 166 | return s.RemoveWithContext(context.Background(), audienceId, id) 167 | } 168 | 169 | // GetWithContext Retrieve a single contact. 170 | // This method can be used to retrieve a contact by either its ID or email address. 171 | // 172 | // @param [id] - can be either a contact id or email 173 | // 174 | // https://resend.com/docs/api-reference/contacts/get-contact 175 | func (s *ContactsSvcImpl) GetWithContext(ctx context.Context, audienceId, id string) (Contact, error) { 176 | path := "audiences/" + audienceId + "/contacts/" + id 177 | 178 | // Prepare request 179 | req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 180 | if err != nil { 181 | return Contact{}, errors.New("[ERROR]: Failed to create Contact.Get request") 182 | } 183 | 184 | contact := new(Contact) 185 | 186 | // Send Request 187 | _, err = s.client.Perform(req, contact) 188 | 189 | if err != nil { 190 | return Contact{}, err 191 | } 192 | 193 | return *contact, nil 194 | } 195 | 196 | // Get Retrieve a single contact. 197 | // This method can be used to retrieve a contact by either its ID or email address. 198 | // 199 | // @param [id] - can be either a contact id or email 200 | // https://resend.com/docs/api-reference/contacts/get-contact 201 | func (s *ContactsSvcImpl) Get(audienceId, id string) (Contact, error) { 202 | return s.GetWithContext(context.Background(), audienceId, id) 203 | } 204 | 205 | // UpdateWithContext updates an existing Contact based on the given params 206 | // https://resend.com/docs/api-reference/contacts/update-contact 207 | func (s *ContactsSvcImpl) UpdateWithContext(ctx context.Context, params *UpdateContactRequest) (UpdateContactResponse, error) { 208 | if params.AudienceId == "" { 209 | return UpdateContactResponse{}, errors.New("[ERROR]: AudienceId is missing") 210 | } 211 | 212 | if params.Id == "" && params.Email == "" { 213 | return UpdateContactResponse{}, &MissingRequiredFieldsError{message: "[ERROR]: Missing `id` or `email` field."} 214 | } 215 | 216 | var val string 217 | if params.Id != "" { 218 | val = params.Id 219 | } else { 220 | val = params.Email 221 | } 222 | 223 | path := "audiences/" + params.AudienceId + "/contacts/" + val 224 | 225 | // Prepare request 226 | req, err := s.client.NewRequest(ctx, http.MethodPatch, path, params) 227 | if err != nil { 228 | return UpdateContactResponse{}, errors.New("[ERROR]: Failed to create Contacts.Update request") 229 | } 230 | 231 | // Build response recipient obj 232 | contactsResp := new(UpdateContactResponse) 233 | 234 | // Send Request 235 | _, err = s.client.Perform(req, contactsResp) 236 | 237 | if err != nil { 238 | return UpdateContactResponse{}, err 239 | } 240 | 241 | return *contactsResp, nil 242 | } 243 | 244 | // UpdateWithContext updates an existing Contact based on the given params 245 | // https://resend.com/docs/api-reference/contacts/update-contact 246 | func (s *ContactsSvcImpl) Update(params *UpdateContactRequest) (UpdateContactResponse, error) { 247 | return s.UpdateWithContext(context.Background(), params) 248 | } 249 | -------------------------------------------------------------------------------- /contacts_test.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCreateContact(t *testing.T) { 12 | setup() 13 | defer teardown() 14 | 15 | audienceId := "709d076c-2bb1-4be6-94ed-3f8f32622db6" 16 | 17 | mux.HandleFunc("/audiences/"+audienceId+"/contacts", func(w http.ResponseWriter, r *http.Request) { 18 | testMethod(t, r, http.MethodPost) 19 | w.Header().Set("Content-Type", "application/json") 20 | w.WriteHeader(http.StatusCreated) 21 | 22 | var ret interface{} 23 | ret = ` 24 | { 25 | "object": "contact", 26 | "id": "479e3145-dd38-476b-932c-529ceb705947" 27 | }` 28 | 29 | fmt.Fprint(w, ret) 30 | }) 31 | 32 | req := &CreateContactRequest{ 33 | Email: "email@example.com", 34 | AudienceId: audienceId, 35 | } 36 | resp, err := client.Contacts.Create(req) 37 | if err != nil { 38 | t.Errorf("Contacts.Create returned error: %v", err) 39 | } 40 | assert.Equal(t, resp.Object, "contact") 41 | assert.Equal(t, resp.Id, "479e3145-dd38-476b-932c-529ceb705947") 42 | } 43 | 44 | func TestListContacts(t *testing.T) { 45 | setup() 46 | defer teardown() 47 | 48 | audienceId := "709d076c-2bb1-4be6-94ed-3f8f32622db6" 49 | 50 | mux.HandleFunc("/audiences/"+audienceId+"/contacts", func(w http.ResponseWriter, r *http.Request) { 51 | testMethod(t, r, http.MethodGet) 52 | w.WriteHeader(http.StatusOK) 53 | 54 | ret := ` 55 | { 56 | "object": "list", 57 | "data": [ 58 | { 59 | "id": "e169aa45-1ecf-4183-9955-b1499d5701d3", 60 | "email": "steve.wozniak@gmail.com", 61 | "first_name": "Steve", 62 | "last_name": "Wozniak", 63 | "created_at": "2023-10-06T23:47:56.678Z", 64 | "unsubscribed": false 65 | } 66 | ] 67 | }` 68 | 69 | fmt.Fprint(w, ret) 70 | }) 71 | 72 | contacts, err := client.Contacts.List(audienceId) 73 | if err != nil { 74 | t.Errorf("Contacts.List returned error: %v", err) 75 | } 76 | 77 | assert.Equal(t, len(contacts.Data), 1) 78 | assert.Equal(t, contacts.Data[0].Id, "e169aa45-1ecf-4183-9955-b1499d5701d3") 79 | assert.Equal(t, contacts.Data[0].FirstName, "Steve") 80 | assert.Equal(t, contacts.Data[0].LastName, "Wozniak") 81 | assert.Equal(t, contacts.Data[0].CreatedAt, "2023-10-06T23:47:56.678Z") 82 | assert.Equal(t, contacts.Data[0].Unsubscribed, false) 83 | } 84 | 85 | func TestRemoveContact(t *testing.T) { 86 | setup() 87 | defer teardown() 88 | 89 | audienceId := "709d076c-2bb1-4be6-94ed-3f8f32622db6" 90 | contactId := "e169aa45-1ecf-4183-9955-b1499d5701d3" 91 | 92 | mux.HandleFunc("/audiences/"+audienceId+"/contacts/"+contactId, func(w http.ResponseWriter, r *http.Request) { 93 | testMethod(t, r, http.MethodDelete) 94 | w.WriteHeader(http.StatusOK) 95 | 96 | var ret interface{} 97 | ret = ` 98 | { 99 | "object": "contact", 100 | "id": "e169aa45-1ecf-4183-9955-b1499d5701d3", 101 | "deleted": true 102 | }` 103 | 104 | fmt.Fprint(w, ret) 105 | }) 106 | 107 | deleted, err := client.Contacts.Remove(audienceId, contactId) 108 | if err != nil { 109 | t.Errorf("Domains.Remove returned error: %v", err) 110 | } 111 | assert.True(t, deleted.Deleted) 112 | } 113 | 114 | func TestGetContact(t *testing.T) { 115 | setup() 116 | defer teardown() 117 | 118 | audienceId := "709d076c-2bb1-4be6-94ed-3f8f32622db6" 119 | contactId := "e169aa45-1ecf-4183-9955-b1499d5701d3" 120 | 121 | mux.HandleFunc("/audiences/"+audienceId+"/contacts/"+contactId, func(w http.ResponseWriter, r *http.Request) { 122 | testMethod(t, r, http.MethodGet) 123 | w.Header().Set("Content-Type", "application/json") 124 | w.WriteHeader(http.StatusOK) 125 | 126 | ret := ` 127 | { 128 | "object": "contact", 129 | "id": "e169aa45-1ecf-4183-9955-b1499d5701d3", 130 | "email": "steve.wozniak@gmail.com", 131 | "first_name": "Steve", 132 | "last_name": "Wozniak", 133 | "created_at": "2023-10-06T23:47:56.678Z", 134 | "unsubscribed": false 135 | }` 136 | 137 | fmt.Fprint(w, ret) 138 | }) 139 | 140 | contact, err := client.Contacts.Get(audienceId, contactId) 141 | if err != nil { 142 | t.Errorf("Contacts.Get returned error: %v", err) 143 | } 144 | 145 | assert.Equal(t, contact.Id, contactId) 146 | assert.Equal(t, contact.Object, "contact") 147 | assert.Equal(t, contact.FirstName, "Steve") 148 | assert.Equal(t, contact.LastName, "Wozniak") 149 | assert.Equal(t, contact.CreatedAt, "2023-10-06T23:47:56.678Z") 150 | assert.Equal(t, contact.Unsubscribed, false) 151 | } 152 | 153 | func TestGetContactByEmail(t *testing.T) { 154 | setup() 155 | defer teardown() 156 | 157 | audienceId := "709d076c-2bb1-4be6-94ed-3f8f32622db6" 158 | contactEmail := "steve.wozniak@gmail.com" 159 | 160 | mux.HandleFunc("/audiences/"+audienceId+"/contacts/"+contactEmail, func(w http.ResponseWriter, r *http.Request) { 161 | testMethod(t, r, http.MethodGet) 162 | w.Header().Set("Content-Type", "application/json") 163 | w.WriteHeader(http.StatusOK) 164 | 165 | ret := ` 166 | { 167 | "object": "contact", 168 | "id": "e169aa45-1ecf-4183-9955-b1499d5701d3", 169 | "email": "steve.wozniak@gmail.com", 170 | "first_name": "Steve", 171 | "last_name": "Wozniak", 172 | "created_at": "2023-10-06T23:47:56.678Z", 173 | "unsubscribed": false 174 | }` 175 | 176 | fmt.Fprint(w, ret) 177 | }) 178 | 179 | contact, err := client.Contacts.Get(audienceId, contactEmail) 180 | if err != nil { 181 | t.Errorf("Contacts.Get returned error: %v", err) 182 | } 183 | 184 | assert.Equal(t, contact.Id, "e169aa45-1ecf-4183-9955-b1499d5701d3") 185 | assert.Equal(t, contact.Object, "contact") 186 | assert.Equal(t, contact.FirstName, "Steve") 187 | assert.Equal(t, contact.LastName, "Wozniak") 188 | assert.Equal(t, contact.Email, contactEmail) 189 | assert.Equal(t, contact.CreatedAt, "2023-10-06T23:47:56.678Z") 190 | assert.Equal(t, contact.Unsubscribed, false) 191 | } 192 | 193 | func TestUpdateContactById(t *testing.T) { 194 | setup() 195 | defer teardown() 196 | 197 | audienceId := "709d076c-2bb1-4be6-94ed-3f8f32622db6" 198 | id := "109d077c-2bb1-4be6-84ed-ff8f32612dc2" 199 | 200 | mux.HandleFunc("/audiences/"+audienceId+"/contacts/"+id, func(w http.ResponseWriter, r *http.Request) { 201 | testMethod(t, r, http.MethodPatch) 202 | w.Header().Set("Content-Type", "application/json") 203 | w.WriteHeader(http.StatusCreated) 204 | 205 | var ret interface{} 206 | ret = ` 207 | { 208 | "data": { 209 | "id": "479e3145-dd38-476b-932c-529ceb705947" 210 | }, 211 | "error": null 212 | }` 213 | 214 | fmt.Fprint(w, ret) 215 | }) 216 | 217 | req := &UpdateContactRequest{ 218 | AudienceId: audienceId, 219 | Id: id, 220 | FirstName: "Updated First Name", 221 | } 222 | resp, err := client.Contacts.Update(req) 223 | if err != nil { 224 | t.Errorf("Contacts.Update returned error: %v", err) 225 | } 226 | assert.NotNil(t, resp.Data) 227 | assert.Equal(t, resp.Data.Id, "479e3145-dd38-476b-932c-529ceb705947") 228 | assert.Equal(t, resp.Error, struct{}{}) 229 | } 230 | 231 | func TestUpdateContactByEmail(t *testing.T) { 232 | setup() 233 | defer teardown() 234 | 235 | audienceId := "709d076c-2bb1-4be6-94ed-3f8f32622db6" 236 | email := "hi@resend.com" 237 | 238 | mux.HandleFunc("/audiences/"+audienceId+"/contacts/"+email, func(w http.ResponseWriter, r *http.Request) { 239 | testMethod(t, r, http.MethodPatch) 240 | w.Header().Set("Content-Type", "application/json") 241 | w.WriteHeader(http.StatusCreated) 242 | 243 | var ret interface{} 244 | ret = ` 245 | { 246 | "data": { 247 | "id": "479e3145-dd38-476b-932c-529ceb705947" 248 | }, 249 | "error": null 250 | }` 251 | 252 | fmt.Fprint(w, ret) 253 | }) 254 | 255 | req := &UpdateContactRequest{ 256 | AudienceId: audienceId, 257 | Email: email, 258 | FirstName: "Updated First Name", 259 | } 260 | resp, err := client.Contacts.Update(req) 261 | if err != nil { 262 | t.Errorf("Contacts.Update returned error: %v", err) 263 | } 264 | assert.NotNil(t, resp.Data) 265 | assert.Equal(t, resp.Data.Id, "479e3145-dd38-476b-932c-529ceb705947") 266 | assert.Equal(t, resp.Error, struct{}{}) 267 | } 268 | 269 | func TestUpdateContactIdMissing(t *testing.T) { 270 | setup() 271 | defer teardown() 272 | 273 | audienceId := "709d076c-2bb1-4be6-94ed-3f8f32622db6" 274 | id := "" 275 | 276 | params := &UpdateContactRequest{ 277 | AudienceId: audienceId, 278 | Id: id, 279 | FirstName: "Updated First Name", 280 | } 281 | resp, err := client.Contacts.Update(params) 282 | 283 | var missingRequiredFieldsErr *MissingRequiredFieldsError 284 | 285 | assert.Equal(t, resp, UpdateContactResponse{}) 286 | assert.ErrorAsf(t, err, &missingRequiredFieldsErr, "expected error to be of type %T, got %T", &missingRequiredFieldsErr, err) 287 | assert.Containsf(t, err.Error(), "Missing `id` or `email` field.", "expected error containing %q, got %s", "Missing `id` or `email` field.", err) 288 | } 289 | 290 | func TestUpdateContactEmailMissing(t *testing.T) { 291 | setup() 292 | defer teardown() 293 | 294 | audienceId := "709d076c-2bb1-4be6-94ed-3f8f32622db6" 295 | 296 | params := &UpdateContactRequest{ 297 | AudienceId: audienceId, 298 | Email: "", 299 | FirstName: "Updated First Name", 300 | } 301 | resp, err := client.Contacts.Update(params) 302 | 303 | var missingRequiredFieldsErr *MissingRequiredFieldsError 304 | 305 | assert.Equal(t, resp, UpdateContactResponse{}) 306 | assert.ErrorAsf(t, err, &missingRequiredFieldsErr, "expected error to be of type %T, got %T", &missingRequiredFieldsErr, err) 307 | assert.Containsf(t, err.Error(), "Missing `id` or `email` field.", "expected error containing %q, got %s", "Missing `id` or `email` field.", err) 308 | } 309 | 310 | func TestUpdateContactAudienceIdMissing(t *testing.T) { 311 | setup() 312 | defer teardown() 313 | 314 | audienceId := "" 315 | id := "123123123" 316 | 317 | params := &UpdateContactRequest{ 318 | AudienceId: audienceId, 319 | Id: id, 320 | FirstName: "Updated First Name", 321 | } 322 | resp, err := client.Contacts.Update(params) 323 | 324 | assert.NotNil(t, err) 325 | assert.Equal(t, resp, UpdateContactResponse{}) 326 | assert.Containsf(t, err.Error(), "[ERROR]: AudienceId is missing", "expected error containing %q, got %s", "[ERROR]: AudienceId is missing", err) 327 | } 328 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | services: 3 | app: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | command: "sleep infinity" 8 | volumes: 9 | - .:/app 10 | environment: 11 | RESEND_API_KEY: ${RESEND_API_KEY} -------------------------------------------------------------------------------- /domains.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "net/http" 8 | ) 9 | 10 | type TlsOption = string 11 | 12 | const ( 13 | Enforced TlsOption = "enforced" 14 | Opportunistic TlsOption = "opportunistic" 15 | ) 16 | 17 | type DomainsSvc interface { 18 | CreateWithContext(ctx context.Context, params *CreateDomainRequest) (CreateDomainResponse, error) 19 | Create(params *CreateDomainRequest) (CreateDomainResponse, error) 20 | VerifyWithContext(ctx context.Context, domainId string) (bool, error) 21 | Verify(domainId string) (bool, error) 22 | ListWithContext(ctx context.Context) (ListDomainsResponse, error) 23 | List() (ListDomainsResponse, error) 24 | GetWithContext(ctx context.Context, domainId string) (Domain, error) 25 | Get(domainId string) (Domain, error) 26 | RemoveWithContext(ctx context.Context, domainId string) (bool, error) 27 | Remove(domainId string) (bool, error) 28 | UpdateWithContext(ctx context.Context, domainId string, params *UpdateDomainRequest) (Domain, error) 29 | Update(domainId string, params *UpdateDomainRequest) (Domain, error) 30 | } 31 | 32 | type DomainsSvcImpl struct { 33 | client *Client 34 | } 35 | 36 | type CreateDomainRequest struct { 37 | Name string `json:"name"` 38 | Region string `json:"region,omitempty"` 39 | CustomReturnPath string `json:"custom_return_path,omitempty"` 40 | } 41 | 42 | type CreateDomainResponse struct { 43 | Id string `json:"id"` 44 | Name string `json:"name"` 45 | CreatedAt string `json:"createdAt"` 46 | Status string `json:"status"` 47 | Records []Record `json:"records"` 48 | Region string `json:"region"` 49 | DnsProvider string `json:"dnsProvider"` 50 | } 51 | 52 | type ListDomainsResponse struct { 53 | Data []Domain `json:"data"` 54 | } 55 | 56 | type UpdateDomainRequest struct { 57 | OpenTracking bool `json:"open_tracking,omitempty"` 58 | ClickTracking bool `json:"click_tracking,omitempty"` 59 | Tls TlsOption `json:"tls,omitempty"` 60 | } 61 | 62 | type Domain struct { 63 | Id string `json:"id,omitempty"` 64 | Object string `json:"object,omitempty"` 65 | Name string `json:"name,omitempty"` 66 | CreatedAt string `json:"created_at,omitempty"` 67 | Status string `json:"status,omitempty"` 68 | Region string `json:"region,omitempty"` 69 | Records []Record `json:"records,omitempty"` 70 | } 71 | 72 | type Record struct { 73 | Record string `json:"record"` 74 | Name string `json:"name"` 75 | Type string `json:"type"` 76 | Ttl string `json:"ttl"` 77 | Status string `json:"status"` 78 | Value string `json:"value"` 79 | Priority json.Number `json:"priority,omitempty"` 80 | } 81 | 82 | // UpdateWithContext updates an existing Domain entry based on the given params 83 | // https://resend.com/docs/api-reference/domains/update-domain 84 | func (s *DomainsSvcImpl) UpdateWithContext(ctx context.Context, domainId string, params *UpdateDomainRequest) (Domain, error) { 85 | path := "domains/" + domainId 86 | 87 | // Prepare request 88 | req, err := s.client.NewRequest(ctx, http.MethodPatch, path, params) 89 | if err != nil { 90 | return Domain{}, errors.New("[ERROR]: Failed to create Domains.Update request") 91 | } 92 | 93 | domainUpdatedResp := new(Domain) 94 | 95 | // Send Request 96 | _, err = s.client.Perform(req, domainUpdatedResp) 97 | 98 | if err != nil { 99 | return Domain{}, err 100 | } 101 | 102 | return *domainUpdatedResp, nil 103 | } 104 | 105 | // Update is a wrapper around UpdateWithContext 106 | func (s *DomainsSvcImpl) Update(domainId string, params *UpdateDomainRequest) (Domain, error) { 107 | return s.UpdateWithContext(context.Background(), domainId, params) 108 | } 109 | 110 | // CreateWithContext creates a new Domain entry based on the given params 111 | // https://resend.com/docs/api-reference/domains/create-domain 112 | func (s *DomainsSvcImpl) CreateWithContext(ctx context.Context, params *CreateDomainRequest) (CreateDomainResponse, error) { 113 | path := "domains" 114 | 115 | // Prepare request 116 | req, err := s.client.NewRequest(ctx, http.MethodPost, path, params) 117 | if err != nil { 118 | return CreateDomainResponse{}, errors.New("[ERROR]: Failed to create Domains.Create request") 119 | } 120 | 121 | // Build response recipient obj 122 | domainsResp := new(CreateDomainResponse) 123 | 124 | // Send Request 125 | _, err = s.client.Perform(req, domainsResp) 126 | 127 | if err != nil { 128 | return CreateDomainResponse{}, err 129 | } 130 | 131 | return *domainsResp, nil 132 | } 133 | 134 | // Create creates a new Domain entry based on the given params 135 | // https://resend.com/docs/api-reference/domains/create-domain 136 | func (s *DomainsSvcImpl) Create(params *CreateDomainRequest) (CreateDomainResponse, error) { 137 | return s.CreateWithContext(context.Background(), params) 138 | } 139 | 140 | // VerifyWithContext verifies a given domain Id 141 | // https://resend.com/docs/api-reference/domains/verify-domain 142 | func (s *DomainsSvcImpl) VerifyWithContext(ctx context.Context, domainId string) (bool, error) { 143 | path := "domains/" + domainId + "/verify" 144 | 145 | // Prepare request 146 | req, err := s.client.NewRequest(ctx, http.MethodPost, path, nil) 147 | if err != nil { 148 | return false, errors.New("[ERROR]: Failed to create Domains.Verify request") 149 | } 150 | 151 | // Send Request 152 | _, err = s.client.Perform(req, nil) 153 | 154 | if err != nil { 155 | return false, err 156 | } 157 | 158 | return true, nil 159 | } 160 | 161 | // Verify verifies a given domain Id 162 | // https://resend.com/docs/api-reference/domains/verify-domain 163 | func (s *DomainsSvcImpl) Verify(domainId string) (bool, error) { 164 | return s.VerifyWithContext(context.Background(), domainId) 165 | } 166 | 167 | // ListWithContext returns the list of all domains 168 | // https://resend.com/docs/api-reference/domains/list-domains 169 | func (s *DomainsSvcImpl) ListWithContext(ctx context.Context) (ListDomainsResponse, error) { 170 | path := "domains" 171 | 172 | // Prepare request 173 | req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 174 | if err != nil { 175 | return ListDomainsResponse{}, errors.New("[ERROR]: Failed to create Domains.List request") 176 | } 177 | 178 | domains := new(ListDomainsResponse) 179 | 180 | // Send Request 181 | _, err = s.client.Perform(req, domains) 182 | 183 | if err != nil { 184 | return ListDomainsResponse{}, err 185 | } 186 | 187 | return *domains, nil 188 | } 189 | 190 | // List returns the list of all domains 191 | // https://resend.com/docs/api-reference/domains/list-domains 192 | func (s *DomainsSvcImpl) List() (ListDomainsResponse, error) { 193 | return s.ListWithContext(context.Background()) 194 | } 195 | 196 | // RemoveWithContext removes a given domain entry by id 197 | // https://resend.com/docs/api-reference/domains/delete-domain 198 | func (s *DomainsSvcImpl) RemoveWithContext(ctx context.Context, domainId string) (bool, error) { 199 | path := "domains/" + domainId 200 | 201 | // Prepare request 202 | req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) 203 | if err != nil { 204 | return false, errors.New("[ERROR]: Failed to create Domains.Remove request") 205 | } 206 | 207 | // Send Request 208 | _, err = s.client.Perform(req, nil) 209 | 210 | if err != nil { 211 | return false, err 212 | } 213 | 214 | return true, nil 215 | } 216 | 217 | // Remove removes a given domain entry by id 218 | // https://resend.com/docs/api-reference/domains/delete-domain 219 | func (s *DomainsSvcImpl) Remove(domainId string) (bool, error) { 220 | return s.RemoveWithContext(context.Background(), domainId) 221 | } 222 | 223 | // GetWithContext retrieves a domain object 224 | // https://resend.com/docs/api-reference/domains/get-domain 225 | func (s *DomainsSvcImpl) GetWithContext(ctx context.Context, domainId string) (Domain, error) { 226 | path := "domains/" + domainId 227 | 228 | // Prepare request 229 | req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 230 | if err != nil { 231 | return Domain{}, errors.New("[ERROR]: Failed to create Domains.Get request") 232 | } 233 | 234 | domain := new(Domain) 235 | 236 | // Send Request 237 | _, err = s.client.Perform(req, domain) 238 | 239 | if err != nil { 240 | return Domain{}, err 241 | } 242 | 243 | return *domain, nil 244 | } 245 | 246 | // Get retrieves a domain object 247 | // https://resend.com/docs/api-reference/domains/get-domain 248 | func (s *DomainsSvcImpl) Get(domainId string) (Domain, error) { 249 | return s.GetWithContext(context.Background(), domainId) 250 | } 251 | -------------------------------------------------------------------------------- /domains_test.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestCreateDomain(t *testing.T) { 13 | setup() 14 | defer teardown() 15 | 16 | mux.HandleFunc("/domains", func(w http.ResponseWriter, r *http.Request) { 17 | testMethod(t, r, http.MethodPost) 18 | w.Header().Set("Content-Type", "application/json") 19 | w.WriteHeader(http.StatusCreated) 20 | 21 | var ret interface{} 22 | ret = ` 23 | { 24 | "id": "4dd369bc-aa82-4ff3-97de-514ae3000ee0", 25 | "name": "example.com", 26 | "createdAt": "2023-03-28T17:12:02.059593+00:00", 27 | "status": "not_started", 28 | "records": [ 29 | { 30 | "record": "SPF", 31 | "name": "bounces", 32 | "type": "MX", 33 | "ttl": "Auto", 34 | "status": "not_started", 35 | "value": "feedback-smtp.us-east-1.amazonses.com", 36 | "priority": 10 37 | }, 38 | { 39 | "record": "SPF", 40 | "name": "bounces", 41 | "value": "\"v=spf1 include:amazonses.com ~all\"", 42 | "type": "TXT", 43 | "ttl": "Auto", 44 | "status": "not_started" 45 | }, 46 | { 47 | "record": "DKIM", 48 | "name": "nhapbbryle57yxg3fbjytyodgbt2kyyg._domainkey", 49 | "value": "nhapbbryle57yxg3fbjytyodgbt2kyyg.dkim.amazonses.com.", 50 | "type": "CNAME", 51 | "status": "not_started", 52 | "ttl": "Auto" 53 | }, 54 | { 55 | "record": "DKIM", 56 | "name": "xbakwbe5fcscrhzshpap6kbxesf6pfgn._domainkey", 57 | "value": "xbakwbe5fcscrhzshpap6kbxesf6pfgn.dkim.amazonses.com.", 58 | "type": "CNAME", 59 | "status": "not_started", 60 | "ttl": "Auto" 61 | }, 62 | { 63 | "record": "DKIM", 64 | "name": "txrcreso3dqbvcve45tqyosxwaegvhgn._domainkey", 65 | "value": "txrcreso3dqbvcve45tqyosxwaegvhgn.dkim.amazonses.com.", 66 | "type": "CNAME", 67 | "status": "not_started", 68 | "ttl": "Auto" 69 | } 70 | ], 71 | "region": "us-east-1", 72 | "dnsProvider": "Unidentified" 73 | }` 74 | 75 | fmt.Fprint(w, ret) 76 | }) 77 | 78 | req := &CreateDomainRequest{ 79 | Name: "example.com", 80 | Region: "us-east-1", 81 | CustomReturnPath: "outbound", 82 | } 83 | resp, err := client.Domains.Create(req) 84 | if err != nil { 85 | t.Errorf("Domains.Create returned error: %v", err) 86 | } 87 | assert.Equal(t, resp.DnsProvider, "Unidentified") 88 | assert.Equal(t, resp.Id, "4dd369bc-aa82-4ff3-97de-514ae3000ee0") 89 | assert.Equal(t, resp.Region, "us-east-1") 90 | assert.Equal(t, resp.Status, "not_started") 91 | assert.Equal(t, resp.CreatedAt, "2023-03-28T17:12:02.059593+00:00") 92 | assert.NotNil(t, resp.Records) 93 | assert.Equal(t, len(resp.Records), 5) 94 | 95 | assert.Equal(t, resp.Records[0].Record, "SPF") 96 | assert.Equal(t, resp.Records[0].Name, "bounces") 97 | assert.Equal(t, resp.Records[0].Type, "MX") 98 | assert.Equal(t, resp.Records[0].Ttl, "Auto") 99 | assert.Equal(t, resp.Records[0].Status, "not_started") 100 | assert.Equal(t, resp.Records[0].Value, "feedback-smtp.us-east-1.amazonses.com") 101 | assert.Equal(t, resp.Records[0].Priority, json.Number("10")) 102 | 103 | assert.Equal(t, resp.Records[1].Priority, json.Number("")) 104 | } 105 | 106 | func TestVerifyDomain(t *testing.T) { 107 | setup() 108 | defer teardown() 109 | 110 | mux.HandleFunc("/domains/d91cd9bd-1176-453e-8fc1-35364d380206/verify", func(w http.ResponseWriter, r *http.Request) { 111 | testMethod(t, r, http.MethodPost) 112 | w.WriteHeader(http.StatusOK) 113 | w.Header().Set("Content-Length", "0") 114 | fmt.Fprint(w, nil) 115 | }) 116 | 117 | verified, err := client.Domains.Verify("d91cd9bd-1176-453e-8fc1-35364d380206") 118 | if err != nil { 119 | t.Errorf("Domains.Verify returned error: %v", err) 120 | } 121 | assert.True(t, verified) 122 | } 123 | 124 | func TestListDomains(t *testing.T) { 125 | setup() 126 | defer teardown() 127 | 128 | mux.HandleFunc("/domains", func(w http.ResponseWriter, r *http.Request) { 129 | testMethod(t, r, http.MethodGet) 130 | w.WriteHeader(http.StatusOK) 131 | 132 | ret := ` 133 | { 134 | "data": [ 135 | { 136 | "id": "d91cd9bd-1176-453e-8fc1-35364d380206", 137 | "name": "example.com", 138 | "status": "not_started", 139 | "created_at": "2023-04-26T20:21:26.347412+00:00", 140 | "region": "us-east-1", 141 | "records": [ 142 | { 143 | "record": "SPF", 144 | "name": "bounces" 145 | } 146 | ] 147 | } 148 | ] 149 | }` 150 | 151 | fmt.Fprint(w, ret) 152 | }) 153 | 154 | domains, err := client.Domains.List() 155 | if err != nil { 156 | t.Errorf("Domains.List returned error: %v", err) 157 | } 158 | 159 | assert.Equal(t, len(domains.Data), 1) 160 | assert.Equal(t, domains.Data[0].Id, "d91cd9bd-1176-453e-8fc1-35364d380206") 161 | assert.Equal(t, domains.Data[0].Name, "example.com") 162 | assert.Equal(t, domains.Data[0].Status, "not_started") 163 | assert.Equal(t, domains.Data[0].CreatedAt, "2023-04-26T20:21:26.347412+00:00") 164 | assert.Equal(t, domains.Data[0].Region, "us-east-1") 165 | assert.Equal(t, domains.Data[0].Records[0].Record, "SPF") 166 | } 167 | 168 | func TestRemoveDomain(t *testing.T) { 169 | setup() 170 | defer teardown() 171 | 172 | mux.HandleFunc("/domains/b6d24b8e-af0b-4c3c-be0c-359bbd97381e", func(w http.ResponseWriter, r *http.Request) { 173 | testMethod(t, r, http.MethodDelete) 174 | w.WriteHeader(http.StatusOK) 175 | w.Header().Set("Content-Length", "0") 176 | fmt.Fprint(w, nil) 177 | }) 178 | 179 | deleted, err := client.Domains.Remove("b6d24b8e-af0b-4c3c-be0c-359bbd97381e") 180 | if err != nil { 181 | t.Errorf("Domains.Remove returned error: %v", err) 182 | } 183 | assert.True(t, deleted) 184 | } 185 | 186 | func TestGetDomain(t *testing.T) { 187 | setup() 188 | defer teardown() 189 | 190 | mux.HandleFunc("/domains/d91cd9bd-1176-453e-8fc1-35364d380206", func(w http.ResponseWriter, r *http.Request) { 191 | testMethod(t, r, http.MethodGet) 192 | w.Header().Set("Content-Type", "application/json") 193 | w.WriteHeader(http.StatusOK) 194 | 195 | ret := ` 196 | { 197 | "object": "domain", 198 | "id": "d91cd9bd-1176-453e-8fc1-35364d380206", 199 | "name": "example.com", 200 | "status": "not_started", 201 | "created_at": "2023-04-26T20:21:26.347412+00:00", 202 | "region": "us-east-1", 203 | "records": [ 204 | { 205 | "record": "SPF", 206 | "name": "bounces" 207 | } 208 | ] 209 | }` 210 | 211 | fmt.Fprint(w, ret) 212 | }) 213 | 214 | domain, err := client.Domains.Get("d91cd9bd-1176-453e-8fc1-35364d380206") 215 | if err != nil { 216 | t.Errorf("Domains.Get returned error: %v", err) 217 | } 218 | 219 | assert.Equal(t, domain.Id, "d91cd9bd-1176-453e-8fc1-35364d380206") 220 | assert.Equal(t, domain.Object, "domain") 221 | assert.Equal(t, domain.Name, "example.com") 222 | assert.Equal(t, domain.Status, "not_started") 223 | assert.Equal(t, domain.CreatedAt, "2023-04-26T20:21:26.347412+00:00") 224 | assert.Equal(t, domain.Region, "us-east-1") 225 | assert.Equal(t, domain.Records[0].Record, "SPF") 226 | assert.Equal(t, domain.Records[0].Name, "bounces") 227 | } 228 | 229 | func TestUpdateDomain(t *testing.T) { 230 | setup() 231 | defer teardown() 232 | 233 | mux.HandleFunc("/domains/d91cd9bd-1176-453e-8fc1-35364d380206", func(w http.ResponseWriter, r *http.Request) { 234 | testMethod(t, r, http.MethodPatch) 235 | w.Header().Set("Content-Type", "application/json") 236 | w.WriteHeader(http.StatusOK) 237 | 238 | ret := ` 239 | { 240 | "id": "d91cd9bd-1176-453e-8fc1-35364d380206", 241 | "object": "domain" 242 | }` 243 | 244 | fmt.Fprint(w, ret) 245 | }) 246 | 247 | params := &UpdateDomainRequest{ 248 | OpenTracking: true, 249 | Tls: Opportunistic, 250 | } 251 | updated, err := client.Domains.Update("d91cd9bd-1176-453e-8fc1-35364d380206", params) 252 | if err != nil { 253 | t.Errorf("Domains.Update returned error: %v", err) 254 | } 255 | assert.True(t, updated.Id == "d91cd9bd-1176-453e-8fc1-35364d380206") 256 | assert.True(t, updated.Object == "domain") 257 | } 258 | -------------------------------------------------------------------------------- /emails.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | type SendEmailOptions struct { 10 | IdempotencyKey string `json:"idempotency_key,omitempty"` 11 | } 12 | 13 | func (o SendEmailOptions) GetIdempotencyKey() string { 14 | return o.IdempotencyKey 15 | } 16 | 17 | // SendEmailRequest is the request object for the Send call. 18 | // 19 | // See also https://resend.com/docs/api-reference/emails/send-email 20 | type SendEmailRequest struct { 21 | From string `json:"from"` 22 | To []string `json:"to"` 23 | Subject string `json:"subject"` 24 | Bcc []string `json:"bcc,omitempty"` 25 | Cc []string `json:"cc,omitempty"` 26 | ReplyTo string `json:"reply_to,omitempty"` 27 | Html string `json:"html,omitempty"` 28 | Text string `json:"text,omitempty"` 29 | Tags []Tag `json:"tags,omitempty"` 30 | Attachments []*Attachment `json:"attachments,omitempty"` 31 | Headers map[string]string `json:"headers,omitempty"` 32 | ScheduledAt string `json:"scheduled_at,omitempty"` 33 | } 34 | 35 | // CancelScheduledEmailResponse is the response from the Cancel call. 36 | type CancelScheduledEmailResponse struct { 37 | Id string `json:"id"` 38 | Object string `json:"object"` 39 | } 40 | 41 | // SendEmailResponse is the response from the Send call. 42 | type SendEmailResponse struct { 43 | Id string `json:"id"` 44 | } 45 | 46 | // UpdateEmailRequest is the request object for the Update call. 47 | type UpdateEmailRequest struct { 48 | Id string `json:"id"` 49 | ScheduledAt string `json:"scheduled_at"` 50 | } 51 | 52 | // UpdateEmailResponse is the type that represents the response from the Update call. 53 | type UpdateEmailResponse struct { 54 | Id string `json:"id"` 55 | Object string `json:"object"` 56 | } 57 | 58 | // Email provides the structure for the response from the Get call. 59 | type Email struct { 60 | Id string `json:"id"` 61 | Object string `json:"object"` 62 | To []string `json:"to"` 63 | From string `json:"from"` 64 | CreatedAt string `json:"created_at"` 65 | Subject string `json:"subject"` 66 | Html string `json:"html"` 67 | Text string `json:"text"` 68 | Bcc []string `json:"bcc"` 69 | Cc []string `json:"cc"` 70 | ReplyTo []string `json:"reply_to"` 71 | LastEvent string `json:"last_event"` 72 | } 73 | 74 | // Tags are used to define custom metadata for emails 75 | type Tag struct { 76 | Name string `json:"name"` 77 | Value string `json:"value"` 78 | } 79 | 80 | // Attachment is the public struct used for adding attachments to emails 81 | type Attachment struct { 82 | // Content is the binary content of the attachment to use when a Path 83 | // is not available. 84 | Content []byte 85 | 86 | // Filename that will appear in the email. 87 | // Make sure you pick the correct extension otherwise preview 88 | // may not work as expected 89 | Filename string 90 | 91 | // Path where the attachment file is hosted instead of providing the 92 | // content directly. 93 | Path string 94 | 95 | // Content type for the attachment, if not set will be derived from 96 | // the filename property 97 | ContentType string 98 | } 99 | 100 | // MarshalJSON overrides the regular JSON Marshaller to ensure that the 101 | // attachment content is provided in the way Resend expects. 102 | func (a *Attachment) MarshalJSON() ([]byte, error) { 103 | na := struct { 104 | Content []int `json:"content,omitempty"` 105 | Filename string `json:"filename,omitempty"` 106 | Path string `json:"path,omitempty"` 107 | ContentType string `json:"content_type,omitempty"` 108 | }{ 109 | Filename: a.Filename, 110 | Path: a.Path, 111 | Content: BytesToIntArray(a.Content), 112 | ContentType: a.ContentType, 113 | } 114 | return json.Marshal(na) 115 | } 116 | 117 | type EmailsSvc interface { 118 | CancelWithContext(ctx context.Context, emailID string) (*CancelScheduledEmailResponse, error) 119 | Cancel(emailID string) (*CancelScheduledEmailResponse, error) 120 | UpdateWithContext(ctx context.Context, params *UpdateEmailRequest) (*UpdateEmailResponse, error) 121 | Update(params *UpdateEmailRequest) (*UpdateEmailResponse, error) 122 | SendWithOptions(ctx context.Context, params *SendEmailRequest, options *SendEmailOptions) (*SendEmailResponse, error) 123 | SendWithContext(ctx context.Context, params *SendEmailRequest) (*SendEmailResponse, error) 124 | Send(params *SendEmailRequest) (*SendEmailResponse, error) 125 | GetWithContext(ctx context.Context, emailID string) (*Email, error) 126 | Get(emailID string) (*Email, error) 127 | } 128 | 129 | type EmailsSvcImpl struct { 130 | client *Client 131 | } 132 | 133 | // Cancel cancels an email by ID 134 | // https://resend.com/docs/api-reference/emails/cancel-email 135 | func (s *EmailsSvcImpl) Cancel(emailID string) (*CancelScheduledEmailResponse, error) { 136 | return s.CancelWithContext(context.Background(), emailID) 137 | } 138 | 139 | // CancelWithContext cancels an email by ID 140 | // https://resend.com/docs/api-reference/emails/cancel-email 141 | func (s *EmailsSvcImpl) CancelWithContext(ctx context.Context, emailID string) (*CancelScheduledEmailResponse, error) { 142 | path := "emails/" + emailID + "/cancel" 143 | 144 | // Prepare request 145 | req, err := s.client.NewRequest(ctx, http.MethodPost, path, nil) 146 | if err != nil { 147 | return nil, ErrFailedToCreateEmailsSendRequest 148 | } 149 | 150 | // Build response recipient obj 151 | resp := new(CancelScheduledEmailResponse) 152 | 153 | // Send Request 154 | _, err = s.client.Perform(req, resp) 155 | 156 | if err != nil { 157 | return nil, err 158 | } 159 | 160 | return resp, nil 161 | } 162 | 163 | // Update updates an email with the given params 164 | // https://resend.com/docs/api-reference/emails/update-email 165 | func (s *EmailsSvcImpl) Update(params *UpdateEmailRequest) (*UpdateEmailResponse, error) { 166 | return s.UpdateWithContext(context.Background(), params) 167 | } 168 | 169 | // UpdateWithContext updates an email with the given params 170 | // https://resend.com/docs/api-reference/emails/update-email 171 | func (s *EmailsSvcImpl) UpdateWithContext(ctx context.Context, params *UpdateEmailRequest) (*UpdateEmailResponse, error) { 172 | path := "emails/" + params.Id 173 | 174 | // Prepare request 175 | req, err := s.client.NewRequest(ctx, http.MethodPatch, path, params) 176 | if err != nil { 177 | return nil, ErrFailedToCreateUpdateEmailRequest 178 | } 179 | 180 | // Build response recipient obj 181 | updateEmailResponse := new(UpdateEmailResponse) 182 | 183 | // Send Request 184 | _, err = s.client.Perform(req, updateEmailResponse) 185 | 186 | if err != nil { 187 | return nil, err 188 | } 189 | 190 | return updateEmailResponse, nil 191 | } 192 | 193 | // SendWithOptions sends an email with the given params 194 | // and additional options 195 | // https://resend.com/docs/api-reference/emails/send-email 196 | func (s *EmailsSvcImpl) SendWithOptions(ctx context.Context, params *SendEmailRequest, options *SendEmailOptions) (*SendEmailResponse, error) { 197 | path := "emails" 198 | 199 | // Prepare request 200 | req, err := s.client.NewRequestWithOptions(ctx, http.MethodPost, path, params, options) 201 | if err != nil { 202 | return nil, ErrFailedToCreateEmailsSendRequest 203 | } 204 | 205 | // Build response recipient obj 206 | emailResponse := new(SendEmailResponse) 207 | 208 | // Send Request 209 | _, err = s.client.Perform(req, emailResponse) 210 | 211 | if err != nil { 212 | return nil, err 213 | } 214 | 215 | return emailResponse, nil 216 | } 217 | 218 | // SendWithContext sends an email with the given params 219 | // https://resend.com/docs/api-reference/emails/send-email 220 | func (s *EmailsSvcImpl) SendWithContext(ctx context.Context, params *SendEmailRequest) (*SendEmailResponse, error) { 221 | path := "emails" 222 | 223 | // Prepare request 224 | req, err := s.client.NewRequest(ctx, http.MethodPost, path, params) 225 | if err != nil { 226 | return nil, ErrFailedToCreateEmailsSendRequest 227 | } 228 | 229 | // Build response recipient obj 230 | emailResponse := new(SendEmailResponse) 231 | 232 | // Send Request 233 | _, err = s.client.Perform(req, emailResponse) 234 | 235 | if err != nil { 236 | return nil, err 237 | } 238 | 239 | return emailResponse, nil 240 | } 241 | 242 | // Send sends an email with the given params 243 | // https://resend.com/docs/api-reference/emails/send-email 244 | func (s *EmailsSvcImpl) Send(params *SendEmailRequest) (*SendEmailResponse, error) { 245 | return s.SendWithContext(context.Background(), params) 246 | } 247 | 248 | // GetWithContext retrieves an email with the given emailID 249 | // https://resend.com/docs/api-reference/emails/retrieve-email 250 | func (s *EmailsSvcImpl) GetWithContext(ctx context.Context, emailID string) (*Email, error) { 251 | path := "emails/" + emailID 252 | 253 | // Prepare request 254 | req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) 255 | if err != nil { 256 | return nil, ErrFailedToCreateEmailsGetRequest 257 | } 258 | 259 | // Build response recipient obj 260 | emailResponse := new(Email) 261 | 262 | // Send Request 263 | _, err = s.client.Perform(req, emailResponse) 264 | 265 | if err != nil { 266 | return nil, err 267 | } 268 | 269 | return emailResponse, nil 270 | } 271 | 272 | // Get retrieves an email with the given emailID 273 | // https://resend.com/docs/api-reference/emails/retrieve-email 274 | func (s *EmailsSvcImpl) Get(emailID string) (*Email, error) { 275 | return s.GetWithContext(context.Background(), emailID) 276 | } 277 | -------------------------------------------------------------------------------- /emails_test.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "net/http/httptest" 11 | "net/url" 12 | "testing" 13 | 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | var ( 18 | mux *http.ServeMux 19 | client *Client 20 | server *httptest.Server 21 | ) 22 | 23 | func setup() { 24 | mux = http.NewServeMux() 25 | server = httptest.NewServer(mux) 26 | client = NewClient("") 27 | url, _ := url.Parse(server.URL) 28 | client.BaseURL = url 29 | } 30 | 31 | func teardown() { 32 | server.Close() 33 | } 34 | 35 | func TestScheduleEmail(t *testing.T) { 36 | setup() 37 | defer teardown() 38 | 39 | mux.HandleFunc("/emails", func(w http.ResponseWriter, r *http.Request) { 40 | testMethod(t, r, http.MethodPost) 41 | w.Header().Set("Content-Type", "application/json") 42 | w.WriteHeader(http.StatusOK) 43 | 44 | ret := &SendEmailResponse{ 45 | Id: "1923781293", 46 | } 47 | err := json.NewEncoder(w).Encode(&ret) 48 | if err != nil { 49 | panic(err) 50 | } 51 | }) 52 | 53 | req := &SendEmailRequest{ 54 | To: []string{"d@e.com"}, 55 | ScheduledAt: "2024-09-05T11:52:01.858Z", 56 | } 57 | resp, err := client.Emails.Send(req) 58 | if err != nil { 59 | t.Errorf("Emails.Send returned error: %v", err) 60 | } 61 | assert.Equal(t, resp.Id, "1923781293") 62 | } 63 | 64 | func TestSendEmail(t *testing.T) { 65 | setup() 66 | defer teardown() 67 | 68 | mux.HandleFunc("/emails", func(w http.ResponseWriter, r *http.Request) { 69 | testMethod(t, r, http.MethodPost) 70 | w.Header().Set("Content-Type", "application/json") 71 | w.WriteHeader(http.StatusOK) 72 | 73 | ret := &SendEmailResponse{ 74 | Id: "1923781293", 75 | } 76 | err := json.NewEncoder(w).Encode(&ret) 77 | if err != nil { 78 | panic(err) 79 | } 80 | }) 81 | 82 | req := &SendEmailRequest{ 83 | To: []string{"d@e.com"}, 84 | } 85 | resp, err := client.Emails.Send(req) 86 | if err != nil { 87 | t.Errorf("Emails.Send returned error: %v", err) 88 | } 89 | assert.Equal(t, resp.Id, "1923781293") 90 | } 91 | 92 | func TestSendEmailWithAttachment(t *testing.T) { 93 | setup() 94 | defer teardown() 95 | 96 | mux.HandleFunc("/emails", func(w http.ResponseWriter, r *http.Request) { 97 | testMethod(t, r, http.MethodPost) 98 | w.Header().Set("Content-Type", "application/json") 99 | content, err := io.ReadAll(r.Body) 100 | if err != nil { 101 | t.Errorf("failed to read request body: %v", err) 102 | } 103 | exp := `"attachments":[{"content":[104,101,108,108,111],"filename":"hello.txt","content_type":"text/plain"}]` 104 | if !bytes.Contains(content, []byte(exp)) { 105 | t.Errorf("request body does not include attachment data") 106 | } 107 | w.WriteHeader(http.StatusOK) 108 | ret := &SendEmailResponse{ 109 | Id: "1923781293", 110 | } 111 | if err := json.NewEncoder(w).Encode(&ret); err != nil { 112 | panic(err) 113 | } 114 | }) 115 | 116 | req := &SendEmailRequest{ 117 | To: []string{"d@e.com"}, 118 | Attachments: []*Attachment{ 119 | { 120 | Content: []byte("hello"), 121 | Filename: "hello.txt", 122 | ContentType: "text/plain", 123 | }, 124 | }, 125 | } 126 | resp, err := client.Emails.Send(req) 127 | if err != nil { 128 | t.Errorf("Emails.Send returned error: %v", err) 129 | } 130 | assert.Equal(t, resp.Id, "1923781293") 131 | } 132 | 133 | func TestGetEmail(t *testing.T) { 134 | setup() 135 | defer teardown() 136 | 137 | mux.HandleFunc("/emails/49a3999c-0ce1-4ea6-ab68-afcd6dc2e794", func(w http.ResponseWriter, r *http.Request) { 138 | testMethod(t, r, http.MethodGet) 139 | w.Header().Set("Content-Type", "application/json") 140 | w.WriteHeader(http.StatusOK) 141 | 142 | ret := ` 143 | { 144 | "id":"49a3999c-0ce1-4ea6-ab68-afcd6dc2e794", 145 | "from":"from@example.com", 146 | "to":["james@bond.com"], 147 | "created_at":"2023-04-03T22:13:42.674981+00:00", 148 | "subject": "Hello World", 149 | "html":"html" 150 | }` 151 | fmt.Fprintf(w, ret) 152 | }) 153 | 154 | resp, err := client.Emails.Get("49a3999c-0ce1-4ea6-ab68-afcd6dc2e794") 155 | if err != nil { 156 | t.Errorf("Emails.Get returned error: %v", err) 157 | } 158 | assert.Equal(t, resp.Id, "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794") 159 | assert.Equal(t, resp.From, "from@example.com") 160 | assert.Equal(t, resp.Html, "html") 161 | assert.Equal(t, resp.To[0], "james@bond.com") 162 | assert.Equal(t, resp.Subject, "Hello World") 163 | } 164 | 165 | func TestCancelScheduledEmail(t *testing.T) { 166 | setup() 167 | defer teardown() 168 | 169 | mux.HandleFunc("/emails/dacf4072-4119-4d88-932f-6202748ac7c8/cancel", func(w http.ResponseWriter, r *http.Request) { 170 | testMethod(t, r, http.MethodPost) 171 | w.Header().Set("Content-Type", "application/json") 172 | w.WriteHeader(http.StatusOK) 173 | 174 | ret := ` 175 | { 176 | "id": "dacf4072-4119-4d88-932f-6202748ac7c8", 177 | "object": "email" 178 | }` 179 | fmt.Fprintf(w, ret) 180 | }) 181 | 182 | resp, err := client.Emails.Cancel("dacf4072-4119-4d88-932f-6202748ac7c8") 183 | if err != nil { 184 | t.Errorf("Emails.Cancel returned error: %v", err) 185 | } 186 | assert.Equal(t, resp.Id, "dacf4072-4119-4d88-932f-6202748ac7c8") 187 | assert.Equal(t, resp.Object, "email") 188 | } 189 | 190 | func TestSendEmailWithOptions(t *testing.T) { 191 | ctx := context.TODO() 192 | client := NewClient("123") 193 | params := &SendEmailRequest{ 194 | To: []string{"email@example.com", "email2@example.com"}, 195 | } 196 | options := &SendEmailOptions{ 197 | IdempotencyKey: "unique-idempotency-key", 198 | } 199 | 200 | req, err := client.NewRequestWithOptions(ctx, "POST", "/emails/", params, options) 201 | if err != nil { 202 | t.Error(err) 203 | } 204 | assert.Equal(t, req.Header["Accept"][0], "application/json") 205 | assert.Equal(t, req.Header["Content-Type"][0], "application/json") 206 | assert.Equal(t, req.Method, http.MethodPost) 207 | assert.Equal(t, req.URL.String(), "https://api.resend.com/emails/") 208 | assert.Equal(t, req.Header["Authorization"][0], "Bearer 123") 209 | assert.Equal(t, req.Header["Idempotency-Key"][0], "unique-idempotency-key") 210 | } 211 | 212 | func testMethod(t *testing.T, r *http.Request, expected string) { 213 | if expected != r.Method { 214 | t.Errorf("Request method = %v, expected %v", r.Method, expected) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // MissingRequiredFieldsError is used when a required field is missing before making an API request 9 | type MissingRequiredFieldsError struct { 10 | message string 11 | } 12 | 13 | func (e *MissingRequiredFieldsError) Error() string { 14 | return fmt.Sprintf("%s", e.message) 15 | } 16 | 17 | // BroadcastsSvc errors 18 | var ( 19 | ErrFailedToCreateBroadcastUpdateRequest = errors.New("[ERROR]: Failed to create Broadcasts.Update request") 20 | ErrFailedToCreateBroadcastSendRequest = errors.New("[ERROR]: Failed to create Broadcasts.Send request") 21 | ErrFailedToCreateBroadcastCreateRequest = errors.New("[ERROR]: Failed to create Broadcasts.Create request") 22 | ) 23 | 24 | // ApiKeySvc errors 25 | var ( 26 | ErrFailedToCreateApiKeysCreateRequest = errors.New("[ERROR]: Failed to create ApiKeys.Create request") 27 | ErrFailedToCreateApiKeysListRequest = errors.New("[ERROR]: Failed to create ApiKeys.List request") 28 | ErrFailedToCreateApiKeysRemoveRequest = errors.New("[ERROR]: Failed to create ApiKeys.Remove request") 29 | ) 30 | 31 | // EmailsSvc errors 32 | var ( 33 | ErrFailedToCreateUpdateEmailRequest = errors.New("[ERROR]: Failed to create UpdateEmail request") 34 | ErrFailedToCreateEmailsSendRequest = errors.New("[ERROR]: Failed to create SendEmail request") 35 | ErrFailedToCreateEmailsGetRequest = errors.New("[ERROR]: Failed to create GetEmail request") 36 | ) 37 | -------------------------------------------------------------------------------- /examples/api_keys.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/resend/resend-go/v2" 9 | ) 10 | 11 | func apiKeysExample() { 12 | ctx := context.TODO() 13 | apiKey := os.Getenv("RESEND_API_KEY") 14 | 15 | client := resend.NewClient(apiKey) 16 | 17 | // Create API Key 18 | params := &resend.CreateApiKeyRequest{ 19 | Name: "nice api key", 20 | } 21 | 22 | resp, err := client.ApiKeys.CreateWithContext(ctx, params) 23 | if err != nil { 24 | panic(err) 25 | } 26 | fmt.Println("Created API Key id: " + resp.Id) 27 | fmt.Println("Token: " + resp.Token) 28 | 29 | // List 30 | apiKeys, err := client.ApiKeys.ListWithContext(ctx) 31 | if err != nil { 32 | panic(err) 33 | } 34 | fmt.Printf("You have %d api keys in your project\n", len(apiKeys.Data)) 35 | 36 | // Delete 37 | _, err = client.ApiKeys.RemoveWithContext(ctx, resp.Id) 38 | if err != nil { 39 | panic(err) 40 | } 41 | println("deleted api key id: " + resp.Id) 42 | } 43 | -------------------------------------------------------------------------------- /examples/audiences.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/resend/resend-go/v2" 9 | ) 10 | 11 | func audiencesExample() { 12 | ctx := context.TODO() 13 | apiKey := os.Getenv("RESEND_API_KEY") 14 | 15 | client := resend.NewClient(apiKey) 16 | 17 | // Create Audience params 18 | params := &resend.CreateAudienceRequest{ 19 | Name: "New Audience", 20 | } 21 | 22 | audience, err := client.Audiences.CreateWithContext(ctx, params) 23 | if err != nil { 24 | panic(err) 25 | } 26 | fmt.Println("Created audience with entry id: " + audience.Id) 27 | 28 | // Get 29 | retrievedAudience, err := client.Audiences.GetWithContext(ctx, "78b8d3bc-a55a-45a3-aee6-6ec0a5e13d7e") 30 | if err != nil { 31 | panic(err) 32 | } 33 | fmt.Printf("\nRetrieved audience: %v\n", retrievedAudience) 34 | 35 | // List 36 | audiences, err := client.Audiences.ListWithContext(ctx) 37 | if err != nil { 38 | panic(err) 39 | } 40 | fmt.Printf("You have %d audiences in your project\n", len(audiences.Data)) 41 | 42 | // Remove 43 | removed, err := client.Audiences.RemoveWithContext(ctx, audience.Id) 44 | if err != nil { 45 | panic(err) 46 | } 47 | println(removed.Deleted) 48 | } 49 | -------------------------------------------------------------------------------- /examples/broadcasts.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/resend/resend-go/v2" 9 | ) 10 | 11 | func broadcastExamples() { 12 | ctx := context.TODO() 13 | apiKey := os.Getenv("RESEND_API_KEY") 14 | 15 | client := resend.NewClient(apiKey) 16 | 17 | // Create Broadcast 18 | params := &resend.CreateBroadcastRequest{ 19 | AudienceId: "ca4e37c5-a82a-4199-a3b8-bf912a6472aa", 20 | From: "onboarding@resend.dev", 21 | Html: "

Hello, world!

", 22 | Name: "Test Broadcast", 23 | Subject: "Hello, world!", 24 | } 25 | 26 | broadcast, err := client.Broadcasts.CreateWithContext(ctx, params) 27 | if err != nil { 28 | panic(err) 29 | } 30 | fmt.Println("Created broadcast with entry id: " + broadcast.Id) 31 | 32 | // Get Broadcast 33 | retrievedBroadcast, err := client.Broadcasts.GetWithContext(ctx, broadcast.Id) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | fmt.Println("ID: " + retrievedBroadcast.Id) 39 | fmt.Println("Name: " + retrievedBroadcast.Name) 40 | fmt.Println("Audience ID: " + retrievedBroadcast.AudienceId) 41 | fmt.Println("From: " + retrievedBroadcast.From) 42 | fmt.Println("Subject: " + retrievedBroadcast.Subject) 43 | fmt.Println("Preview Text: " + retrievedBroadcast.PreviewText) 44 | fmt.Println("Status: " + retrievedBroadcast.Status) 45 | fmt.Println("Created At: " + retrievedBroadcast.CreatedAt) 46 | fmt.Println("Scheduled At: " + retrievedBroadcast.ScheduledAt) 47 | fmt.Println("Sent At: " + retrievedBroadcast.SentAt) 48 | 49 | updateParams := &resend.UpdateBroadcastRequest{ 50 | BroadcastId: retrievedBroadcast.Id, 51 | Name: "Updated Test Broadcast, Go SDK", 52 | } 53 | 54 | updatedBroadcast, err := client.Broadcasts.UpdateWithContext(ctx, updateParams) 55 | if err != nil { 56 | panic(err) 57 | } 58 | fmt.Println("Updated broadcast with entry id: " + updatedBroadcast.Id) 59 | 60 | // Send Broadcast 61 | // sendParams := &resend.SendBroadcastRequest{ 62 | // BroadcastId: broadcast.Id, 63 | // } 64 | 65 | // sendResponse, err := client.Broadcasts.SendWithContext(ctx, sendParams) 66 | // if err != nil { 67 | // panic(err) 68 | // } 69 | // fmt.Println("Sent broadcast with entry id: " + sendResponse.Id) 70 | 71 | // List Broadcasts 72 | listResponse, err := client.Broadcasts.ListWithContext(ctx) 73 | if err != nil { 74 | panic(err) 75 | } 76 | 77 | for _, b := range listResponse.Data { 78 | fmt.Println("ID: " + b.Id) 79 | fmt.Println("Name: " + b.Name) 80 | fmt.Println("Audience ID: " + b.AudienceId) 81 | fmt.Println("From: " + b.From) 82 | fmt.Println("Subject: " + b.Subject) 83 | fmt.Println("Preview Text: " + b.PreviewText) 84 | fmt.Println("Status: " + b.Status) 85 | fmt.Println("Created At: " + b.CreatedAt) 86 | fmt.Println("Scheduled At: " + b.ScheduledAt) 87 | fmt.Println("Sent At: " + b.SentAt) 88 | } 89 | 90 | // Remove Broadcast (Only Draft Broadcasts can be deleted) 91 | removeResponse, err := client.Broadcasts.RemoveWithContext(ctx, broadcast.Id) 92 | if err != nil { 93 | panic(err) 94 | } 95 | fmt.Println("Deleted broadcast with entry id: " + removeResponse.Id) 96 | } 97 | -------------------------------------------------------------------------------- /examples/contacts.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/resend/resend-go/v2" 9 | ) 10 | 11 | func contactsExample() { 12 | 13 | audienceId := "ca4e37c5-a82a-4199-a3b8-bf912a6472aa" 14 | 15 | ctx := context.TODO() 16 | apiKey := os.Getenv("RESEND_API_KEY") 17 | contactEmail := "hi@example.com" 18 | 19 | client := resend.NewClient(apiKey) 20 | 21 | // Create Contact params 22 | params := &resend.CreateContactRequest{ 23 | Email: contactEmail, 24 | AudienceId: audienceId, 25 | FirstName: "Steve", 26 | LastName: "Woz", 27 | } 28 | 29 | contact, err := client.Contacts.CreateWithContext(ctx, params) 30 | if err != nil { 31 | panic(err) 32 | } 33 | fmt.Println("Created contact with entry id: " + contact.Id) 34 | 35 | // Update 36 | updateParams := &resend.UpdateContactRequest{ 37 | AudienceId: audienceId, 38 | Id: contact.Id, 39 | FirstName: "Updated First Name", 40 | LastName: "Updated Last Name", 41 | Unsubscribed: true, 42 | } 43 | _, err = client.Contacts.UpdateWithContext(ctx, updateParams) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | // Get by ID 49 | retrievedContact, err := client.Contacts.GetWithContext(ctx, audienceId, contact.Id) 50 | if err != nil { 51 | panic(err) 52 | } 53 | fmt.Printf("\nRetrieved contact by ID: %v\n", retrievedContact) 54 | 55 | // Get by email 56 | retrievedByEmail, err := client.Contacts.GetWithContext(ctx, audienceId, contactEmail) 57 | if err != nil { 58 | panic(err) 59 | } 60 | fmt.Printf("\nRetrieved contact by email: %v\n", retrievedByEmail) 61 | 62 | // List 63 | contacts, err := client.Contacts.ListWithContext(ctx, audienceId) 64 | if err != nil { 65 | panic(err) 66 | } 67 | fmt.Printf("You have %d contacts in your audience\n", len(contacts.Data)) 68 | for _, c := range contacts.Data { 69 | fmt.Printf("%v\n", c) 70 | } 71 | 72 | // Remove by id 73 | removed, err := client.Contacts.RemoveWithContext(ctx, audienceId, contact.Id) 74 | 75 | // Remove by email 76 | // removed, err := client.Contacts.RemoveWithContext(ctx, audienceId, "hi@example.com") 77 | if err != nil { 78 | panic(err) 79 | } 80 | println(removed.Deleted) 81 | } 82 | -------------------------------------------------------------------------------- /examples/domains.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/resend/resend-go/v2" 9 | ) 10 | 11 | func domainsExample() { 12 | ctx := context.TODO() 13 | apiKey := os.Getenv("RESEND_API_KEY") 14 | 15 | client := resend.NewClient(apiKey) 16 | 17 | // Create Domain params 18 | params := &resend.CreateDomainRequest{ 19 | Name: "drish.dev", 20 | Region: "us-east-1", 21 | CustomReturnPath: "outbound", 22 | } 23 | 24 | domain, err := client.Domains.CreateWithContext(ctx, params) 25 | if err != nil { 26 | panic(err) 27 | } 28 | fmt.Println("Created Domain entry id: " + domain.Id) 29 | fmt.Println("Status: " + domain.Status) 30 | 31 | for _, record := range domain.Records { 32 | fmt.Printf("%v\n", record) 33 | } 34 | 35 | // Get 36 | retrievedDomain, err := client.Domains.GetWithContext(ctx, domain.Id) 37 | if err != nil { 38 | panic(err) 39 | } 40 | fmt.Printf("Retrieved domain: %v", retrievedDomain) 41 | 42 | updateDomainParams := &resend.UpdateDomainRequest{ 43 | OpenTracking: true, 44 | ClickTracking: true, 45 | Tls: resend.Enforced, 46 | } 47 | 48 | updated, err := client.Domains.UpdateWithContext(ctx, domain.Id, updateDomainParams) 49 | if err != nil { 50 | panic(err) 51 | } 52 | fmt.Printf("%v\n", updated) 53 | 54 | // List 55 | domains, err := client.Domains.ListWithContext(ctx) 56 | if err != nil { 57 | panic(err) 58 | } 59 | fmt.Printf("You have %d domains in your project\n", len(domains.Data)) 60 | 61 | // Verify 62 | verified, err := client.Domains.VerifyWithContext(ctx, domain.Id) 63 | if err != nil { 64 | panic(err) 65 | } 66 | if verified { 67 | println("verified domain id: " + domain.Id) 68 | } else { 69 | println("could not verify domain id: " + domain.Id) 70 | } 71 | 72 | // Remove 73 | removed, err := client.Domains.RemoveWithContext(ctx, domain.Id) 74 | if err != nil { 75 | panic(err) 76 | } 77 | if removed { 78 | println("removed domain id: " + domain.Id) 79 | } else { 80 | println("could not remove domain id: " + domain.Id) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/schedule_email.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/resend/resend-go/v2" 9 | ) 10 | 11 | func scheduleEmail() { 12 | ctx := context.TODO() 13 | apiKey := os.Getenv("RESEND_API_KEY") 14 | 15 | client := resend.NewClient(apiKey) 16 | 17 | // Schedule the email 18 | params := &resend.SendEmailRequest{ 19 | To: []string{"delivered@resend.dev"}, 20 | From: "onboarding@resend.dev", 21 | Text: "hello world", 22 | Subject: "Hello from Golang", 23 | ScheduledAt: "2024-09-05T11:52:01.858Z", 24 | } 25 | 26 | sent, err := client.Emails.SendWithContext(ctx, params) 27 | if err != nil { 28 | panic(err) 29 | } 30 | fmt.Println(sent.Id) 31 | 32 | updateParams := &resend.UpdateEmailRequest{ 33 | Id: sent.Id, 34 | ScheduledAt: "2024-11-05T11:52:01.858Z", 35 | } 36 | 37 | // Update the scheduled email 38 | updatedEmail, err := client.Emails.UpdateWithContext(ctx, updateParams) 39 | if err != nil { 40 | panic(err) 41 | } 42 | fmt.Printf("%v\n", updatedEmail) 43 | 44 | canceled, err := client.Emails.CancelWithContext(ctx, "32723fee-8502-4b58-8b5e-bfd98f453ced") 45 | if err != nil { 46 | panic(err) 47 | } 48 | fmt.Printf("%v\n", canceled) 49 | } 50 | -------------------------------------------------------------------------------- /examples/send_batch_email.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/resend/resend-go/v2" 9 | ) 10 | 11 | func sendBatchEmails() { 12 | 13 | ctx := context.TODO() 14 | apiKey := os.Getenv("RESEND_API_KEY") 15 | 16 | client := resend.NewClient(apiKey) 17 | 18 | // Batch Send 19 | var batchEmails = []*resend.SendEmailRequest{ 20 | { 21 | To: []string{"delivered@resend.dev"}, 22 | From: "onboarding@resend.dev", 23 | Text: "hey", 24 | Subject: "Hello from emails", 25 | }, 26 | { 27 | To: []string{"delivered@resend.dev"}, 28 | From: "onboarding@resend.dev", 29 | Text: "hellooo", 30 | Subject: "Hello from batch emails 2", 31 | }, 32 | } 33 | 34 | // Regular send without options 35 | sent, err := client.Batch.SendWithContext(ctx, batchEmails) 36 | if err != nil { 37 | panic(err) 38 | } 39 | fmt.Println("Sent without options") 40 | fmt.Println(sent.Data) 41 | 42 | // Send with options: IdempotencyKey 43 | options := &resend.BatchSendEmailOptions{ 44 | IdempotencyKey: "68656c6c6f2d776f726c64", 45 | } 46 | sent, err = client.Batch.SendWithOptions(ctx, batchEmails, options) 47 | if err != nil { 48 | panic(err) 49 | } 50 | fmt.Println("Sent with idempotency key") 51 | fmt.Println(sent.Data) 52 | } 53 | -------------------------------------------------------------------------------- /examples/send_email.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/resend/resend-go/v2" 9 | ) 10 | 11 | func sendEmailExample() { 12 | ctx := context.TODO() 13 | apiKey := os.Getenv("RESEND_API_KEY") 14 | 15 | client := resend.NewClient(apiKey) 16 | 17 | // Send params 18 | params := &resend.SendEmailRequest{ 19 | To: []string{"delivered@resend.dev"}, 20 | From: "onboarding@resend.dev", 21 | Text: "hello world", 22 | Subject: "Hello from Golang", 23 | Cc: []string{"cc@example.com"}, 24 | Bcc: []string{"ccc@example.com"}, 25 | ReplyTo: "to@example.com", 26 | } 27 | 28 | sent, err := client.Emails.SendWithContext(ctx, params) 29 | if err != nil { 30 | panic(err) 31 | } 32 | fmt.Printf("Sent basic email: %s\n", sent.Id) 33 | 34 | // Sending with IdempotencyKey 35 | options := &resend.SendEmailOptions{ 36 | IdempotencyKey: "unique-idempotency-key", 37 | } 38 | 39 | sent, err = client.Emails.SendWithOptions(ctx, params, options) 40 | if err != nil { 41 | panic(err) 42 | } 43 | fmt.Printf("Sent email with idempotency key: %s\n", sent.Id) 44 | 45 | // Get Email 46 | email, err := client.Emails.GetWithContext(ctx, sent.Id) 47 | if err != nil { 48 | panic(err) 49 | } 50 | fmt.Printf("%v\n", email) 51 | } 52 | -------------------------------------------------------------------------------- /examples/with_attachments.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/resend/resend-go/v2" 9 | ) 10 | 11 | func withAttachments() { 12 | ctx := context.TODO() 13 | apiKey := os.Getenv("RESEND_API_KEY") 14 | 15 | if apiKey == "" { 16 | panic("Api Key is missing") 17 | } 18 | 19 | // Read attachment file 20 | pwd, _ := os.Getwd() 21 | f, err := os.ReadFile(pwd + "/resources/invoice.pdf") 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | client := resend.NewClient(apiKey) 27 | 28 | // Create attachments objects 29 | pdfAttachmentFromLocalFile := &resend.Attachment{ 30 | Content: f, 31 | Filename: "invoice1.pdf", 32 | ContentType: "application/pdf", 33 | } 34 | 35 | pdfAttachmentFromRemotePath := &resend.Attachment{ 36 | Path: "https://github.com/resend/resend-go/raw/main/resources/invoice.pdf", 37 | Filename: "invoice2.pdf", 38 | ContentType: "application/pdf", 39 | } 40 | 41 | params := &resend.SendEmailRequest{ 42 | To: []string{"delivered@resend.dev"}, 43 | From: "onboarding@resend.dev", 44 | Text: "email with attachments !!", 45 | Html: "email with attachments !!", 46 | Subject: "Email with attachment", 47 | Attachments: []*resend.Attachment{pdfAttachmentFromLocalFile, pdfAttachmentFromRemotePath}, 48 | } 49 | 50 | sent, err := client.Emails.SendWithContext(ctx, params) 51 | if err != nil { 52 | panic(err) 53 | } 54 | fmt.Println(sent.Id) 55 | } 56 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/resend/resend-go/v2 2 | 3 | go 1.23 4 | 5 | require github.com/stretchr/testify v1.8.2 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 8 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 9 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 10 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 11 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 12 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 16 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 17 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 18 | -------------------------------------------------------------------------------- /resend.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "io" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | const ( 16 | version = "2.20.0" 17 | userAgent = "resend-go/" + version 18 | contentType = "application/json" 19 | ) 20 | 21 | var defaultBaseURL = getEnv("RESEND_BASE_URL", "https://api.resend.com/") 22 | 23 | var defaultHTTPClient = &http.Client{ 24 | Timeout: time.Minute, 25 | } 26 | 27 | // Options interface is used to define additional options that can be passed 28 | // to the API methods. 29 | type Options interface { 30 | // GetIdempotencyKey returns the idempotency key 31 | GetIdempotencyKey() string 32 | } 33 | 34 | // Client handles communication with Resend API. 35 | type Client struct { 36 | // HTTP client 37 | client *http.Client 38 | 39 | // Api Key 40 | ApiKey string 41 | 42 | // Base URL 43 | BaseURL *url.URL 44 | 45 | // User agent for client 46 | UserAgent string 47 | 48 | // HTTP headers 49 | headers map[string]string 50 | 51 | // Services 52 | Emails EmailsSvc 53 | Batch BatchSvc 54 | ApiKeys ApiKeysSvc 55 | Domains DomainsSvc 56 | Audiences AudiencesSvc 57 | Contacts ContactsSvc 58 | Broadcasts BroadcastsSvc 59 | } 60 | 61 | // NewClient is the default client constructor 62 | func NewClient(apiKey string) *Client { 63 | key := strings.Trim(strings.TrimSpace(apiKey), "'") 64 | return NewCustomClient(defaultHTTPClient, key) 65 | } 66 | 67 | // NewCustomClient builds a new Resend API client, using a provided Http client. 68 | func NewCustomClient(httpClient *http.Client, apiKey string) *Client { 69 | if httpClient == nil { 70 | httpClient = defaultHTTPClient 71 | } 72 | 73 | baseURL, _ := url.Parse(defaultBaseURL) 74 | 75 | c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent} 76 | 77 | c.Emails = &EmailsSvcImpl{client: c} 78 | c.Batch = &BatchSvcImpl{client: c} 79 | c.ApiKeys = &ApiKeysSvcImpl{client: c} 80 | c.Domains = &DomainsSvcImpl{client: c} 81 | c.Audiences = &AudiencesSvcImpl{client: c} 82 | c.Contacts = &ContactsSvcImpl{client: c} 83 | c.Broadcasts = &BroadcastsSvcImpl{client: c} 84 | 85 | c.ApiKey = apiKey 86 | c.headers = make(map[string]string) 87 | return c 88 | } 89 | 90 | // NewRequestWithOptions builds and returns a new HTTP request object 91 | // based on the given arguments and options 92 | // It is used to set additional options like idempotency key 93 | func (c *Client) NewRequestWithOptions(ctx context.Context, method, path string, params interface{}, options Options) (*http.Request, error) { 94 | req, err := c.NewRequest(ctx, method, path, params) 95 | 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | // Set the idempotency key if provided and the method is POST for now. 101 | if options != nil { 102 | if options.GetIdempotencyKey() != "" && method == http.MethodPost { 103 | req.Header.Set("Idempotency-Key", options.GetIdempotencyKey()) 104 | } 105 | } 106 | 107 | return req, nil 108 | } 109 | 110 | // NewRequest builds and returns a new HTTP request object 111 | // based on the given arguments 112 | func (c *Client) NewRequest(ctx context.Context, method, path string, params interface{}) (*http.Request, error) { 113 | u, err := c.BaseURL.Parse(path) 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | var req *http.Request 119 | req, err = http.NewRequestWithContext(ctx, method, u.String(), nil) 120 | if err != nil { 121 | return nil, err 122 | } 123 | 124 | if params != nil { 125 | buf := new(bytes.Buffer) 126 | err = json.NewEncoder(buf).Encode(params) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | req.Body = io.NopCloser(buf) 132 | req.Header.Set("Content-Type", contentType) 133 | } 134 | 135 | for k, v := range c.headers { 136 | req.Header.Add(k, v) 137 | } 138 | 139 | req.Header.Set("Accept", contentType) 140 | req.Header.Set("User-Agent", c.UserAgent) 141 | req.Header.Set("Authorization", "Bearer "+c.ApiKey) 142 | 143 | return req, nil 144 | } 145 | 146 | // Perform sends the request to the Resend API 147 | func (c *Client) Perform(req *http.Request, ret interface{}) (*http.Response, error) { 148 | resp, err := c.client.Do(req) 149 | if err != nil { 150 | return nil, err 151 | } 152 | defer resp.Body.Close() 153 | 154 | // Handle possible errors. 155 | if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { 156 | return nil, handleError(resp) 157 | } 158 | 159 | if resp.StatusCode != http.StatusNoContent && ret != nil { 160 | if w, ok := ret.(io.Writer); ok { 161 | _, err = io.Copy(w, resp.Body) 162 | if err != nil { 163 | return nil, err 164 | } 165 | } else { 166 | if resp.Body != nil { 167 | err = json.NewDecoder(resp.Body).Decode(ret) 168 | if err != nil { 169 | return nil, err 170 | } 171 | } 172 | } 173 | } 174 | 175 | return resp, err 176 | } 177 | 178 | // handleError tries to handle errors based on HTTP status codes 179 | func handleError(resp *http.Response) error { 180 | switch resp.StatusCode { 181 | 182 | // Handles errors most likely caused by the client 183 | case http.StatusUnprocessableEntity, http.StatusBadRequest: 184 | r := &InvalidRequestError{} 185 | if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") { 186 | err := json.NewDecoder(resp.Body).Decode(r) 187 | if err != nil { 188 | r.Message = resp.Status 189 | } 190 | } else { 191 | r.Message = resp.Status 192 | } 193 | 194 | // TODO: replace this with a new ResendError type 195 | return errors.New("[ERROR]: " + r.Message) 196 | default: 197 | // Tries to parse `message` attr from error 198 | r := &DefaultError{} 199 | 200 | if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") { 201 | err := json.NewDecoder(resp.Body).Decode(r) 202 | if err != nil { 203 | r.Message = resp.Status 204 | } 205 | } else { 206 | r.Message = resp.Status 207 | } 208 | 209 | if r.Message != "" { 210 | // TODO: replace this with a new ResendError type 211 | return errors.New("[ERROR]: " + r.Message) 212 | } 213 | return errors.New("[ERROR]: Unknown Error") 214 | } 215 | } 216 | 217 | type InvalidRequestError struct { 218 | StatusCode int `json:"statusCode"` 219 | Name string `json:"name"` 220 | Message string `json:"message"` 221 | } 222 | 223 | type DefaultError struct { 224 | Message string `json:"message"` 225 | } 226 | -------------------------------------------------------------------------------- /resend_test.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestResend(t *testing.T) { 16 | client := NewClient("123") 17 | assert.NotNil(t, client) 18 | } 19 | 20 | func TestResendRequestHeaders(t *testing.T) { 21 | ctx := context.TODO() 22 | client := NewClient("123") 23 | params := &SendEmailRequest{ 24 | To: []string{"email@example.com", "email2@example.com"}, 25 | } 26 | req, err := client.NewRequest(ctx, "POST", "/emails/", params) 27 | if err != nil { 28 | t.Error(err) 29 | } 30 | assert.Equal(t, req.Header["Accept"][0], "application/json") 31 | assert.Equal(t, req.Header["Content-Type"][0], "application/json") 32 | assert.Equal(t, req.Method, http.MethodPost) 33 | assert.Equal(t, req.URL.String(), "https://api.resend.com/emails/") 34 | assert.Equal(t, req.Header["Authorization"][0], "Bearer 123") 35 | 36 | _, ok := req.Header["Idempotency-Key"] 37 | assert.False(t, ok, "expected 'Idempotency-Key' header to be absent") 38 | } 39 | 40 | func TestResendRequestShouldReturnErrorIfContextIsCancelled(t *testing.T) { 41 | client := NewClient("123") 42 | ctx, cancel := context.WithCancel(context.Background()) 43 | cancel() 44 | req, err := client.NewRequest(ctx, "POST", "/", nil) 45 | if err != nil { 46 | t.Error(err) 47 | } 48 | 49 | res, err := client.Perform(req, nil) 50 | assert.True(t, errors.Unwrap(err) == context.Canceled) 51 | assert.Nil(t, res) 52 | } 53 | 54 | func TestHandleError(t *testing.T) { 55 | cases := []struct { 56 | desc string 57 | resp *http.Response 58 | want error 59 | }{ 60 | { 61 | desc: "validation_error", 62 | resp: &http.Response{ 63 | StatusCode: http.StatusUnprocessableEntity, 64 | Status: fmt.Sprintf("%d %s", http.StatusUnprocessableEntity, http.StatusText(http.StatusUnprocessableEntity)), 65 | Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, 66 | Body: io.NopCloser(bytes.NewBufferString(`{"message":"Validation error"}`)), 67 | }, 68 | want: errors.New("[ERROR]: Validation error"), 69 | }, 70 | { 71 | desc: "validation_error_no_json", 72 | resp: &http.Response{ 73 | StatusCode: http.StatusUnprocessableEntity, 74 | Status: fmt.Sprintf("%d %s", http.StatusUnprocessableEntity, http.StatusText(http.StatusUnprocessableEntity)), 75 | Body: io.NopCloser(bytes.NewBufferString(`Validation error`)), 76 | }, 77 | want: errors.New("[ERROR]: 422 Unprocessable Entity"), 78 | }, 79 | { 80 | desc: "bad_request", 81 | resp: &http.Response{ 82 | StatusCode: http.StatusBadRequest, 83 | Status: fmt.Sprintf("%d %s", http.StatusBadRequest, http.StatusText(http.StatusBadRequest)), 84 | Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, 85 | Body: io.NopCloser(bytes.NewBufferString(`{"message":"Validation error"}`)), 86 | }, 87 | want: errors.New("[ERROR]: Validation error"), 88 | }, 89 | { 90 | desc: "bad_request_no_json", 91 | resp: &http.Response{ 92 | StatusCode: http.StatusBadRequest, 93 | Status: fmt.Sprintf("%d %s", http.StatusBadRequest, http.StatusText(http.StatusBadRequest)), 94 | Body: io.NopCloser(bytes.NewBufferString(`Validation error`)), 95 | }, 96 | want: errors.New("[ERROR]: 400 Bad Request"), 97 | }, 98 | { 99 | desc: "bad_request_invalid_json", 100 | resp: &http.Response{ 101 | StatusCode: http.StatusBadRequest, 102 | Status: fmt.Sprintf("%d %s", http.StatusBadRequest, http.StatusText(http.StatusBadRequest)), 103 | Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, 104 | Body: io.NopCloser(bytes.NewBufferString(`{`)), 105 | }, 106 | want: errors.New("[ERROR]: 400 Bad Request"), 107 | }, 108 | { 109 | desc: "server_error", 110 | resp: &http.Response{ 111 | StatusCode: http.StatusInternalServerError, 112 | Status: fmt.Sprintf("%d %s", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)), 113 | Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, 114 | Body: io.NopCloser(bytes.NewBufferString(`{"message":"Server error"}`)), 115 | }, 116 | want: errors.New("[ERROR]: Server error"), 117 | }, 118 | { 119 | desc: "server_error_no_json", 120 | resp: &http.Response{ 121 | StatusCode: http.StatusInternalServerError, 122 | Status: fmt.Sprintf("%d %s", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)), 123 | Body: io.NopCloser(bytes.NewBufferString(`Server error`)), 124 | }, 125 | want: errors.New("[ERROR]: 500 Internal Server Error"), 126 | }, 127 | { 128 | desc: "server_error_invalid_json", 129 | resp: &http.Response{ 130 | StatusCode: http.StatusInternalServerError, 131 | Status: fmt.Sprintf("%d %s", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)), 132 | Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, 133 | Body: io.NopCloser(bytes.NewBufferString(`{`)), 134 | }, 135 | want: errors.New("[ERROR]: 500 Internal Server Error"), 136 | }, 137 | { 138 | desc: "server_error_no_message", 139 | resp: &http.Response{ 140 | StatusCode: http.StatusInternalServerError, 141 | Status: fmt.Sprintf("%d %s", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)), 142 | Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, 143 | Body: io.NopCloser(bytes.NewBufferString(`{}`)), 144 | }, 145 | want: errors.New("[ERROR]: Unknown Error"), 146 | }, 147 | } 148 | 149 | for _, c := range cases { 150 | t.Run(c.desc, func(t *testing.T) { 151 | err := handleError(c.resp) 152 | assert.Equal(t, c.want, err) 153 | }) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /resources/invoice.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /Length 2 0 R >> 5 | stream 6 | 0.574574 0 0.040483 -0.011009 0.511009 0.604048 d1 7 | 8 | endstream 9 | endobj 10 | 11 | 2 0 obj 12 | 51 13 | endobj 14 | 15 | 3 0 obj 16 | << /Length 4 0 R >> 17 | stream 18 | 0.556818 0 0.034091 -0.204545 0.522727 0.784091 d1 19 | 20 | endstream 21 | endobj 22 | 23 | 4 0 obj 24 | 51 25 | endobj 26 | 27 | 5 0 obj 28 | << /Length 6 0 R >> 29 | stream 30 | 0.248224 0 0.000000 0.000000 0.248224 1.000000 d1 31 | 32 | endstream 33 | endobj 34 | 35 | 6 0 obj 36 | 50 37 | endobj 38 | 39 | 7 0 obj 40 | << /Length 8 0 R >> 41 | stream 42 | 0.575994 0 0.022727 -0.204545 0.553267 0.772727 d1 43 | 44 | endstream 45 | endobj 46 | 47 | 8 0 obj 48 | 51 49 | endobj 50 | 51 | 9 0 obj 52 | << /Length 10 0 R >> 53 | stream 54 | 0.643466 0 0.071733 0.000000 0.600852 0.799006 d1 55 | 56 | endstream 57 | endobj 58 | 59 | 10 0 obj 60 | 50 61 | endobj 62 | 63 | 11 0 obj 64 | << /Length 12 0 R >> 65 | stream 66 | 0.596591 0 0.051136 -0.011364 0.545455 0.615057 d1 67 | 68 | endstream 69 | endobj 70 | 71 | 12 0 obj 72 | 51 73 | endobj 74 | 75 | 13 0 obj 76 | << /Length 14 0 R >> 77 | stream 78 | 0.609730 0 0.066051 0.000000 0.545099 0.618608 d1 79 | 80 | endstream 81 | endobj 82 | 83 | 14 0 obj 84 | 50 85 | endobj 86 | 87 | 15 0 obj 88 | << /Length 16 0 R >> 89 | stream 90 | 0.281250 0 0.000000 0.000000 0.281250 1.000000 d1 91 | 92 | endstream 93 | endobj 94 | 95 | 16 0 obj 96 | 50 97 | endobj 98 | 99 | 17 0 obj 100 | << /Length 18 0 R >> 101 | stream 102 | 0.650568 0 0.071733 0.000000 0.627486 0.799006 d1 103 | 104 | endstream 105 | endobj 106 | 107 | 18 0 obj 108 | 50 109 | endobj 110 | 111 | 19 0 obj 112 | << /Length 20 0 R >> 113 | stream 114 | 0.563920 0 0.051136 -0.012784 0.487216 0.616477 d1 115 | 116 | endstream 117 | endobj 118 | 119 | 20 0 obj 120 | 51 121 | endobj 122 | 123 | 21 0 obj 124 | << /Length 22 0 R >> 125 | stream 126 | 0.607955 0 0.042614 -0.010653 0.565341 0.605824 d1 127 | 128 | endstream 129 | endobj 130 | 131 | 22 0 obj 132 | 51 133 | endobj 134 | 135 | 23 0 obj 136 | << /Length 24 0 R >> 137 | stream 138 | 0.590909 0 0.076705 0.000000 0.514205 0.803977 d1 139 | 140 | endstream 141 | endobj 142 | 143 | 24 0 obj 144 | 50 145 | endobj 146 | 147 | 25 0 obj 148 | << /Length 26 0 R >> 149 | stream 150 | 0.608310 0 0.066051 -0.007102 0.542259 0.618608 d1 151 | 152 | endstream 153 | endobj 154 | 155 | 26 0 obj 156 | 51 157 | endobj 158 | 159 | 27 0 obj 160 | << /Length 28 0 R >> 161 | stream 162 | 0.869318 0 0.076705 0.000000 0.792614 0.629261 d1 163 | 164 | endstream 165 | endobj 166 | 167 | 28 0 obj 168 | 50 169 | endobj 170 | 171 | 29 0 obj 172 | << /Length 30 0 R >> 173 | stream 174 | 0.577415 0 0.042614 -0.010653 0.537642 0.605824 d1 175 | 176 | endstream 177 | endobj 178 | 179 | 30 0 obj 180 | 51 181 | endobj 182 | 183 | 31 0 obj 184 | << /Length 32 0 R >> 185 | stream 186 | 0.642045 0 0.048295 0.000000 0.593750 0.775568 d1 187 | 188 | endstream 189 | endobj 190 | 191 | 32 0 obj 192 | 50 193 | endobj 194 | 195 | 33 0 obj 196 | << /Length 34 0 R >> 197 | stream 198 | 0.379972 0 0.024858 -0.008878 0.348366 0.709872 d1 199 | 200 | endstream 201 | endobj 202 | 203 | 34 0 obj 204 | 51 205 | endobj 206 | 207 | 35 0 obj 208 | << /Length 36 0 R >> 209 | stream 210 | 0.897727 0 0.066051 0.000000 0.833097 0.618608 d1 211 | 212 | endstream 213 | endobj 214 | 215 | 36 0 obj 216 | 50 217 | endobj 218 | 219 | 37 0 obj 220 | << /Length 38 0 R >> 221 | stream 222 | 0.396662 0 0.066051 0.000000 0.377131 0.619318 d1 223 | 224 | endstream 225 | endobj 226 | 227 | 38 0 obj 228 | 50 229 | endobj 230 | 231 | 39 0 obj 232 | << /Length 40 0 R >> 233 | stream 234 | 0.377131 0 0.019886 0.000000 0.368608 0.781250 d1 235 | 236 | endstream 237 | endobj 238 | 239 | 40 0 obj 240 | 50 241 | endobj 242 | 243 | 41 0 obj 244 | << /Length 42 0 R >> 245 | stream 246 | 0.260653 0 0.056108 0.000000 0.205256 0.818537 d1 247 | 248 | endstream 249 | endobj 250 | 251 | 42 0 obj 252 | 50 253 | endobj 254 | 255 | 43 0 obj 256 | << /Length 44 0 R >> 257 | stream 258 | 0.363636 0 0.031250 -0.007102 0.323864 0.714489 d1 259 | 260 | endstream 261 | endobj 262 | 263 | 44 0 obj 264 | 51 265 | endobj 266 | 267 | 45 0 obj 268 | << /Length 46 0 R >> 269 | stream 270 | 0.624645 0 0.066051 -0.204545 0.580966 0.823153 d1 271 | 272 | endstream 273 | endobj 274 | 275 | 46 0 obj 276 | 51 277 | endobj 278 | 279 | 47 0 obj 280 | << /Length 48 0 R >> 281 | stream 282 | 0.360795 0 0.025568 0.000000 0.346591 0.786932 d1 283 | 284 | endstream 285 | endobj 286 | 287 | 48 0 obj 288 | 50 289 | endobj 290 | 291 | 49 0 obj 292 | << /Length 50 0 R >> 293 | stream 294 | 0.592685 0 0.042614 -0.010653 0.550071 0.605824 d1 295 | 296 | endstream 297 | endobj 298 | 299 | 50 0 obj 300 | 51 301 | endobj 302 | 303 | 51 0 obj 304 | << /Length 52 0 R >> 305 | stream 306 | 0.275568 0 0.073864 -0.005682 0.201705 0.201705 d1 307 | 308 | endstream 309 | endobj 310 | 311 | 52 0 obj 312 | 51 313 | endobj 314 | 315 | 53 0 obj 316 | << /Length 54 0 R >> 317 | stream 318 | 0.582386 0 0.051136 -0.011364 0.531250 0.615057 d1 319 | 320 | endstream 321 | endobj 322 | 323 | 54 0 obj 324 | 51 325 | endobj 326 | 327 | 55 0 obj 328 | << /Length 56 0 R >> 329 | stream 330 | 0.704545 0 0.022017 0.000000 0.682528 0.749290 d1 331 | 332 | endstream 333 | endobj 334 | 335 | 56 0 obj 336 | 50 337 | endobj 338 | 339 | 57 0 obj 340 | << /Length 58 0 R >> 341 | stream 342 | 0.609375 0 0.076705 -0.204545 0.558239 0.833807 d1 343 | 344 | endstream 345 | endobj 346 | 347 | 58 0 obj 348 | 51 349 | endobj 350 | 351 | 59 0 obj 352 | << /Length 60 0 R >> 353 | stream 354 | 0.522727 0 0.052557 -0.011364 0.473011 0.616477 d1 355 | 356 | endstream 357 | endobj 358 | 359 | 60 0 obj 360 | 51 361 | endobj 362 | 363 | 61 0 obj 364 | << /Length 62 0 R >> 365 | stream 366 | 0.585227 0 0.076705 0.000000 0.508523 0.629261 d1 367 | 368 | endstream 369 | endobj 370 | 371 | 62 0 obj 372 | 50 373 | endobj 374 | 375 | 63 0 obj 376 | << /Length 64 0 R >> 377 | stream 378 | 0.544034 0 0.076705 0.000000 0.529830 0.803977 d1 379 | 380 | endstream 381 | endobj 382 | 383 | 64 0 obj 384 | 50 385 | endobj 386 | 387 | 65 0 obj 388 | << /Length 66 0 R >> 389 | stream 390 | 0.372159 0 0.076705 0.000000 0.349432 0.630682 d1 391 | 392 | endstream 393 | endobj 394 | 395 | 66 0 obj 396 | 50 397 | endobj 398 | 399 | 67 0 obj 400 | [ 0.609730 0.897727 0.575994 0.574574 0.643466 0.608310 0.704545 0.607955 0.379972 0.248224 0.396662 0.377131 0.260653 0.624645 0.592685 0.577415 0.650568 0.275568 0.556818 0.582386 0.869318 0.363636 0.360795 0.522727 0.281250 0.585227 0.544034 0.563920 0.590909 0.372159 0.596591 0.609375 0.642045 ] 401 | endobj 402 | 403 | 68 0 obj 404 | << /Length 69 0 R >> 405 | stream 406 | /CIDInit /ProcSet findresource begin 407 | 12 dict begin 408 | begincmap 409 | /CIDSystemInfo 410 | << /Registry (FigmaPDF) 411 | /Ordering (FigmaPDF) 412 | /Supplement 0 413 | >> def 414 | /CMapName /A-B-C def 415 | /CMapType 2 def 416 | 1 begincodespacerange 417 | <00> 418 | endcodespacerange 419 | 1 beginbfchar 420 | <00> <006E> 421 | endbfchar 422 | 1 beginbfchar 423 | <01> <006D> 424 | endbfchar 425 | 1 beginbfchar 426 | <02> <0079> 427 | endbfchar 428 | 1 beginbfchar 429 | <03> <0061> 430 | endbfchar 431 | 1 beginbfchar 432 | <04> <0050> 433 | endbfchar 434 | 1 beginbfchar 435 | <05> <0075> 436 | endbfchar 437 | 1 beginbfchar 438 | <06> <0059> 439 | endbfchar 440 | 1 beginbfchar 441 | <07> <006F> 442 | endbfchar 443 | 1 beginbfchar 444 | <08> <0074> 445 | endbfchar 446 | 1 beginbfchar 447 | <09> <0020> 448 | endbfchar 449 | 1 beginbfchar 450 | <0A> <0072> 451 | endbfchar 452 | 1 beginbfchar 453 | <0B> <0066> 454 | endbfchar 455 | 1 beginbfchar 456 | <0C> <0069> 457 | endbfchar 458 | 1 beginbfchar 459 | <0D> <0070> 460 | endbfchar 461 | 1 beginbfchar 462 | <0E> <0065> 463 | endbfchar 464 | 1 beginbfchar 465 | <0F> <0063> 466 | endbfchar 467 | 1 beginbfchar 468 | <10> <0052> 469 | endbfchar 470 | 1 beginbfchar 471 | <11> <002E> 472 | endbfchar 473 | 1 beginbfchar 474 | <12> <0079> 475 | endbfchar 476 | 1 beginbfchar 477 | <13> <0065> 478 | endbfchar 479 | 1 beginbfchar 480 | <14> <006D> 481 | endbfchar 482 | 1 beginbfchar 483 | <15> <0074> 484 | endbfchar 485 | 1 beginbfchar 486 | <16> <0066> 487 | endbfchar 488 | 1 beginbfchar 489 | <17> <0073> 490 | endbfchar 491 | 1 beginbfchar 492 | <18> <0020> 493 | endbfchar 494 | 1 beginbfchar 495 | <19> <006E> 496 | endbfchar 497 | 1 beginbfchar 498 | <1A> <006B> 499 | endbfchar 500 | 1 beginbfchar 501 | <1B> <0061> 502 | endbfchar 503 | 1 beginbfchar 504 | <1C> <0068> 505 | endbfchar 506 | 1 beginbfchar 507 | <1D> <0072> 508 | endbfchar 509 | 1 beginbfchar 510 | <1E> <006F> 511 | endbfchar 512 | 1 beginbfchar 513 | <1F> <0070> 514 | endbfchar 515 | 1 beginbfchar 516 | <20> <0054> 517 | endbfchar 518 | endcmap 519 | CMapName currentdict /CMap defineresource pop 520 | end 521 | end 522 | endstream 523 | endobj 524 | 525 | 69 0 obj 526 | 1484 527 | endobj 528 | 529 | 70 0 obj 530 | << /Subtype /Type3 531 | /CharProcs << /C18 3 0 R 532 | /C3 1 0 R 533 | /C2 7 0 R 534 | /C30 11 0 R 535 | /C4 9 0 R 536 | /C11 39 0 R 537 | /C9 5 0 R 538 | /C0 13 0 R 539 | /C24 15 0 R 540 | /C27 19 0 R 541 | /C16 17 0 R 542 | /C7 21 0 R 543 | /C28 23 0 R 544 | /C12 41 0 R 545 | /C21 43 0 R 546 | /C5 25 0 R 547 | /C20 27 0 R 548 | /C15 29 0 R 549 | /C32 31 0 R 550 | /C8 33 0 R 551 | /C1 35 0 R 552 | /C10 37 0 R 553 | /C13 45 0 R 554 | /C22 47 0 R 555 | /C14 49 0 R 556 | /C17 51 0 R 557 | /C19 53 0 R 558 | /C6 55 0 R 559 | /C31 57 0 R 560 | /C23 59 0 R 561 | /C25 61 0 R 562 | /C26 63 0 R 563 | /C29 65 0 R 564 | >> 565 | /Encoding << /Type /Encoding 566 | /Differences [ 0 /C0 /C1 /C2 /C3 /C4 /C5 /C6 /C7 /C8 /C9 /C10 /C11 /C12 /C13 /C14 /C15 /C16 /C17 /C18 /C19 /C20 /C21 /C22 /C23 /C24 /C25 /C26 /C27 /C28 /C29 /C30 /C31 /C32 ] 567 | >> 568 | /Widths 67 0 R 569 | /FontBBox [ 0.000000 0.000000 0.000000 0.000000 ] 570 | /FontMatrix [ 1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 ] 571 | /Type /Font 572 | /ToUnicode 68 0 R 573 | /FirstChar 0 574 | /LastChar 32 575 | /Resources << >> 576 | >> 577 | endobj 578 | 579 | 71 0 obj 580 | << /Length 72 0 R 581 | /Range [ 0.000000 1.000000 ] 582 | /Domain [ 0.000000 1.000000 ] 583 | /FunctionType 4 584 | >> 585 | stream 586 | { 1.000000 exch dup 0.000000 gt { exch pop dup 0.000000 sub -1.000000 mul 1.000000 add exch } if dup 1.000000 gt { exch pop 0.000000 exch } if pop } 587 | endstream 588 | endobj 589 | 590 | 72 0 obj 591 | 149 592 | endobj 593 | 594 | 73 0 obj 595 | << /BBox [ 0.000000 0.000000 500.000000 700.000000 ] 596 | /Resources << /Pattern << /P1 << /Matrix [ 35.064983 -28.864431 28.864431 35.064983 43.777412 595.760620 ] 597 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 598 | /ColorSpace /DeviceGray 599 | /Function 71 0 R 600 | /Domain [ 0.000000 1.000000 ] 601 | /ShadingType 2 602 | /Extend [ true true ] 603 | >> 604 | /PatternType 2 605 | /Type /Pattern 606 | >> >> >> 607 | /Subtype /Form 608 | /Length 74 0 R 609 | /Group << /Type /Group 610 | /S /Transparency 611 | /CS /DeviceGray 612 | >> 613 | /Type /XObject 614 | >> 615 | stream 616 | /DeviceGray CS 617 | /DeviceGray cs 618 | 1.000000 0.000000 -0.000000 1.000000 71.476868 612.707703 cm 619 | 0.000000 -0.000017 m 620 | 0.000000 19.076904 l 621 | 7.247755 19.076904 l 622 | 8.732555 19.076904 9.978277 18.822321 10.984923 18.313089 c 623 | 11.997847 17.803858 12.762276 17.089705 13.278153 16.170628 c 624 | 13.800368 15.257766 14.061415 14.192782 14.061415 12.975613 c 625 | 14.061415 11.752291 13.797171 10.690382 13.268740 9.789951 c 626 | 12.746524 8.895737 11.975815 8.203305 10.956615 7.712721 c 627 | 9.937353 7.228352 8.685353 6.986135 7.200614 6.986135 c 628 | 2.038462 6.986135 l 629 | 2.038462 9.855121 l 630 | 6.728738 9.855121 l 631 | 7.596922 9.855121 8.307877 9.973151 8.861539 10.209089 c 632 | 9.415200 10.451304 9.824122 10.802135 10.088368 11.261703 c 633 | 10.358891 11.727427 10.494154 12.298752 10.494154 12.975613 c 634 | 10.494154 13.652536 10.358891 14.230074 10.088368 14.708227 c 635 | 9.817845 15.192596 9.405723 15.558997 8.852122 15.807366 c 636 | 8.298461 16.061951 7.584368 16.189274 6.709845 16.189274 c 637 | 3.501231 16.189274 l 638 | 3.501231 -0.000017 l 639 | 0.000000 -0.000017 l 640 | h 641 | 9.984555 8.644228 m 642 | 14.769230 -0.000017 l 643 | 10.862215 -0.000017 l 644 | 6.162524 8.644228 l 645 | 9.984555 8.644228 l 646 | h 647 | /Pattern cs 648 | /P1 scn 649 | f 650 | n 651 | 652 | endstream 653 | endobj 654 | 655 | 74 0 obj 656 | 1128 657 | endobj 658 | 659 | 75 0 obj 660 | << /Length 76 0 R 661 | /Range [ 0.000000 1.000000 ] 662 | /Domain [ 0.000000 1.000000 ] 663 | /FunctionType 4 664 | >> 665 | stream 666 | { 0.600000 exch dup 0.000000 gt { exch pop dup 0.000000 sub -0.400000 mul 0.600000 add exch } if dup 1.000000 gt { exch pop 0.200000 exch } if pop } 667 | endstream 668 | endobj 669 | 670 | 76 0 obj 671 | 149 672 | endobj 673 | 674 | 77 0 obj 675 | << /BBox [ 0.000000 0.000000 500.000000 700.000000 ] 676 | /Resources << /Pattern << /P1 << /Matrix [ 0.000000 -44.307686 44.307686 0.000000 9.692314 644.153870 ] 677 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 678 | /ColorSpace /DeviceGray 679 | /Function 75 0 R 680 | /Domain [ 0.000000 1.000000 ] 681 | /ShadingType 2 682 | /Extend [ true true ] 683 | >> 684 | /PatternType 2 685 | /Type /Pattern 686 | >> >> >> 687 | /Subtype /Form 688 | /Length 78 0 R 689 | /Group << /Type /Group 690 | /S /Transparency 691 | /CS /DeviceGray 692 | >> 693 | /Type /XObject 694 | >> 695 | stream 696 | /DeviceGray CS 697 | /DeviceGray cs 698 | 1.000000 0.000000 -0.000000 1.000000 54.000000 598.000000 cm 699 | 30.481234 48.000000 m 700 | 33.169601 48.000015 35.300064 48.000027 37.017292 47.859722 c 701 | 38.773293 47.716248 40.259140 47.416904 41.615879 46.725613 c 702 | 43.815941 45.604633 45.604618 43.815937 46.725601 41.615875 c 703 | 47.416924 40.259136 47.716251 38.773293 47.859695 37.017292 c 704 | 48.000004 35.300060 48.000000 33.169601 48.000000 30.481293 c 705 | 48.000000 17.518705 l 706 | 48.000000 14.830399 48.000004 12.699936 47.859695 10.982708 c 707 | 47.716251 9.226707 47.416924 7.740860 46.725601 6.384121 c 708 | 45.604618 4.184059 43.815941 2.395382 41.615879 1.274399 c 709 | 40.259140 0.583076 38.773293 0.283749 37.017292 0.140305 c 710 | 35.300064 -0.000004 33.169601 0.000000 30.481295 0.000000 c 711 | 17.518707 0.000000 l 712 | 14.830400 0.000000 12.699940 -0.000004 10.982709 0.140305 c 713 | 9.226708 0.283749 7.740862 0.583076 6.384123 1.274399 c 714 | 4.184062 2.395382 2.395366 4.184059 1.274388 6.384121 c 715 | 0.583094 7.740860 0.283751 9.226707 0.140278 10.982708 c 716 | -0.000025 12.699936 -0.000014 14.830399 0.000000 17.518766 c 717 | 0.000000 30.481230 l 718 | -0.000014 33.169601 -0.000025 35.300060 0.140278 37.017292 c 719 | 0.283751 38.773293 0.583094 40.259136 1.274388 41.615875 c 720 | 2.395366 43.815937 4.184062 45.604633 6.384123 46.725613 c 721 | 7.740862 47.416908 9.226708 47.716248 10.982709 47.859722 c 722 | 12.699940 48.000027 14.830400 48.000015 17.518770 48.000000 c 723 | 30.481234 48.000000 l 724 | h 725 | 2.919323 40.777721 m 726 | 1.846154 38.671570 1.846154 35.914337 1.846154 30.400000 c 727 | 1.846154 17.599998 l 728 | 1.846154 12.085659 1.846154 9.328430 2.919323 7.222275 c 729 | 3.863305 5.369598 5.369576 3.863319 7.222277 2.919319 c 730 | 9.328431 1.846149 12.085662 1.846153 17.600000 1.846153 c 731 | 30.400002 1.846153 l 732 | 35.914341 1.846153 38.671570 1.846149 40.777725 2.919319 c 733 | 42.630402 3.863319 44.136681 5.369598 45.080681 7.222275 c 734 | 46.153851 9.328430 46.153847 12.085659 46.153847 17.599998 c 735 | 46.153847 30.400000 l 736 | 46.153847 35.914337 46.153851 38.671570 45.080681 40.777721 c 737 | 44.136681 42.630424 42.630402 44.136696 40.777725 45.080677 c 738 | 38.671570 46.153847 35.914341 46.153847 30.400002 46.153847 c 739 | 17.600000 46.153847 l 740 | 12.085662 46.153847 9.328431 46.153847 7.222277 45.080677 c 741 | 5.369576 44.136696 3.863305 42.630424 2.919323 40.777721 c 742 | h 743 | /Pattern cs 744 | /P1 scn 745 | f* 746 | n 747 | 748 | endstream 749 | endobj 750 | 751 | 78 0 obj 752 | 2249 753 | endobj 754 | 755 | 79 0 obj 756 | << /Length 80 0 R 757 | /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] 758 | /Domain [ 0.000000 1.000000 ] 759 | /FunctionType 4 760 | >> 761 | stream 762 | { 1.000000 exch 1.000000 exch 1.000000 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub 0.000000 mul 1.000000 add exch dup 0.000000 sub 0.000000 mul 1.000000 add exch dup 0.000000 sub 0.000000 mul 1.000000 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 1.000000 exch 1.000000 exch 1.000000 exch } if pop } 763 | endstream 764 | endobj 765 | 766 | 80 0 obj 767 | 336 768 | endobj 769 | 770 | 81 0 obj 771 | << /Length 82 0 R 772 | /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] 773 | /Domain [ 0.000000 1.000000 ] 774 | /FunctionType 4 775 | >> 776 | stream 777 | { 1.000000 exch 1.000000 exch 1.000000 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub 0.000000 mul 1.000000 add exch dup 0.000000 sub 0.000000 mul 1.000000 add exch dup 0.000000 sub 0.000000 mul 1.000000 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 1.000000 exch 1.000000 exch 1.000000 exch } if pop } 778 | endstream 779 | endobj 780 | 781 | 82 0 obj 782 | 336 783 | endobj 784 | 785 | 83 0 obj 786 | << /Length 84 0 R 787 | /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] 788 | /Domain [ 0.000000 1.000000 ] 789 | /FunctionType 4 790 | >> 791 | stream 792 | { 0.298039 exch 0.298039 exch 0.341176 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub -0.626471 mul 0.298039 add exch dup 0.000000 sub -0.626471 mul 0.298039 add exch dup 0.000000 sub -0.679412 mul 0.341176 add exch } if dup 0.444444 gt { exch pop exch pop exch pop dup 0.444444 sub -0.035294 mul 0.019608 add exch dup 0.444444 sub -0.035294 mul 0.019608 add exch dup 0.444444 sub -0.070588 mul 0.039216 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.000000 exch 0.000000 exch 0.000000 exch } if pop } 793 | endstream 794 | endobj 795 | 796 | 84 0 obj 797 | 536 798 | endobj 799 | 800 | 85 0 obj 801 | << /Length 86 0 R 802 | /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] 803 | /Domain [ 0.000000 1.000000 ] 804 | /FunctionType 4 805 | >> 806 | stream 807 | { 0.298039 exch 0.298039 exch 0.341176 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub -0.626471 mul 0.298039 add exch dup 0.000000 sub -0.626471 mul 0.298039 add exch dup 0.000000 sub -0.679412 mul 0.341176 add exch } if dup 0.444444 gt { exch pop exch pop exch pop dup 0.444444 sub -0.035294 mul 0.019608 add exch dup 0.444444 sub -0.035294 mul 0.019608 add exch dup 0.444444 sub -0.070588 mul 0.039216 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.000000 exch 0.000000 exch 0.000000 exch } if pop } 808 | endstream 809 | endobj 810 | 811 | 86 0 obj 812 | 536 813 | endobj 814 | 815 | 87 0 obj 816 | << /Font << /F1 70 0 R >> 817 | /ExtGState << /E2 << /SMask << /Type /Mask 818 | /G 73 0 R 819 | /S /Luminosity 820 | >> 821 | /Type /ExtGState 822 | >> 823 | /E1 << /SMask << /Type /Mask 824 | /G 77 0 R 825 | /S /Luminosity 826 | >> 827 | /Type /ExtGState 828 | >> 829 | >> 830 | /Pattern << /P4 << /Matrix [ 35.064983 -28.864431 28.864431 35.064983 43.777412 595.760620 ] 831 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 832 | /ColorSpace /DeviceRGB 833 | /Function 79 0 R 834 | /Domain [ 0.000000 1.000000 ] 835 | /ShadingType 2 836 | /Extend [ true true ] 837 | >> 838 | /PatternType 2 839 | /Type /Pattern 840 | >> 841 | /P3 << /Matrix [ 0.000000 -44.307686 44.307686 0.000000 9.692314 644.153870 ] 842 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 843 | /ColorSpace /DeviceRGB 844 | /Function 81 0 R 845 | /Domain [ 0.000000 1.000000 ] 846 | /ShadingType 2 847 | /Extend [ true true ] 848 | >> 849 | /PatternType 2 850 | /Type /Pattern 851 | >> 852 | /P2 << /Matrix [ 0.000000 -58.461540 58.461540 0.000000 -2.615410 658.307739 ] 853 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 854 | /ColorSpace /DeviceRGB 855 | /Function 83 0 R 856 | /Domain [ 0.000000 1.000000 ] 857 | /ShadingType 2 858 | /Extend [ true true ] 859 | >> 860 | /PatternType 2 861 | /Type /Pattern 862 | >> 863 | /P1 << /Matrix [ 0.000000 -58.461540 58.461540 0.000000 -2.615410 658.307739 ] 864 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 865 | /ColorSpace /DeviceRGB 866 | /Function 85 0 R 867 | /Domain [ 0.000000 1.000000 ] 868 | /ShadingType 2 869 | /Extend [ true true ] 870 | >> 871 | /PatternType 2 872 | /Type /Pattern 873 | >> 874 | >> 875 | >> 876 | endobj 877 | 878 | 88 0 obj 879 | << /Length 89 0 R >> 880 | stream 881 | /DeviceRGB CS 882 | /DeviceRGB cs 883 | q 884 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 885 | 1.000000 1.000000 1.000000 scn 886 | 0.000000 700.000000 m 887 | 500.000000 700.000000 l 888 | 500.000000 0.000000 l 889 | 0.000000 0.000000 l 890 | 0.000000 700.000000 l 891 | h 892 | f 893 | n 894 | Q 895 | q 896 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 897 | 0.000000 0.000000 0.000000 scn 898 | 0.000000 700.000000 m 899 | 500.000000 700.000000 l 900 | 500.000000 0.000000 l 901 | 0.000000 0.000000 l 902 | 0.000000 700.000000 l 903 | h 904 | f 905 | n 906 | Q 907 | q 908 | 1.000000 0.000000 -0.000000 1.000000 55.846130 599.846191 cm 909 | /Pattern cs 910 | /P1 scn 911 | 0.000000 28.553831 m 912 | 0.000000 34.068169 -0.000000 36.825401 1.073169 38.931557 c 913 | 2.017151 40.784256 3.523422 42.290527 5.376123 43.234509 c 914 | 7.482277 44.307678 10.239509 44.307678 15.753847 44.307678 c 915 | 28.553848 44.307678 l 916 | 34.068188 44.307678 36.825417 44.307678 38.931572 43.234509 c 917 | 40.784248 42.290527 42.290527 40.784256 43.234528 38.931557 c 918 | 44.307697 36.825401 44.307693 34.068169 44.307693 28.553831 c 919 | 44.307693 15.753830 l 920 | 44.307693 10.239491 44.307697 7.482262 43.234528 5.376106 c 921 | 42.290527 3.523430 40.784248 2.017151 38.931572 1.073151 c 922 | 36.825417 -0.000019 34.068188 -0.000015 28.553848 -0.000015 c 923 | 15.753847 -0.000015 l 924 | 10.239509 -0.000015 7.482277 -0.000019 5.376123 1.073151 c 925 | 3.523422 2.017151 2.017151 3.523430 1.073169 5.376106 c 926 | -0.000000 7.482262 0.000000 10.239491 0.000000 15.753830 c 927 | 0.000000 28.553831 l 928 | h 929 | f 930 | n 931 | Q 932 | q 933 | 1.000000 0.000000 -0.000000 1.000000 55.846130 599.846191 cm 934 | 0.000000 0.000000 0.000000 scn 935 | 28.553848 42.461525 m 936 | 15.753847 42.461525 l 937 | 12.966216 42.461525 10.986524 42.460091 9.437232 42.333511 c 938 | 7.909539 42.208694 6.963692 41.971451 6.214215 41.589573 c 939 | 4.708923 40.822590 3.485090 39.598755 2.718105 38.093464 c 940 | 2.336228 37.343987 2.098985 36.398140 1.974166 34.870445 c 941 | 1.847588 33.321156 1.846154 31.341461 1.846154 28.553831 c 942 | 1.846154 15.753830 l 943 | 1.846154 12.966200 1.847588 10.986507 1.974166 9.437214 c 944 | 2.098985 7.909523 2.336228 6.963676 2.718105 6.214199 c 945 | 3.485090 4.708908 4.708923 3.485092 6.214215 2.718079 c 946 | 6.963692 2.336231 7.909539 2.098999 9.437232 1.974140 c 947 | 10.986524 1.847553 12.966216 1.846138 15.753847 1.846138 c 948 | 28.553848 1.846138 l 949 | 31.341478 1.846138 33.321171 1.847553 34.870464 1.974140 c 950 | 36.398155 2.098999 37.344002 2.336231 38.093479 2.718079 c 951 | 39.598770 3.485092 40.822586 4.708908 41.589600 6.214199 c 952 | 41.971447 6.963676 42.208679 7.909523 42.333538 9.437214 c 953 | 42.460125 10.986507 42.461540 12.966200 42.461540 15.753830 c 954 | 42.461540 28.553831 l 955 | 42.461540 31.341461 42.460125 33.321156 42.333538 34.870445 c 956 | 42.208679 36.398140 41.971447 37.343987 41.589600 38.093464 c 957 | 40.822586 39.598755 39.598770 40.822590 38.093479 41.589573 c 958 | 37.344002 41.971451 36.398155 42.208694 34.870464 42.333511 c 959 | 33.321171 42.460091 31.341478 42.461525 28.553848 42.461525 c 960 | h 961 | 1.073169 38.931557 m 962 | -0.000000 36.825401 0.000000 34.068169 0.000000 28.553831 c 963 | 0.000000 15.753830 l 964 | 0.000000 10.239491 -0.000000 7.482262 1.073169 5.376106 c 965 | 2.017151 3.523430 3.523422 2.017151 5.376123 1.073151 c 966 | 7.482277 -0.000019 10.239509 -0.000015 15.753847 -0.000015 c 967 | 28.553848 -0.000015 l 968 | 34.068188 -0.000015 36.825417 -0.000019 38.931572 1.073151 c 969 | 40.784248 2.017151 42.290527 3.523430 43.234528 5.376106 c 970 | 44.307697 7.482262 44.307693 10.239491 44.307693 15.753830 c 971 | 44.307693 28.553831 l 972 | 44.307693 34.068169 44.307697 36.825401 43.234528 38.931557 c 973 | 42.290527 40.784256 40.784248 42.290527 38.931572 43.234509 c 974 | 36.825417 44.307678 34.068188 44.307678 28.553848 44.307678 c 975 | 15.753847 44.307678 l 976 | 10.239509 44.307678 7.482277 44.307678 5.376123 43.234509 c 977 | 3.523422 42.290527 2.017151 40.784256 1.073169 38.931557 c 978 | h 979 | f* 980 | n 981 | Q 982 | q 983 | 1.000000 0.000000 -0.000000 1.000000 55.846130 599.846191 cm 984 | /Pattern cs 985 | /P2 scn 986 | 0.000000 28.553831 m 987 | 0.000000 34.068169 -0.000000 36.825401 1.073169 38.931557 c 988 | 2.017151 40.784256 3.523422 42.290527 5.376123 43.234509 c 989 | 7.482277 44.307678 10.239509 44.307678 15.753847 44.307678 c 990 | 28.553848 44.307678 l 991 | 34.068188 44.307678 36.825417 44.307678 38.931572 43.234509 c 992 | 40.784248 42.290527 42.290527 40.784256 43.234528 38.931557 c 993 | 44.307697 36.825401 44.307693 34.068169 44.307693 28.553831 c 994 | 44.307693 15.753830 l 995 | 44.307693 10.239491 44.307697 7.482262 43.234528 5.376106 c 996 | 42.290527 3.523430 40.784248 2.017151 38.931572 1.073151 c 997 | 36.825417 -0.000019 34.068188 -0.000015 28.553848 -0.000015 c 998 | 15.753847 -0.000015 l 999 | 10.239509 -0.000015 7.482277 -0.000019 5.376123 1.073151 c 1000 | 3.523422 2.017151 2.017151 3.523430 1.073169 5.376106 c 1001 | -0.000000 7.482262 0.000000 10.239491 0.000000 15.753830 c 1002 | 0.000000 28.553831 l 1003 | h 1004 | f 1005 | n 1006 | Q 1007 | q 1008 | 1.000000 0.000000 -0.000000 1.000000 54.000000 598.000000 cm 1009 | 0.000000 0.000000 0.000000 scn 1010 | 30.481234 48.000000 m 1011 | 33.169601 48.000015 35.300064 48.000027 37.017292 47.859722 c 1012 | 38.773293 47.716248 40.259140 47.416904 41.615879 46.725613 c 1013 | 43.815941 45.604633 45.604618 43.815937 46.725601 41.615875 c 1014 | 47.416924 40.259136 47.716251 38.773293 47.859695 37.017292 c 1015 | 48.000004 35.300060 48.000000 33.169601 48.000000 30.481293 c 1016 | 48.000000 17.518705 l 1017 | 48.000000 14.830399 48.000004 12.699936 47.859695 10.982708 c 1018 | 47.716251 9.226707 47.416924 7.740860 46.725601 6.384121 c 1019 | 45.604618 4.184059 43.815941 2.395382 41.615879 1.274399 c 1020 | 40.259140 0.583076 38.773293 0.283749 37.017292 0.140305 c 1021 | 35.300064 -0.000004 33.169601 0.000000 30.481295 0.000000 c 1022 | 17.518707 0.000000 l 1023 | 14.830400 0.000000 12.699940 -0.000004 10.982709 0.140305 c 1024 | 9.226708 0.283749 7.740862 0.583076 6.384123 1.274399 c 1025 | 4.184062 2.395382 2.395366 4.184059 1.274388 6.384121 c 1026 | 0.583094 7.740860 0.283751 9.226707 0.140278 10.982708 c 1027 | -0.000025 12.699936 -0.000014 14.830399 0.000000 17.518766 c 1028 | 0.000000 30.481230 l 1029 | -0.000014 33.169601 -0.000025 35.300060 0.140278 37.017292 c 1030 | 0.283751 38.773293 0.583094 40.259136 1.274388 41.615875 c 1031 | 2.395366 43.815937 4.184062 45.604633 6.384123 46.725613 c 1032 | 7.740862 47.416908 9.226708 47.716248 10.982709 47.859722 c 1033 | 12.699940 48.000027 14.830400 48.000015 17.518770 48.000000 c 1034 | 30.481234 48.000000 l 1035 | h 1036 | 2.919323 40.777721 m 1037 | 1.846154 38.671570 1.846154 35.914337 1.846154 30.400000 c 1038 | 1.846154 17.599998 l 1039 | 1.846154 12.085659 1.846154 9.328430 2.919323 7.222275 c 1040 | 3.863305 5.369598 5.369576 3.863319 7.222277 2.919319 c 1041 | 9.328431 1.846149 12.085662 1.846153 17.600000 1.846153 c 1042 | 30.400002 1.846153 l 1043 | 35.914341 1.846153 38.671570 1.846149 40.777725 2.919319 c 1044 | 42.630402 3.863319 44.136681 5.369598 45.080681 7.222275 c 1045 | 46.153851 9.328430 46.153847 12.085659 46.153847 17.599998 c 1046 | 46.153847 30.400000 l 1047 | 46.153847 35.914337 46.153851 38.671570 45.080681 40.777721 c 1048 | 44.136681 42.630424 42.630402 44.136696 40.777725 45.080677 c 1049 | 38.671570 46.153847 35.914341 46.153847 30.400002 46.153847 c 1050 | 17.600000 46.153847 l 1051 | 12.085662 46.153847 9.328431 46.153847 7.222277 45.080677 c 1052 | 5.369576 44.136696 3.863305 42.630424 2.919323 40.777721 c 1053 | h 1054 | f* 1055 | n 1056 | Q 1057 | q 1058 | /E1 gs 1059 | 1.000000 0.000000 -0.000000 1.000000 54.000000 598.000000 cm 1060 | /Pattern cs 1061 | /P3 scn 1062 | 30.481234 48.000000 m 1063 | 33.169601 48.000015 35.300064 48.000027 37.017292 47.859722 c 1064 | 38.773293 47.716248 40.259140 47.416904 41.615879 46.725613 c 1065 | 43.815941 45.604633 45.604618 43.815937 46.725601 41.615875 c 1066 | 47.416924 40.259136 47.716251 38.773293 47.859695 37.017292 c 1067 | 48.000004 35.300060 48.000000 33.169601 48.000000 30.481293 c 1068 | 48.000000 17.518705 l 1069 | 48.000000 14.830399 48.000004 12.699936 47.859695 10.982708 c 1070 | 47.716251 9.226707 47.416924 7.740860 46.725601 6.384121 c 1071 | 45.604618 4.184059 43.815941 2.395382 41.615879 1.274399 c 1072 | 40.259140 0.583076 38.773293 0.283749 37.017292 0.140305 c 1073 | 35.300064 -0.000004 33.169601 0.000000 30.481295 0.000000 c 1074 | 17.518707 0.000000 l 1075 | 14.830400 0.000000 12.699940 -0.000004 10.982709 0.140305 c 1076 | 9.226708 0.283749 7.740862 0.583076 6.384123 1.274399 c 1077 | 4.184062 2.395382 2.395366 4.184059 1.274388 6.384121 c 1078 | 0.583094 7.740860 0.283751 9.226707 0.140278 10.982708 c 1079 | -0.000025 12.699936 -0.000014 14.830399 0.000000 17.518766 c 1080 | 0.000000 30.481230 l 1081 | -0.000014 33.169601 -0.000025 35.300060 0.140278 37.017292 c 1082 | 0.283751 38.773293 0.583094 40.259136 1.274388 41.615875 c 1083 | 2.395366 43.815937 4.184062 45.604633 6.384123 46.725613 c 1084 | 7.740862 47.416908 9.226708 47.716248 10.982709 47.859722 c 1085 | 12.699940 48.000027 14.830400 48.000015 17.518770 48.000000 c 1086 | 30.481234 48.000000 l 1087 | h 1088 | 2.919323 40.777721 m 1089 | 1.846154 38.671570 1.846154 35.914337 1.846154 30.400000 c 1090 | 1.846154 17.599998 l 1091 | 1.846154 12.085659 1.846154 9.328430 2.919323 7.222275 c 1092 | 3.863305 5.369598 5.369576 3.863319 7.222277 2.919319 c 1093 | 9.328431 1.846149 12.085662 1.846153 17.600000 1.846153 c 1094 | 30.400002 1.846153 l 1095 | 35.914341 1.846153 38.671570 1.846149 40.777725 2.919319 c 1096 | 42.630402 3.863319 44.136681 5.369598 45.080681 7.222275 c 1097 | 46.153851 9.328430 46.153847 12.085659 46.153847 17.599998 c 1098 | 46.153847 30.400000 l 1099 | 46.153847 35.914337 46.153851 38.671570 45.080681 40.777721 c 1100 | 44.136681 42.630424 42.630402 44.136696 40.777725 45.080677 c 1101 | 38.671570 46.153847 35.914341 46.153847 30.400002 46.153847 c 1102 | 17.600000 46.153847 l 1103 | 12.085662 46.153847 9.328431 46.153847 7.222277 45.080677 c 1104 | 5.369576 44.136696 3.863305 42.630424 2.919323 40.777721 c 1105 | h 1106 | f* 1107 | n 1108 | Q 1109 | q 1110 | /E2 gs 1111 | 1.000000 0.000000 -0.000000 1.000000 71.476868 612.707703 cm 1112 | /Pattern cs 1113 | /P4 scn 1114 | 0.000000 -0.000017 m 1115 | 0.000000 19.076904 l 1116 | 7.247755 19.076904 l 1117 | 8.732555 19.076904 9.978277 18.822321 10.984923 18.313089 c 1118 | 11.997847 17.803858 12.762276 17.089705 13.278153 16.170628 c 1119 | 13.800368 15.257766 14.061415 14.192782 14.061415 12.975613 c 1120 | 14.061415 11.752291 13.797171 10.690382 13.268740 9.789951 c 1121 | 12.746524 8.895737 11.975815 8.203305 10.956615 7.712721 c 1122 | 9.937353 7.228352 8.685353 6.986135 7.200614 6.986135 c 1123 | 2.038462 6.986135 l 1124 | 2.038462 9.855121 l 1125 | 6.728738 9.855121 l 1126 | 7.596922 9.855121 8.307877 9.973151 8.861539 10.209089 c 1127 | 9.415200 10.451304 9.824122 10.802135 10.088368 11.261703 c 1128 | 10.358891 11.727427 10.494154 12.298752 10.494154 12.975613 c 1129 | 10.494154 13.652536 10.358891 14.230074 10.088368 14.708227 c 1130 | 9.817845 15.192596 9.405723 15.558997 8.852122 15.807366 c 1131 | 8.298461 16.061951 7.584368 16.189274 6.709845 16.189274 c 1132 | 3.501231 16.189274 l 1133 | 3.501231 -0.000017 l 1134 | 0.000000 -0.000017 l 1135 | h 1136 | 9.984555 8.644228 m 1137 | 14.769230 -0.000017 l 1138 | 10.862215 -0.000017 l 1139 | 6.162524 8.644228 l 1140 | 9.984555 8.644228 l 1141 | h 1142 | f 1143 | n 1144 | Q 1145 | q 1146 | 1.000000 0.000000 -0.000000 1.000000 54.000000 543.594482 cm 1147 | 1.000000 1.000000 1.000000 scn 1148 | 0.000000 -0.594482 m 1149 | h 1150 | 1.291193 -0.594482 m 1151 | 1.291193 12.496428 l 1152 | 6.200284 12.496428 l 1153 | 7.205966 12.496428 8.049716 12.321711 8.731535 11.972280 c 1154 | 9.417615 11.622848 9.935370 11.132791 10.284801 10.502110 c 1155 | 10.638495 9.875689 10.815342 9.144864 10.815342 8.309637 c 1156 | 10.815342 7.470149 10.636365 6.741455 10.278410 6.123557 c 1157 | 9.924716 5.509921 9.402699 5.034780 8.712358 4.698132 c 1158 | 8.022017 4.365746 7.174006 4.199553 6.168324 4.199553 c 1159 | 2.671875 4.199553 l 1160 | 2.671875 6.168303 l 1161 | 5.848722 6.168303 l 1162 | 6.436790 6.168303 6.918324 6.249269 7.293324 6.411200 c 1163 | 7.668324 6.577394 7.945313 6.818160 8.124290 7.133501 c 1164 | 8.307528 7.453103 8.399148 7.845149 8.399148 8.309637 c 1165 | 8.399148 8.774125 8.307528 9.170432 8.124290 9.498558 c 1166 | 7.941051 9.830944 7.661932 10.082365 7.286932 10.252819 c 1167 | 6.911932 10.427534 6.428267 10.514893 5.835938 10.514893 c 1168 | 3.662642 10.514893 l 1169 | 3.662642 -0.594482 l 1170 | 1.291193 -0.594482 l 1171 | h 1172 | 8.053977 5.337336 m 1173 | 11.294744 -0.594482 l 1174 | 8.648438 -0.594482 l 1175 | 5.465199 5.337336 l 1176 | 8.053977 5.337336 l 1177 | h 1178 | 11.566406 -0.594482 m 1179 | h 1180 | 17.101917 -0.786243 m 1181 | 16.117542 -0.786243 15.267401 -0.581697 14.551492 -0.172607 c 1182 | 13.839844 0.240744 13.292259 0.824551 12.908736 1.578814 c 1183 | 12.525213 2.337337 12.333451 3.230092 12.333451 4.257080 c 1184 | 12.333451 5.267023 12.525213 6.153387 12.908736 6.916171 c 1185 | 13.296520 7.683217 13.837713 8.279808 14.532315 8.705944 c 1186 | 15.226917 9.136342 16.042969 9.351541 16.980469 9.351541 c 1187 | 17.585583 9.351541 18.156605 9.253530 18.693537 9.057507 c 1188 | 19.234730 8.865746 19.712004 8.567450 20.125355 8.162620 c 1189 | 20.542969 7.757791 20.871094 7.242166 21.109730 6.615745 c 1190 | 21.348368 5.993586 21.467686 5.252109 21.467686 4.391314 c 1191 | 21.467686 3.681796 l 1192 | 13.420099 3.681796 l 1193 | 13.420099 5.241455 l 1194 | 19.249645 5.241455 l 1195 | 19.245384 5.684637 19.149504 6.078814 18.962004 6.423984 c 1196 | 18.774504 6.773416 18.512430 7.048273 18.175781 7.248557 c 1197 | 17.843395 7.448841 17.455610 7.548984 17.012428 7.548984 c 1198 | 16.539417 7.548984 16.123936 7.433927 15.765981 7.203814 c 1199 | 15.408026 6.977962 15.128906 6.679666 14.928622 6.308928 c 1200 | 14.732599 5.942450 14.632458 5.539750 14.628196 5.100830 c 1201 | 14.628196 3.739325 l 1202 | 14.628196 3.168302 14.732599 2.678245 14.941406 2.269154 c 1203 | 15.150213 1.864325 15.442117 1.553245 15.817117 1.335915 c 1204 | 16.192116 1.122847 16.631037 1.016314 17.133879 1.016314 c 1205 | 17.470526 1.016314 17.775213 1.063189 18.047941 1.156939 c 1206 | 18.320667 1.254950 18.557173 1.397705 18.757458 1.585205 c 1207 | 18.957743 1.772705 19.109020 2.004950 19.211292 2.281939 c 1208 | 21.371803 2.039041 l 1209 | 21.235441 1.468018 20.975498 0.969439 20.591976 0.543303 c 1210 | 20.212713 0.121428 19.726917 -0.206697 19.134588 -0.441072 c 1211 | 18.542259 -0.671186 17.864702 -0.786243 17.101917 -0.786243 c 1212 | h 1213 | 22.236328 -0.594482 m 1214 | h 1215 | 27.707920 -0.786243 m 1216 | 26.727806 -0.786243 25.886187 -0.571045 25.183062 -0.140648 c 1217 | 24.484198 0.289751 23.945135 0.884211 23.565874 1.642734 c 1218 | 23.190874 2.405518 23.003374 3.283359 23.003374 4.276257 c 1219 | 23.003374 5.273416 23.195135 6.153387 23.578657 6.916171 c 1220 | 23.962181 7.683217 24.503374 8.279808 25.202238 8.705944 c 1221 | 25.905363 9.136342 26.736328 9.351541 27.695135 9.351541 c 1222 | 28.492010 9.351541 29.197266 9.204524 29.810902 8.910490 c 1223 | 30.428801 8.620717 30.920988 8.209495 31.287464 7.676825 c 1224 | 31.653942 7.148416 31.862749 6.530518 31.913885 5.823132 c 1225 | 29.702238 5.823132 l 1226 | 29.612749 6.296143 29.399681 6.690319 29.063032 7.005660 c 1227 | 28.730646 7.325262 28.285334 7.485064 27.727095 7.485064 c 1228 | 27.254084 7.485064 26.838600 7.357223 26.480646 7.101541 c 1229 | 26.122692 6.850121 25.843573 6.487905 25.643288 6.014893 c 1230 | 25.447266 5.541882 25.349255 4.975121 25.349255 4.314609 c 1231 | 25.349255 3.645575 25.447266 3.070291 25.643288 2.588757 c 1232 | 25.839312 2.111484 26.114170 1.742876 26.467863 1.482932 c 1233 | 26.825817 1.227249 27.245562 1.099409 27.727095 1.099409 c 1234 | 28.068005 1.099409 28.372692 1.163330 28.641157 1.291170 c 1235 | 28.913885 1.423273 29.141869 1.612905 29.325108 1.860064 c 1236 | 29.508345 2.107223 29.634056 2.407648 29.702238 2.761342 c 1237 | 31.913885 2.761342 l 1238 | 31.858486 2.066740 31.653942 1.450973 31.300249 0.914040 c 1239 | 30.946556 0.381369 30.465021 -0.036243 29.855646 -0.338800 c 1240 | 29.246271 -0.637096 28.530363 -0.786243 27.707920 -0.786243 c 1241 | h 1242 | 32.625000 -0.594482 m 1243 | h 1244 | 38.160511 -0.786243 m 1245 | 37.176136 -0.786243 36.325993 -0.581697 35.610085 -0.172607 c 1246 | 34.898438 0.240744 34.350853 0.824551 33.967331 1.578814 c 1247 | 33.583805 2.337337 33.392044 3.230092 33.392044 4.257080 c 1248 | 33.392044 5.267023 33.583805 6.153387 33.967331 6.916171 c 1249 | 34.355114 7.683217 34.896309 8.279808 35.590908 8.705944 c 1250 | 36.285511 9.136342 37.101562 9.351541 38.039062 9.351541 c 1251 | 38.644176 9.351541 39.215199 9.253530 39.752132 9.057507 c 1252 | 40.293324 8.865746 40.770596 8.567450 41.183949 8.162620 c 1253 | 41.601562 7.757791 41.929688 7.242166 42.168324 6.615745 c 1254 | 42.406960 5.993586 42.526279 5.252109 42.526279 4.391314 c 1255 | 42.526279 3.681796 l 1256 | 34.478695 3.681796 l 1257 | 34.478695 5.241455 l 1258 | 40.308239 5.241455 l 1259 | 40.303978 5.684637 40.208096 6.078814 40.020596 6.423984 c 1260 | 39.833096 6.773416 39.571022 7.048273 39.234375 7.248557 c 1261 | 38.901989 7.448841 38.514202 7.548984 38.071022 7.548984 c 1262 | 37.598011 7.548984 37.182529 7.433927 36.824574 7.203814 c 1263 | 36.466621 6.977962 36.187500 6.679666 35.987217 6.308928 c 1264 | 35.791191 5.942450 35.691051 5.539750 35.686790 5.100830 c 1265 | 35.686790 3.739325 l 1266 | 35.686790 3.168302 35.791195 2.678245 36.000000 2.269154 c 1267 | 36.208805 1.864325 36.500710 1.553245 36.875710 1.335915 c 1268 | 37.250710 1.122847 37.689629 1.016314 38.192471 1.016314 c 1269 | 38.529121 1.016314 38.833809 1.063189 39.106533 1.156939 c 1270 | 39.379261 1.254950 39.615768 1.397705 39.816051 1.585205 c 1271 | 40.016335 1.772705 40.167614 2.004950 40.269886 2.281939 c 1272 | 42.430397 2.039041 l 1273 | 42.294033 1.468018 42.034092 0.969439 41.650570 0.543303 c 1274 | 41.271309 0.121428 40.785511 -0.206697 40.193184 -0.441072 c 1275 | 39.600853 -0.671186 38.923294 -0.786243 38.160511 -0.786243 c 1276 | h 1277 | 43.294922 -0.594482 m 1278 | h 1279 | 44.483841 -0.594482 m 1280 | 44.483841 9.223700 l 1281 | 46.797764 9.223700 l 1282 | 46.797764 -0.594482 l 1283 | 44.483841 -0.594482 l 1284 | h 1285 | 45.647194 10.617165 m 1286 | 45.280716 10.617165 44.965374 10.738614 44.701172 10.981512 c 1287 | 44.436970 11.228672 44.304867 11.524837 44.304867 11.870007 c 1288 | 44.304867 12.219439 44.436970 12.515604 44.701172 12.758501 c 1289 | 44.965374 13.005660 45.280716 13.129240 45.647194 13.129240 c 1290 | 46.017933 13.129240 46.333275 13.005660 46.593216 12.758501 c 1291 | 46.857422 12.515604 46.989525 12.219439 46.989525 11.870007 c 1292 | 46.989525 11.524837 46.857422 11.228672 46.593216 10.981512 c 1293 | 46.333275 10.738614 46.017933 10.617165 45.647194 10.617165 c 1294 | h 1295 | 47.988281 -0.594482 m 1296 | h 1297 | 49.177200 -4.276300 m 1298 | 49.177200 9.223700 l 1299 | 51.452770 9.223700 l 1300 | 51.452770 7.600120 l 1301 | 51.587002 7.600120 l 1302 | 51.706322 7.838757 51.874645 8.092307 52.091976 8.360773 c 1303 | 52.309303 8.633501 52.603336 8.865746 52.974075 9.057507 c 1304 | 53.344814 9.253530 53.817829 9.351541 54.393112 9.351541 c 1305 | 55.151634 9.351541 55.835583 9.157649 56.444958 8.769865 c 1306 | 57.058594 8.386342 57.544392 7.817450 57.902344 7.063189 c 1307 | 58.264561 6.313189 58.445667 5.392734 58.445667 4.301825 c 1308 | 58.445667 3.223700 58.268822 2.307507 57.915127 1.553245 c 1309 | 57.561436 0.798983 57.079903 0.223700 56.470528 -0.172607 c 1310 | 55.861153 -0.568914 55.170811 -0.767067 54.399502 -0.767067 c 1311 | 53.837002 -0.767067 53.370384 -0.673317 52.999645 -0.485817 c 1312 | 52.628906 -0.298317 52.330612 -0.072464 52.104759 0.191740 c 1313 | 51.883167 0.460205 51.710583 0.713757 51.587002 0.952393 c 1314 | 51.491123 0.952393 l 1315 | 51.491123 -4.276300 l 1316 | 49.177200 -4.276300 l 1317 | h 1318 | 51.446377 4.314609 m 1319 | 51.446377 3.679666 51.535866 3.123558 51.714844 2.646285 c 1320 | 51.898083 2.169012 52.160156 1.796143 52.501064 1.527677 c 1321 | 52.846237 1.263474 53.263851 1.131371 53.753906 1.131371 c 1322 | 54.265270 1.131371 54.693535 1.267735 55.038708 1.540461 c 1323 | 55.383881 1.817450 55.643822 2.194580 55.818539 2.671853 c 1324 | 55.997513 3.153387 56.087002 3.700973 56.087002 4.314609 c 1325 | 56.087002 4.923984 55.999645 5.465177 55.824928 5.938189 c 1326 | 55.650211 6.411200 55.390270 6.781939 55.045101 7.050405 c 1327 | 54.699928 7.318871 54.269531 7.453103 53.753906 7.453103 c 1328 | 53.259586 7.453103 52.839844 7.323132 52.494675 7.063189 c 1329 | 52.149502 6.803245 51.887428 6.438898 51.708450 5.970148 c 1330 | 51.533733 5.501398 51.446377 4.949552 51.446377 4.314609 c 1331 | h 1332 | 59.238281 -0.594482 m 1333 | h 1334 | 65.329903 9.223700 m 1335 | 65.329903 7.433928 l 1336 | 59.685726 7.433928 l 1337 | 59.685726 9.223700 l 1338 | 65.329903 9.223700 l 1339 | h 1340 | 61.079189 11.575973 m 1341 | 63.393112 11.575973 l 1342 | 63.393112 2.358643 l 1343 | 63.393112 2.047563 63.439987 1.808927 63.533737 1.642734 c 1344 | 63.631748 1.480802 63.759586 1.370007 63.917259 1.310347 c 1345 | 64.074928 1.250687 64.249641 1.220858 64.441406 1.220858 c 1346 | 64.586296 1.220858 64.718399 1.231512 64.837715 1.252819 c 1347 | 64.961288 1.274126 65.055038 1.293301 65.118965 1.310347 c 1348 | 65.508881 -0.498602 l 1349 | 65.385300 -0.541216 65.208450 -0.588091 64.978340 -0.639227 c 1350 | 64.752487 -0.690363 64.475494 -0.720192 64.147369 -0.728716 c 1351 | 63.567825 -0.745762 63.045811 -0.658403 62.581322 -0.466642 c 1352 | 62.116833 -0.270618 61.748222 0.031939 61.475498 0.441029 c 1353 | 61.207031 0.850119 61.074928 1.361483 61.079189 1.975120 c 1354 | 61.079189 11.575973 l 1355 | h 1356 | 70.541016 -0.594482 m 1357 | h 1358 | 76.702950 9.223700 m 1359 | 76.702950 7.433928 l 1360 | 70.898972 7.433928 l 1361 | 70.898972 9.223700 l 1362 | 76.702950 9.223700 l 1363 | h 1364 | 72.349968 -0.594482 m 1365 | 72.349968 10.150547 l 1366 | 72.349968 10.811058 72.486328 11.360774 72.759056 11.799694 c 1367 | 73.036041 12.238615 73.406784 12.566740 73.871269 12.784069 c 1368 | 74.335754 13.001399 74.851379 13.110064 75.418144 13.110064 c 1369 | 75.818710 13.110064 76.174538 13.078104 76.485619 13.014183 c 1370 | 76.796700 12.950262 77.026810 12.892735 77.175957 12.841598 c 1371 | 76.715729 11.051825 l 1372 | 76.617722 11.081654 76.494141 11.111485 76.344994 11.141314 c 1373 | 76.195847 11.175405 76.029648 11.192450 75.846413 11.192450 c 1374 | 75.416016 11.192450 75.111328 11.088046 74.932350 10.879240 c 1375 | 74.757637 10.674694 74.670280 10.380660 74.670280 9.997137 c 1376 | 74.670280 -0.594482 l 1377 | 72.349968 -0.594482 l 1378 | h 1379 | 76.957031 -0.594482 m 1380 | h 1381 | 82.428619 -0.786243 m 1382 | 81.469810 -0.786243 80.638847 -0.575306 79.935722 -0.153431 c 1383 | 79.232597 0.268444 78.687141 0.858643 78.299362 1.617166 c 1384 | 77.915840 2.375689 77.724075 3.262052 77.724075 4.276257 c 1385 | 77.724075 5.290462 77.915840 6.178956 78.299362 6.941740 c 1386 | 78.687141 7.704524 79.232597 8.296853 79.935722 8.718728 c 1387 | 80.638847 9.140603 81.469810 9.351541 82.428619 9.351541 c 1388 | 83.387428 9.351541 84.218391 9.140603 84.921516 8.718728 c 1389 | 85.624649 8.296853 86.167969 7.704524 86.551491 6.941740 c 1390 | 86.939278 6.178956 87.133171 5.290462 87.133171 4.276257 c 1391 | 87.133171 3.262052 86.939278 2.375689 86.551491 1.617166 c 1392 | 86.167969 0.858643 85.624649 0.268444 84.921516 -0.153431 c 1393 | 84.218391 -0.575306 83.387428 -0.786243 82.428619 -0.786243 c 1394 | h 1395 | 82.441406 1.067450 m 1396 | 82.961296 1.067450 83.395950 1.210205 83.745384 1.495716 c 1397 | 84.094818 1.785489 84.354759 2.173274 84.525215 2.659069 c 1398 | 84.699928 3.144864 84.787285 3.686057 84.787285 4.282648 c 1399 | 84.787285 4.883501 84.699928 5.426825 84.525215 5.912620 c 1400 | 84.354759 6.402677 84.094818 6.792592 83.745384 7.082365 c 1401 | 83.395950 7.372137 82.961296 7.517023 82.441406 7.517023 c 1402 | 81.908737 7.517023 81.465553 7.372137 81.111862 7.082365 c 1403 | 80.762428 6.792592 80.500359 6.402677 80.325638 5.912620 c 1404 | 80.155182 5.426825 80.069954 4.883501 80.069954 4.282648 c 1405 | 80.069954 3.686057 80.155182 3.144864 80.325638 2.659069 c 1406 | 80.500359 2.173274 80.762428 1.785489 81.111862 1.495716 c 1407 | 81.465553 1.210205 81.908737 1.067450 82.441406 1.067450 c 1408 | h 1409 | 87.908203 -0.594482 m 1410 | h 1411 | 89.097122 -0.594482 m 1412 | 89.097122 9.223700 l 1413 | 91.340729 9.223700 l 1414 | 91.340729 7.587336 l 1415 | 91.443001 7.587336 l 1416 | 91.621979 8.154098 91.928802 8.590887 92.363457 8.897705 c 1417 | 92.802383 9.208785 93.303093 9.364325 93.865593 9.364325 c 1418 | 93.993431 9.364325 94.136185 9.357933 94.293854 9.345149 c 1419 | 94.455788 9.336626 94.590019 9.321712 94.696556 9.300405 c 1420 | 94.696556 7.171853 l 1421 | 94.598541 7.205944 94.443001 7.235774 94.229935 7.261342 c 1422 | 94.021126 7.291171 93.818710 7.306086 93.622688 7.306086 c 1423 | 93.200813 7.306086 92.821556 7.214466 92.484909 7.031228 c 1424 | 92.152519 6.852251 91.890450 6.602961 91.698685 6.283359 c 1425 | 91.506920 5.963757 91.411041 5.595148 91.411041 5.177535 c 1426 | 91.411041 -0.594482 l 1427 | 89.097122 -0.594482 l 1428 | h 1429 | 99.509766 -0.594482 m 1430 | h 1431 | 99.906075 12.496428 m 1432 | 102.584343 12.496428 l 1433 | 105.786751 6.705234 l 1434 | 105.914597 6.705234 l 1435 | 109.117012 12.496428 l 1436 | 111.795280 12.496428 l 1437 | 107.033203 4.289041 l 1438 | 107.033203 -0.594482 l 1439 | 104.668144 -0.594482 l 1440 | 104.668144 4.289041 l 1441 | 99.906075 12.496428 l 1442 | h 1443 | 110.355469 -0.594482 m 1444 | h 1445 | 115.827057 -0.786243 m 1446 | 114.868248 -0.786243 114.037285 -0.575306 113.334160 -0.153431 c 1447 | 112.631035 0.268444 112.085579 0.858643 111.697800 1.617166 c 1448 | 111.314278 2.375689 111.122513 3.262052 111.122513 4.276257 c 1449 | 111.122513 5.290462 111.314278 6.178956 111.697800 6.941740 c 1450 | 112.085579 7.704524 112.631035 8.296853 113.334160 8.718728 c 1451 | 114.037285 9.140603 114.868248 9.351541 115.827057 9.351541 c 1452 | 116.785866 9.351541 117.616829 9.140603 118.319954 8.718728 c 1453 | 119.023087 8.296853 119.566406 7.704524 119.949928 6.941740 c 1454 | 120.337715 6.178956 120.531609 5.290462 120.531609 4.276257 c 1455 | 120.531609 3.262052 120.337715 2.375689 119.949928 1.617166 c 1456 | 119.566406 0.858643 119.023087 0.268444 118.319954 -0.153431 c 1457 | 117.616829 -0.575306 116.785866 -0.786243 115.827057 -0.786243 c 1458 | h 1459 | 115.839844 1.067450 m 1460 | 116.359734 1.067450 116.794388 1.210205 117.143822 1.495716 c 1461 | 117.493256 1.785489 117.753197 2.173274 117.923653 2.659069 c 1462 | 118.098366 3.144864 118.185722 3.686057 118.185722 4.282648 c 1463 | 118.185722 4.883501 118.098366 5.426825 117.923653 5.912620 c 1464 | 117.753197 6.402677 117.493256 6.792592 117.143822 7.082365 c 1465 | 116.794388 7.372137 116.359734 7.517023 115.839844 7.517023 c 1466 | 115.307175 7.517023 114.863991 7.372137 114.510300 7.082365 c 1467 | 114.160866 6.792592 113.898796 6.402677 113.724075 5.912620 c 1468 | 113.553619 5.426825 113.468391 4.883501 113.468391 4.282648 c 1469 | 113.468391 3.686057 113.553619 3.144864 113.724075 2.659069 c 1470 | 113.898796 2.173274 114.160866 1.785489 114.510300 1.495716 c 1471 | 114.863991 1.210205 115.307175 1.067450 115.839844 1.067450 c 1472 | h 1473 | 121.306641 -0.594482 m 1474 | h 1475 | 128.753372 3.534780 m 1476 | 128.753372 9.223700 l 1477 | 131.067291 9.223700 l 1478 | 131.067291 -0.594482 l 1479 | 128.823685 -0.594482 l 1480 | 128.823685 1.150545 l 1481 | 128.721420 1.150545 l 1482 | 128.499817 0.600830 128.135468 0.151257 127.628372 -0.198175 c 1483 | 127.125534 -0.547607 126.505508 -0.722322 125.768288 -0.722322 c 1484 | 125.124825 -0.722322 124.555931 -0.579567 124.061615 -0.294056 c 1485 | 123.571556 -0.004284 123.188034 0.415461 122.911041 0.965178 c 1486 | 122.634056 1.519155 122.495560 2.188189 122.495560 2.972280 c 1487 | 122.495560 9.223700 l 1488 | 124.809479 9.223700 l 1489 | 124.809479 3.330234 l 1490 | 124.809479 2.708075 124.979935 2.213756 125.320847 1.847279 c 1491 | 125.661751 1.480802 126.109200 1.297564 126.663177 1.297564 c 1492 | 127.004082 1.297564 127.334335 1.380661 127.653938 1.546853 c 1493 | 127.973541 1.713046 128.235626 1.960205 128.440170 2.288330 c 1494 | 128.648972 2.620717 128.753372 3.036200 128.753372 3.534780 c 1495 | h 1496 | 132.257812 -0.594482 m 1497 | h 1498 | 133.446732 -0.594482 m 1499 | 133.446732 9.223700 l 1500 | 135.690338 9.223700 l 1501 | 135.690338 7.587336 l 1502 | 135.792618 7.587336 l 1503 | 135.971588 8.154098 136.278412 8.590887 136.713074 8.897705 c 1504 | 137.151993 9.208785 137.652695 9.364325 138.215195 9.364325 c 1505 | 138.343048 9.364325 138.485794 9.357933 138.643463 9.345149 c 1506 | 138.805405 9.336626 138.939636 9.321712 139.046158 9.300405 c 1507 | 139.046158 7.171853 l 1508 | 138.948151 7.205944 138.792618 7.235774 138.579544 7.261342 c 1509 | 138.370743 7.291171 138.168335 7.306086 137.972305 7.306086 c 1510 | 137.550430 7.306086 137.171173 7.214466 136.834518 7.031228 c 1511 | 136.502136 6.852251 136.240051 6.602961 136.048294 6.283359 c 1512 | 135.856537 5.963757 135.760651 5.595148 135.760651 5.177535 c 1513 | 135.760651 -0.594482 l 1514 | 133.446732 -0.594482 l 1515 | h 1516 | 143.859375 -0.594482 m 1517 | h 1518 | 145.150574 -0.594482 m 1519 | 145.150574 12.496428 l 1520 | 150.059662 12.496428 l 1521 | 151.065338 12.496428 151.909088 12.308928 152.590912 11.933928 c 1522 | 153.276993 11.558928 153.794739 11.043303 154.144180 10.387053 c 1523 | 154.497864 9.735064 154.674713 8.993586 154.674713 8.162620 c 1524 | 154.674713 7.323132 154.497864 6.577393 154.144180 5.925404 c 1525 | 153.790482 5.273416 153.268463 4.759921 152.578125 4.384921 c 1526 | 151.887787 4.014183 151.037643 3.828814 150.027695 3.828814 c 1527 | 146.774155 3.828814 l 1528 | 146.774155 5.778387 l 1529 | 149.708099 5.778387 l 1530 | 150.296158 5.778387 150.777695 5.880660 151.152695 6.085205 c 1531 | 151.527695 6.289750 151.804688 6.571000 151.983658 6.928955 c 1532 | 152.166901 7.286910 152.258530 7.698132 152.258530 8.162620 c 1533 | 152.258530 8.627109 152.166901 9.036200 151.983658 9.389893 c 1534 | 151.804688 9.743586 151.525558 10.018444 151.146301 10.214467 c 1535 | 150.771301 10.414751 150.287643 10.514893 149.695312 10.514893 c 1536 | 147.522018 10.514893 l 1537 | 147.522018 -0.594482 l 1538 | 145.150574 -0.594482 l 1539 | h 1540 | 155.267578 -0.594482 m 1541 | h 1542 | 159.281784 -0.792635 m 1543 | 158.659622 -0.792635 158.099258 -0.681839 157.600677 -0.460249 c 1544 | 157.106354 -0.234398 156.714310 0.097988 156.424545 0.536909 c 1545 | 156.139038 0.975830 155.996277 1.517023 155.996277 2.160489 c 1546 | 155.996277 2.714466 156.098541 3.172564 156.303085 3.534780 c 1547 | 156.507629 3.896996 156.786758 4.186768 157.140442 4.404098 c 1548 | 157.494141 4.621427 157.892578 4.785489 158.335754 4.896285 c 1549 | 158.783203 5.011341 159.245560 5.094438 159.722839 5.145575 c 1550 | 160.298111 5.205234 160.764740 5.258501 161.122696 5.305376 c 1551 | 161.480652 5.356512 161.740585 5.433217 161.902527 5.535490 c 1552 | 162.068710 5.642024 162.151810 5.806087 162.151810 6.027678 c 1553 | 162.151810 6.066029 l 1554 | 162.151810 6.547563 162.009048 6.920432 161.723541 7.184637 c 1555 | 161.438034 7.448842 161.026810 7.580944 160.489883 7.580944 c 1556 | 159.923126 7.580944 159.473541 7.457364 159.141159 7.210205 c 1557 | 158.813034 6.963046 158.591446 6.671143 158.476379 6.334495 c 1558 | 156.315872 6.641314 l 1559 | 156.486328 7.237905 156.767578 7.736484 157.159622 8.137053 c 1560 | 157.551666 8.541883 158.031082 8.844439 158.597839 9.044723 c 1561 | 159.164597 9.249268 159.791016 9.351541 160.477097 9.351541 c 1562 | 160.950104 9.351541 161.420990 9.296143 161.889740 9.185348 c 1563 | 162.358490 9.074553 162.786758 8.891314 163.174545 8.635632 c 1564 | 163.562317 8.384212 163.873398 8.041171 164.107773 7.606512 c 1565 | 164.346405 7.171853 164.465729 6.628529 164.465729 5.976541 c 1566 | 164.465729 -0.594482 l 1567 | 162.241302 -0.594482 l 1568 | 162.241302 0.754240 l 1569 | 162.164597 0.754240 l 1570 | 162.023972 0.481512 161.825821 0.225830 161.570129 -0.012806 c 1571 | 161.318710 -0.247181 161.001251 -0.436811 160.617722 -0.581697 c 1572 | 160.238464 -0.722322 159.793152 -0.792635 159.281784 -0.792635 c 1573 | h 1574 | 159.882629 0.907648 m 1575 | 160.347122 0.907648 160.749817 0.999268 161.090729 1.182507 c 1576 | 161.431641 1.370007 161.693710 1.617166 161.876953 1.923984 c 1577 | 162.064453 2.230802 162.158203 2.565319 162.158203 2.927535 c 1578 | 162.158203 4.084495 l 1579 | 162.085770 4.024836 161.962189 3.969439 161.787460 3.918303 c 1580 | 161.617004 3.867166 161.425247 3.822421 161.212173 3.784069 c 1581 | 160.999115 3.745717 160.788177 3.711626 160.579361 3.681796 c 1582 | 160.370560 3.651966 160.189453 3.626398 160.036041 3.605092 c 1583 | 159.690872 3.558217 159.381927 3.481512 159.109192 3.374978 c 1584 | 158.836472 3.268444 158.621277 3.119296 158.463608 2.927535 c 1585 | 158.305939 2.740035 158.227097 2.497137 158.227097 2.198841 c 1586 | 158.227097 1.772705 158.382629 1.450973 158.693710 1.233643 c 1587 | 159.004791 1.016314 159.401108 0.907648 159.882629 0.907648 c 1588 | h 1589 | 165.251953 -0.594482 m 1590 | h 1591 | 167.879089 -4.276300 m 1592 | 167.563736 -4.276300 167.271835 -4.250732 167.003372 -4.199596 c 1593 | 166.739166 -4.152721 166.528229 -4.097324 166.370560 -4.033403 c 1594 | 166.907486 -2.230846 l 1595 | 167.244141 -2.328857 167.544571 -2.375732 167.808777 -2.371471 c 1596 | 168.072983 -2.367210 168.305222 -2.284113 168.505508 -2.122181 c 1597 | 168.710052 -1.964510 168.882629 -1.700306 169.023254 -1.329567 c 1598 | 169.221420 -0.799028 l 1599 | 165.661041 9.223700 l 1600 | 168.115585 9.223700 l 1601 | 170.378372 1.808927 l 1602 | 170.480652 1.808927 l 1603 | 172.749817 9.223700 l 1604 | 175.210754 9.223700 l 1605 | 171.279648 -1.783403 l 1606 | 171.096405 -2.303289 170.853516 -2.748602 170.550964 -3.119341 c 1607 | 170.248398 -3.494341 169.877655 -3.779852 169.438736 -3.975874 c 1608 | 169.004089 -4.176159 168.484207 -4.276300 167.879089 -4.276300 c 1609 | h 1610 | 175.623047 -0.594482 m 1611 | h 1612 | 176.811966 -0.594482 m 1613 | 176.811966 9.223700 l 1614 | 179.023621 9.223700 l 1615 | 179.023621 7.555376 l 1616 | 179.138672 7.555376 l 1617 | 179.343216 8.117876 179.682007 8.556796 180.155014 8.872137 c 1618 | 180.628021 9.191739 181.192642 9.351541 181.848892 9.351541 c 1619 | 182.513672 9.351541 183.074036 9.189609 183.530014 8.865745 c 1620 | 183.990234 8.546143 184.314102 8.109353 184.501602 7.555376 c 1621 | 184.603867 7.555376 l 1622 | 184.821198 8.100830 185.187683 8.535489 185.703308 8.859353 c 1623 | 186.223190 9.187478 186.838959 9.351541 187.550598 9.351541 c 1624 | 188.454010 9.351541 189.191223 9.066030 189.762253 8.495007 c 1625 | 190.333282 7.923984 190.618790 7.090887 190.618790 5.995716 c 1626 | 190.618790 -0.594482 l 1627 | 188.298477 -0.594482 l 1628 | 188.298477 5.637762 l 1629 | 188.298477 6.247137 188.136551 6.692450 187.812683 6.973700 c 1630 | 187.488815 7.259212 187.092514 7.401967 186.623764 7.401967 c 1631 | 186.065521 7.401967 185.628723 7.227251 185.313385 6.877819 c 1632 | 185.002304 6.532649 184.846771 6.083075 184.846771 5.529098 c 1633 | 184.846771 -0.594482 l 1634 | 182.577591 -0.594482 l 1635 | 182.577591 5.733643 l 1636 | 182.577591 6.240745 182.424179 6.645575 182.117371 6.948132 c 1637 | 181.814804 7.250689 181.418503 7.401967 180.928452 7.401967 c 1638 | 180.596069 7.401967 180.293503 7.316740 180.020767 7.146285 c 1639 | 179.748047 6.980091 179.530716 6.743586 179.368790 6.436768 c 1640 | 179.206848 6.134211 179.125885 5.780518 179.125885 5.375689 c 1641 | 179.125885 -0.594482 l 1642 | 176.811966 -0.594482 l 1643 | h 1644 | 191.777344 -0.594482 m 1645 | h 1646 | 197.312851 -0.786243 m 1647 | 196.328476 -0.786243 195.478333 -0.581697 194.762436 -0.172607 c 1648 | 194.050781 0.240744 193.503204 0.824551 193.119675 1.578814 c 1649 | 192.736145 2.337337 192.544388 3.230092 192.544388 4.257080 c 1650 | 192.544388 5.267023 192.736145 6.153387 193.119675 6.916171 c 1651 | 193.507462 7.683217 194.048660 8.279808 194.743256 8.705944 c 1652 | 195.437851 9.136342 196.253906 9.351541 197.191406 9.351541 c 1653 | 197.796524 9.351541 198.367538 9.253530 198.904480 9.057507 c 1654 | 199.445663 8.865746 199.922943 8.567450 200.336288 8.162620 c 1655 | 200.753906 7.757791 201.082031 7.242166 201.320663 6.615745 c 1656 | 201.559296 5.993586 201.678619 5.252109 201.678619 4.391314 c 1657 | 201.678619 3.681796 l 1658 | 193.631042 3.681796 l 1659 | 193.631042 5.241455 l 1660 | 199.460587 5.241455 l 1661 | 199.456329 5.684637 199.360443 6.078814 199.172943 6.423984 c 1662 | 198.985443 6.773416 198.723373 7.048273 198.386719 7.248557 c 1663 | 198.054337 7.448841 197.666550 7.548984 197.223373 7.548984 c 1664 | 196.750366 7.548984 196.334869 7.433927 195.976913 7.203814 c 1665 | 195.618958 6.977962 195.339844 6.679666 195.139557 6.308928 c 1666 | 194.943527 5.942450 194.843384 5.539750 194.839127 5.100830 c 1667 | 194.839127 3.739325 l 1668 | 194.839127 3.168302 194.943527 2.678245 195.152344 2.269154 c 1669 | 195.361160 1.864325 195.653061 1.553245 196.028061 1.335915 c 1670 | 196.403061 1.122847 196.841980 1.016314 197.344818 1.016314 c 1671 | 197.681473 1.016314 197.986160 1.063189 198.258881 1.156939 c 1672 | 198.531601 1.254950 198.768112 1.397705 198.968399 1.585205 c 1673 | 199.168671 1.772705 199.319946 2.004950 199.422226 2.281939 c 1674 | 201.582748 2.039041 l 1675 | 201.446381 1.468018 201.186432 0.969439 200.802917 0.543303 c 1676 | 200.423660 0.121428 199.937851 -0.206697 199.345520 -0.441072 c 1677 | 198.753189 -0.671186 198.075638 -0.786243 197.312851 -0.786243 c 1678 | h 1679 | 202.447266 -0.594482 m 1680 | h 1681 | 205.950104 5.158359 m 1682 | 205.950104 -0.594482 l 1683 | 203.636185 -0.594482 l 1684 | 203.636185 9.223700 l 1685 | 205.847839 9.223700 l 1686 | 205.847839 7.555376 l 1687 | 205.962891 7.555376 l 1688 | 206.188736 8.105092 206.548828 8.541882 207.043152 8.865745 c 1689 | 207.541733 9.189609 208.157486 9.351541 208.890442 9.351541 c 1690 | 209.568008 9.351541 210.158203 9.206655 210.661041 8.916882 c 1691 | 211.168137 8.627109 211.560181 8.207364 211.837173 7.657648 c 1692 | 212.118423 7.107932 212.256927 6.441030 212.252670 5.656939 c 1693 | 212.252670 -0.594482 l 1694 | 209.938736 -0.594482 l 1695 | 209.938736 5.298984 l 1696 | 209.938736 5.955234 209.768280 6.468728 209.427383 6.839467 c 1697 | 209.090729 7.210205 208.624115 7.395575 208.027527 7.395575 c 1698 | 207.622696 7.395575 207.262604 7.306086 206.947266 7.127109 c 1699 | 206.636185 6.952393 206.391159 6.698842 206.212173 6.366455 c 1700 | 206.037460 6.034069 205.950104 5.631371 205.950104 5.158359 c 1701 | h 1702 | 213.416016 -0.594482 m 1703 | h 1704 | 219.507629 9.223700 m 1705 | 219.507629 7.433928 l 1706 | 213.863464 7.433928 l 1707 | 213.863464 9.223700 l 1708 | 219.507629 9.223700 l 1709 | h 1710 | 215.256927 11.575973 m 1711 | 217.570847 11.575973 l 1712 | 217.570847 2.358643 l 1713 | 217.570847 2.047563 217.617722 1.808927 217.711472 1.642734 c 1714 | 217.809479 1.480802 217.937317 1.370007 218.094986 1.310347 c 1715 | 218.252655 1.250687 218.427383 1.220858 218.619141 1.220858 c 1716 | 218.764023 1.220858 218.896133 1.231512 219.015442 1.252819 c 1717 | 219.139023 1.274126 219.232773 1.293301 219.296692 1.310347 c 1718 | 219.686615 -0.498602 l 1719 | 219.563034 -0.541216 219.386185 -0.588091 219.156067 -0.639227 c 1720 | 218.930222 -0.690363 218.653229 -0.720192 218.325104 -0.728716 c 1721 | 217.745560 -0.745762 217.223541 -0.658403 216.759048 -0.466642 c 1722 | 216.294556 -0.270618 215.925949 0.031939 215.653229 0.441029 c 1723 | 215.384766 0.850119 215.252670 1.361483 215.256927 1.975120 c 1724 | 215.256927 11.575973 l 1725 | h 1726 | f 1727 | n 1728 | Q 1729 | q 1730 | 1.000000 0.000000 -0.000000 1.000000 54.000000 543.594482 cm 1731 | BT 1732 | 18.000000 0.000000 0.000000 18.000000 0.000000 -0.594482 Tm 1733 | /F1 1.000000 Tf 1734 | [ (\020) 7.990042 (\016) (\017) (\016) (\014) (\015) (\010) (\t) (\013) 20.685408 (\007) (\n) (\t) (\006) 102.006276 (\007) (\005) (\n) (\t) (\004) 9.676615 (\003) 19.886441 (\002) (\001) (\016) (\000) (\010) ] TJ 1735 | ET 1736 | Q 1737 | q 1738 | 1.000000 0.000000 -0.000000 1.000000 54.000000 513.477234 cm 1739 | 0.683333 0.683333 0.683333 scn 1740 | 0.000000 -0.477234 m 1741 | h 1742 | 0.676136 8.610834 m 1743 | 0.676136 9.704584 l 1744 | 8.312500 9.704584 l 1745 | 8.312500 8.610834 l 1746 | 5.110796 8.610834 l 1747 | 5.110796 -0.477234 l 1748 | 3.877841 -0.477234 l 1749 | 3.877841 8.610834 l 1750 | 0.676136 8.610834 l 1751 | h 1752 | 8.982422 -0.477234 m 1753 | h 1754 | 11.229581 4.116516 m 1755 | 11.229581 -0.477234 l 1756 | 10.056286 -0.477234 l 1757 | 10.056286 9.704584 l 1758 | 11.229581 9.704584 l 1759 | 11.229581 5.965948 l 1760 | 11.329013 5.965948 l 1761 | 11.507990 6.360361 11.776456 6.673571 12.134411 6.905579 c 1762 | 12.495680 7.140901 12.976267 7.258562 13.576172 7.258562 c 1763 | 14.096532 7.258562 14.552260 7.154158 14.943359 6.945352 c 1764 | 15.334458 6.739859 15.637725 6.423334 15.853161 5.995778 c 1765 | 16.071911 5.571535 16.181286 5.031289 16.181286 4.375039 c 1766 | 16.181286 -0.477234 l 1767 | 15.007990 -0.477234 l 1768 | 15.007990 4.295493 l 1769 | 15.007990 4.902027 14.850556 5.371015 14.535689 5.702454 c 1770 | 14.224136 6.037208 13.791608 6.204585 13.238104 6.204585 c 1771 | 12.853634 6.204585 12.508937 6.123382 12.204013 5.960977 c 1772 | 11.902403 5.798571 11.663767 5.561592 11.488104 5.250039 c 1773 | 11.315755 4.938486 11.229581 4.560645 11.229581 4.116516 c 1774 | h 1775 | 17.253906 -0.477234 m 1776 | h 1777 | 20.574928 -0.656211 m 1778 | 20.091028 -0.656211 19.651871 -0.565065 19.257458 -0.382773 c 1779 | 18.863045 -0.197167 18.549835 0.069641 18.317827 0.417652 c 1780 | 18.085819 0.768978 17.969816 1.193221 17.969816 1.690380 c 1781 | 17.969816 2.127880 18.055990 2.482520 18.228338 2.754300 c 1782 | 18.400686 3.029395 18.631037 3.244831 18.919390 3.400607 c 1783 | 19.207741 3.556383 19.525923 3.672387 19.873934 3.748618 c 1784 | 20.225260 3.828163 20.578243 3.891137 20.932884 3.937539 c 1785 | 21.396898 3.997198 21.773083 4.041943 22.061436 4.071772 c 1786 | 22.353102 4.104916 22.565224 4.159603 22.697798 4.235834 c 1787 | 22.833689 4.312065 22.901634 4.444641 22.901634 4.633562 c 1788 | 22.901634 4.673334 l 1789 | 22.901634 5.163865 22.767401 5.545020 22.498936 5.816800 c 1790 | 22.233784 6.088581 21.831085 6.224471 21.290838 6.224471 c 1791 | 20.730705 6.224471 20.291548 6.101838 19.973366 5.856573 c 1792 | 19.655184 5.611308 19.431463 5.349471 19.302202 5.071062 c 1793 | 18.188566 5.468789 l 1794 | 18.387428 5.932805 18.652580 6.294073 18.984020 6.552596 c 1795 | 19.318773 6.814433 19.683357 6.996725 20.077770 7.099471 c 1796 | 20.475496 7.205531 20.866596 7.258562 21.251066 7.258562 c 1797 | 21.496330 7.258562 21.778053 7.228732 22.096235 7.169074 c 1798 | 22.417732 7.112729 22.727627 6.995068 23.025923 6.816091 c 1799 | 23.327534 6.637113 23.577770 6.366990 23.776634 6.005721 c 1800 | 23.975498 5.644452 24.074930 5.160550 24.074930 4.554016 c 1801 | 24.074930 -0.477234 l 1802 | 22.901634 -0.477234 l 1803 | 22.901634 0.556857 l 1804 | 22.841974 0.556857 l 1805 | 22.762428 0.391137 22.629854 0.213817 22.444248 0.024897 c 1806 | 22.258642 -0.164023 22.011719 -0.324772 21.703480 -0.457348 c 1807 | 21.395243 -0.589923 21.019058 -0.656211 20.574928 -0.656211 c 1808 | h 1809 | 20.753906 0.397766 m 1810 | 21.217920 0.397766 21.609020 0.488912 21.927202 0.671204 c 1811 | 22.248699 0.853496 22.490648 1.088818 22.653053 1.377170 c 1812 | 22.818773 1.665522 22.901634 1.968789 22.901634 2.286971 c 1813 | 22.901634 3.360834 l 1814 | 22.851919 3.301175 22.742544 3.246488 22.573509 3.196772 c 1815 | 22.407789 3.150371 22.215553 3.108940 21.996803 3.072482 c 1816 | 21.781368 3.039338 21.570904 3.009508 21.365412 2.982993 c 1817 | 21.163235 2.959793 20.999172 2.939906 20.873224 2.923334 c 1818 | 20.568300 2.883561 20.283262 2.818931 20.018110 2.729443 c 1819 | 19.756273 2.643268 19.544151 2.512349 19.381746 2.336687 c 1820 | 19.222656 2.164338 19.143110 1.929016 19.143110 1.630721 c 1821 | 19.143110 1.223050 19.293915 0.914811 19.595526 0.706005 c 1822 | 19.900450 0.500512 20.286577 0.397766 20.753906 0.397766 c 1823 | h 1824 | 25.142578 -0.477234 m 1825 | h 1826 | 27.389738 4.116516 m 1827 | 27.389738 -0.477234 l 1828 | 26.216442 -0.477234 l 1829 | 26.216442 7.159130 l 1830 | 27.349964 7.159130 l 1831 | 27.349964 5.965948 l 1832 | 27.449396 5.965948 l 1833 | 27.628374 6.353732 27.900154 6.665285 28.264738 6.900607 c 1834 | 28.629322 7.139244 29.099966 7.258562 29.676670 7.258562 c 1835 | 30.193716 7.258562 30.646130 7.152501 31.033915 6.940380 c 1836 | 31.421698 6.731573 31.723307 6.413391 31.938744 5.985835 c 1837 | 32.154179 5.561592 32.261898 5.024660 32.261898 4.375039 c 1838 | 32.261898 -0.477234 l 1839 | 31.088602 -0.477234 l 1840 | 31.088602 4.295493 l 1841 | 31.088602 4.895399 30.932825 5.362728 30.621271 5.697482 c 1842 | 30.309719 6.035551 29.882162 6.204585 29.338600 6.204585 c 1843 | 28.964075 6.204585 28.629320 6.123382 28.334339 5.960977 c 1844 | 28.042673 5.798571 27.812323 5.561592 27.643288 5.250039 c 1845 | 27.474255 4.938486 27.389738 4.560645 27.389738 4.116516 c 1846 | h 1847 | 33.332031 -0.477234 m 1848 | h 1849 | 35.499645 2.306857 m 1850 | 35.479759 3.758561 l 1851 | 35.718395 3.758561 l 1852 | 39.059303 7.159130 l 1853 | 40.511009 7.159130 l 1854 | 36.951351 3.559698 l 1855 | 36.851917 3.559698 l 1856 | 35.499645 2.306857 l 1857 | h 1858 | 34.405895 -0.477234 m 1859 | 34.405895 9.704584 l 1860 | 35.579189 9.704584 l 1861 | 35.579189 -0.477234 l 1862 | 34.405895 -0.477234 l 1863 | h 1864 | 39.258167 -0.477234 m 1865 | 36.275211 3.301175 l 1866 | 37.110439 4.116516 l 1867 | 40.749645 -0.477234 l 1868 | 39.258167 -0.477234 l 1869 | h 1870 | 40.947266 -0.477234 m 1871 | h 1872 | 47.430222 5.448903 m 1873 | 46.376244 5.150607 l 1874 | 46.309956 5.326270 46.212181 5.496962 46.082920 5.662681 c 1875 | 45.956970 5.831715 45.784622 5.970920 45.565872 6.080295 c 1876 | 45.347122 6.189670 45.067055 6.244357 44.725674 6.244357 c 1877 | 44.258347 6.244357 43.868904 6.136639 43.557350 5.921204 c 1878 | 43.249111 5.709083 43.094994 5.438959 43.094994 5.110834 c 1879 | 43.094994 4.819167 43.201054 4.588818 43.413174 4.419784 c 1880 | 43.625298 4.250750 43.956738 4.109887 44.407494 3.997198 c 1881 | 45.541016 3.718789 l 1882 | 46.223782 3.553069 46.732540 3.299518 47.067295 2.958136 c 1883 | 47.402046 2.620068 47.569424 2.184225 47.569424 1.650607 c 1884 | 47.569424 1.213107 47.443478 0.822008 47.191586 0.477311 c 1885 | 46.943005 0.132614 46.594994 -0.139166 46.147549 -0.338029 c 1886 | 45.700104 -0.536893 45.179745 -0.636325 44.586472 -0.636325 c 1887 | 43.807590 -0.636325 43.162941 -0.467291 42.652523 -0.129223 c 1888 | 42.142105 0.208846 41.818951 0.702691 41.683060 1.352312 c 1889 | 42.796696 1.630721 l 1890 | 42.902756 1.219736 43.103279 0.911497 43.398258 0.706005 c 1891 | 43.696556 0.500512 44.085999 0.397766 44.566586 0.397766 c 1892 | 45.113461 0.397766 45.547646 0.513770 45.869141 0.745777 c 1893 | 46.193951 0.981099 46.356358 1.262823 46.356358 1.590948 c 1894 | 46.356358 1.856099 46.263554 2.078164 46.077946 2.257141 c 1895 | 45.892342 2.439433 45.607304 2.575323 45.222836 2.664812 c 1896 | 43.950108 2.963107 l 1897 | 43.250771 3.128826 42.737038 3.385692 42.408913 3.733704 c 1898 | 42.084103 4.085030 41.921696 4.524187 41.921696 5.051175 c 1899 | 41.921696 5.482046 42.042671 5.863202 42.284622 6.194641 c 1900 | 42.529888 6.526081 42.862988 6.786261 43.283913 6.975182 c 1901 | 43.708157 7.164102 44.188744 7.258562 44.725674 7.258562 c 1902 | 45.481354 7.258562 46.074631 7.092842 46.505505 6.761403 c 1903 | 46.939690 6.429964 47.247929 5.992464 47.430222 5.448903 c 1904 | h 1905 | 52.199219 -0.477234 m 1906 | h 1907 | 56.673649 7.159130 m 1908 | 56.673649 6.164812 l 1909 | 52.557175 6.164812 l 1910 | 52.557175 7.159130 l 1911 | 56.673649 7.159130 l 1912 | h 1913 | 53.790127 -0.477234 m 1914 | 53.790127 8.213107 l 1915 | 53.790127 8.650608 53.892872 9.015191 54.098366 9.306858 c 1916 | 54.303860 9.598524 54.570667 9.817274 54.898792 9.963108 c 1917 | 55.226917 10.108941 55.573273 10.181858 55.937855 10.181858 c 1918 | 56.226208 10.181858 56.461529 10.158657 56.643822 10.112255 c 1919 | 56.826115 10.065854 56.962002 10.022766 57.051491 9.982994 c 1920 | 56.713425 8.968789 l 1921 | 56.653767 8.988676 56.570904 9.013534 56.464844 9.043363 c 1922 | 56.362099 9.073193 56.226208 9.088108 56.057175 9.088108 c 1923 | 55.669392 9.088108 55.389324 8.990333 55.216976 8.794783 c 1924 | 55.047943 8.599234 54.963425 8.312539 54.963425 7.934699 c 1925 | 54.963425 -0.477234 l 1926 | 53.790127 -0.477234 l 1927 | h 1928 | 56.929688 -0.477234 m 1929 | h 1930 | 61.105824 -0.636325 m 1931 | 60.416428 -0.636325 59.811554 -0.472262 59.291195 -0.144137 c 1932 | 58.774147 0.183988 58.369793 0.643031 58.078125 1.232993 c 1933 | 57.789772 1.822956 57.645596 2.512350 57.645596 3.301175 c 1934 | 57.645596 4.096630 57.789772 4.790996 58.078125 5.384272 c 1935 | 58.369793 5.977549 58.774147 6.438250 59.291195 6.766374 c 1936 | 59.811554 7.094499 60.416428 7.258562 61.105824 7.258562 c 1937 | 61.795219 7.258562 62.398438 7.094499 62.915482 6.766374 c 1938 | 63.435841 6.438250 63.840199 5.977549 64.128548 5.384272 c 1939 | 64.420212 4.790996 64.566048 4.096630 64.566048 3.301175 c 1940 | 64.566048 2.512350 64.420212 1.822956 64.128548 1.232993 c 1941 | 63.840199 0.643031 63.435841 0.183988 62.915482 -0.144137 c 1942 | 62.398438 -0.472262 61.795219 -0.636325 61.105824 -0.636325 c 1943 | h 1944 | 61.105824 0.417652 m 1945 | 61.629498 0.417652 62.060368 0.551886 62.398438 0.820352 c 1946 | 62.736507 1.088818 62.986744 1.441800 63.149147 1.879300 c 1947 | 63.311554 2.316800 63.392757 2.790758 63.392757 3.301175 c 1948 | 63.392757 3.811592 63.311554 4.287208 63.149147 4.728022 c 1949 | 62.986744 5.168836 62.736507 5.525134 62.398438 5.796914 c 1950 | 62.060368 6.068695 61.629498 6.204585 61.105824 6.204585 c 1951 | 60.582150 6.204585 60.151279 6.068695 59.813210 5.796914 c 1952 | 59.475140 5.525134 59.224903 5.168836 59.062500 4.728022 c 1953 | 58.900097 4.287208 58.818893 3.811592 58.818893 3.301175 c 1954 | 58.818893 2.790758 58.900097 2.316800 59.062500 1.879300 c 1955 | 59.224903 1.441800 59.475140 1.088818 59.813210 0.820352 c 1956 | 60.151279 0.551886 60.582150 0.417652 61.105824 0.417652 c 1957 | h 1958 | 65.283203 -0.477234 m 1959 | h 1960 | 66.357063 -0.477234 m 1961 | 66.357063 7.159130 l 1962 | 67.490593 7.159130 l 1963 | 67.490593 6.005721 l 1964 | 67.570137 6.005721 l 1965 | 67.709335 6.383562 67.961227 6.690143 68.325813 6.925466 c 1966 | 68.690399 7.160788 69.101379 7.278449 69.558769 7.278449 c 1967 | 69.644943 7.278449 69.752663 7.276791 69.881927 7.273477 c 1968 | 70.011185 7.270162 70.108955 7.265191 70.175247 7.258562 c 1969 | 70.175247 6.065380 l 1970 | 70.135475 6.075323 70.044334 6.090238 69.901810 6.110125 c 1971 | 69.762604 6.133325 69.615112 6.144925 69.459343 6.144925 c 1972 | 69.088127 6.144925 68.756683 6.067037 68.465019 5.911261 c 1973 | 68.176666 5.758799 67.947975 5.546677 67.778938 5.274897 c 1974 | 67.613220 5.006431 67.530365 4.699850 67.530365 4.355153 c 1975 | 67.530365 -0.477234 l 1976 | 66.357063 -0.477234 l 1977 | h 1978 | 74.429688 -0.477234 m 1979 | h 1980 | 78.824577 7.159130 m 1981 | 78.824577 6.164812 l 1982 | 74.867188 6.164812 l 1983 | 74.867188 7.159130 l 1984 | 78.824577 7.159130 l 1985 | h 1986 | 76.020599 8.988675 m 1987 | 77.193893 8.988675 l 1988 | 77.193893 1.710266 l 1989 | 77.193893 1.378827 77.241951 1.130247 77.338066 0.964527 c 1990 | 77.437500 0.802122 77.563446 0.692747 77.715912 0.636402 c 1991 | 77.871689 0.583372 78.035751 0.556857 78.208099 0.556857 c 1992 | 78.337357 0.556857 78.443413 0.563486 78.526276 0.576743 c 1993 | 78.609138 0.593315 78.675423 0.606573 78.725143 0.616516 c 1994 | 78.963776 -0.437461 l 1995 | 78.884232 -0.467291 78.773201 -0.497120 78.630684 -0.526950 c 1996 | 78.488159 -0.560094 78.307526 -0.576666 78.088776 -0.576666 c 1997 | 77.757339 -0.576666 77.432533 -0.505406 77.114349 -0.362887 c 1998 | 76.799477 -0.220368 76.537643 -0.003276 76.328835 0.288391 c 1999 | 76.123344 0.580058 76.020599 0.947955 76.020599 1.392084 c 2000 | 76.020599 8.988675 l 2001 | h 2002 | 79.679688 -0.477234 m 2003 | h 2004 | 81.926849 4.116516 m 2005 | 81.926849 -0.477234 l 2006 | 80.753548 -0.477234 l 2007 | 80.753548 9.704584 l 2008 | 81.926849 9.704584 l 2009 | 81.926849 5.965948 l 2010 | 82.026276 5.965948 l 2011 | 82.205254 6.360361 82.473724 6.673571 82.831673 6.905579 c 2012 | 83.192947 7.140901 83.673531 7.258562 84.273438 7.258562 c 2013 | 84.793800 7.258562 85.249527 7.154158 85.640625 6.945352 c 2014 | 86.031723 6.739859 86.334991 6.423334 86.550423 5.995778 c 2015 | 86.769173 5.571535 86.878548 5.031289 86.878548 4.375039 c 2016 | 86.878548 -0.477234 l 2017 | 85.705254 -0.477234 l 2018 | 85.705254 4.295493 l 2019 | 85.705254 4.902027 85.547821 5.371015 85.232956 5.702454 c 2020 | 84.921402 6.037208 84.488876 6.204585 83.935371 6.204585 c 2021 | 83.550903 6.204585 83.206200 6.123382 82.901276 5.960977 c 2022 | 82.599663 5.798571 82.361031 5.561592 82.185371 5.250039 c 2023 | 82.013023 4.938486 81.926849 4.560645 81.926849 4.116516 c 2024 | h 2025 | 87.951172 -0.477234 m 2026 | h 2027 | 92.226738 -0.636325 m 2028 | 91.490944 -0.636325 90.856239 -0.473920 90.322624 -0.149109 c 2029 | 89.792320 0.179016 89.382988 0.636402 89.094635 1.223050 c 2030 | 88.809601 1.813012 88.667084 2.499092 88.667084 3.281289 c 2031 | 88.667084 4.063486 88.809601 4.752880 89.094635 5.349471 c 2032 | 89.382988 5.949376 89.784035 6.416706 90.297760 6.751460 c 2033 | 90.814812 7.089528 91.418030 7.258562 92.107422 7.258562 c 2034 | 92.505150 7.258562 92.897903 7.192274 93.285690 7.059699 c 2035 | 93.673470 6.927123 94.026451 6.711687 94.344635 6.413391 c 2036 | 94.662819 6.118410 94.916367 5.727312 95.105293 5.240096 c 2037 | 95.294212 4.752880 95.388672 4.152974 95.388672 3.440380 c 2038 | 95.388672 2.943221 l 2039 | 89.502312 2.943221 l 2040 | 89.502312 3.957425 l 2041 | 94.195488 3.957425 l 2042 | 94.195488 4.388296 94.109314 4.772766 93.936966 5.110834 c 2043 | 93.767937 5.448903 93.525986 5.715712 93.211113 5.911261 c 2044 | 92.899559 6.106810 92.531662 6.204585 92.107422 6.204585 c 2045 | 91.640091 6.204585 91.235733 6.088581 90.894356 5.856573 c 2046 | 90.556290 5.627880 90.296104 5.329585 90.113815 4.961687 c 2047 | 89.931526 4.593789 89.840378 4.199376 89.840378 3.778448 c 2048 | 89.840378 3.102311 l 2049 | 89.840378 2.525607 89.939812 2.036735 90.138672 1.635693 c 2050 | 90.340851 1.237966 90.620918 0.934698 90.978874 0.725891 c 2051 | 91.336823 0.520398 91.752777 0.417652 92.226738 0.417652 c 2052 | 92.534973 0.417652 92.813385 0.460739 93.061966 0.546914 c 2053 | 93.313866 0.636402 93.530960 0.768978 93.713249 0.944641 c 2054 | 93.895538 1.123618 94.036400 1.345683 94.135834 1.610834 c 2055 | 95.269356 1.292652 l 2056 | 95.150040 0.908183 94.949516 0.570115 94.667793 0.278448 c 2057 | 94.386070 -0.009904 94.038055 -0.235283 93.623756 -0.397689 c 2058 | 93.209457 -0.556780 92.743790 -0.636325 92.226738 -0.636325 c 2059 | h 2060 | 100.037109 -0.477234 m 2061 | h 2062 | 101.110970 -3.340870 m 2063 | 101.110970 7.159130 l 2064 | 102.244499 7.159130 l 2065 | 102.244499 5.946062 l 2066 | 102.383698 5.946062 l 2067 | 102.469872 6.078638 102.589195 6.247672 102.741653 6.453164 c 2068 | 102.897430 6.661971 103.119492 6.847577 103.407845 7.009982 c 2069 | 103.699509 7.175702 104.093925 7.258562 104.591087 7.258562 c 2070 | 105.234077 7.258562 105.800842 7.097814 106.291374 6.776318 c 2071 | 106.781906 6.454821 107.164719 5.999092 107.439812 5.409130 c 2072 | 107.714905 4.819168 107.852448 4.123145 107.852448 3.321061 c 2073 | 107.852448 2.512349 107.714905 1.811355 107.439812 1.218079 c 2074 | 107.164719 0.628117 106.783562 0.170731 106.296341 -0.154080 c 2075 | 105.809128 -0.475576 105.247337 -0.636325 104.610970 -0.636325 c 2076 | 104.120438 -0.636325 103.727684 -0.555122 103.432709 -0.392716 c 2077 | 103.137726 -0.226997 102.910690 -0.039734 102.751595 0.169073 c 2078 | 102.592506 0.381194 102.469872 0.556857 102.383698 0.696061 c 2079 | 102.284271 0.696061 l 2080 | 102.284271 -3.340870 l 2081 | 101.110970 -3.340870 l 2082 | h 2083 | 102.264381 3.340948 m 2084 | 102.264381 2.764243 102.348900 2.255484 102.517937 1.814670 c 2085 | 102.686966 1.377170 102.933891 1.034130 103.258698 0.785550 c 2086 | 103.583511 0.540285 103.981239 0.417652 104.451881 0.417652 c 2087 | 104.942413 0.417652 105.351738 0.546913 105.679863 0.805436 c 2088 | 106.011307 1.067274 106.259888 1.418600 106.425606 1.859414 c 2089 | 106.594635 2.303543 106.679153 2.797387 106.679153 3.340948 c 2090 | 106.679153 3.877880 106.596291 4.361782 106.430573 4.792653 c 2091 | 106.268166 5.226838 106.021248 5.569878 105.689812 5.821772 c 2092 | 105.361687 6.076981 104.949043 6.204585 104.451881 6.204585 c 2093 | 103.974609 6.204585 103.573563 6.083609 103.248756 5.841658 c 2094 | 102.923950 5.603022 102.678680 5.268268 102.512962 4.837397 c 2095 | 102.347244 4.409841 102.264381 3.911024 102.264381 3.340948 c 2096 | h 2097 | 108.568359 -0.477234 m 2098 | h 2099 | 111.889381 -0.656211 m 2100 | 111.405479 -0.656211 110.966324 -0.565065 110.571907 -0.382773 c 2101 | 110.177498 -0.197167 109.864288 0.069641 109.632278 0.417652 c 2102 | 109.400276 0.768978 109.284271 1.193221 109.284271 1.690380 c 2103 | 109.284271 2.127880 109.370445 2.482520 109.542793 2.754300 c 2104 | 109.715141 3.029395 109.945488 3.244831 110.233841 3.400607 c 2105 | 110.522194 3.556383 110.840378 3.672387 111.188385 3.748618 c 2106 | 111.539711 3.828163 111.892700 3.891137 112.247337 3.937539 c 2107 | 112.711349 3.997198 113.087532 4.041943 113.375885 4.071772 c 2108 | 113.667549 4.104916 113.879677 4.159603 114.012253 4.235834 c 2109 | 114.148140 4.312065 114.216087 4.444641 114.216087 4.633562 c 2110 | 114.216087 4.673334 l 2111 | 114.216087 5.163865 114.081856 5.545020 113.813385 5.816800 c 2112 | 113.548233 6.088581 113.145538 6.224471 112.605293 6.224471 c 2113 | 112.045158 6.224471 111.606003 6.101838 111.287819 5.856573 c 2114 | 110.969635 5.611308 110.745918 5.349471 110.616653 5.071062 c 2115 | 109.503021 5.468789 l 2116 | 109.701881 5.932805 109.967033 6.294073 110.298470 6.552596 c 2117 | 110.633224 6.814433 110.997810 6.996725 111.392220 7.099471 c 2118 | 111.789948 7.205531 112.181053 7.258562 112.565521 7.258562 c 2119 | 112.810783 7.258562 113.092506 7.228732 113.410690 7.169074 c 2120 | 113.732185 7.112729 114.042084 6.995068 114.340378 6.816091 c 2121 | 114.641991 6.637113 114.892227 6.366990 115.091087 6.005721 c 2122 | 115.289948 5.644452 115.389381 5.160550 115.389381 4.554016 c 2123 | 115.389381 -0.477234 l 2124 | 114.216087 -0.477234 l 2125 | 114.216087 0.556857 l 2126 | 114.156425 0.556857 l 2127 | 114.076881 0.391137 113.944305 0.213817 113.758698 0.024897 c 2128 | 113.573097 -0.164023 113.326172 -0.324772 113.017937 -0.457348 c 2129 | 112.709694 -0.589923 112.333511 -0.656211 111.889381 -0.656211 c 2130 | h 2131 | 112.068359 0.397766 m 2132 | 112.532372 0.397766 112.923470 0.488912 113.241653 0.671204 c 2133 | 113.563148 0.853496 113.805099 1.088818 113.967506 1.377170 c 2134 | 114.133224 1.665522 114.216087 1.968789 114.216087 2.286971 c 2135 | 114.216087 3.360834 l 2136 | 114.166367 3.301175 114.056992 3.246488 113.887962 3.196772 c 2137 | 113.722244 3.150371 113.530006 3.108940 113.311256 3.072482 c 2138 | 113.095825 3.039338 112.885361 3.009508 112.679863 2.982993 c 2139 | 112.477684 2.959793 112.313622 2.939906 112.187675 2.923334 c 2140 | 111.882751 2.883561 111.597717 2.818931 111.332565 2.729443 c 2141 | 111.070732 2.643268 110.858604 2.512349 110.696198 2.336687 c 2142 | 110.537109 2.164338 110.457565 1.929016 110.457565 1.630721 c 2143 | 110.457565 1.223050 110.608368 0.914811 110.909981 0.706005 c 2144 | 111.214905 0.500512 111.601028 0.397766 112.068359 0.397766 c 2145 | h 2146 | 116.183594 -0.477234 m 2147 | h 2148 | 118.013138 -3.340870 m 2149 | 117.814278 -3.340870 117.636955 -3.324299 117.481178 -3.291155 c 2150 | 117.325401 -3.261326 117.217682 -3.231495 117.158028 -3.201666 c 2151 | 117.456322 -2.167575 l 2152 | 117.741364 -2.240492 117.993256 -2.267007 118.212006 -2.247120 c 2153 | 118.430756 -2.227234 118.624649 -2.129459 118.793678 -1.953796 c 2154 | 118.966026 -1.781447 119.123459 -1.501382 119.265984 -1.113598 c 2155 | 119.484734 -0.517007 l 2156 | 116.660866 7.159130 l 2157 | 117.933594 7.159130 l 2158 | 120.041550 1.073902 l 2159 | 120.121094 1.073902 l 2160 | 122.229050 7.159130 l 2161 | 123.501778 7.159130 l 2162 | 120.260300 -1.590870 l 2163 | 120.114464 -1.985283 119.933830 -2.311751 119.718391 -2.570274 c 2164 | 119.502960 -2.832111 119.252724 -3.026003 118.967682 -3.151949 c 2165 | 118.685959 -3.277897 118.367775 -3.340870 118.013138 -3.340870 c 2166 | h 2167 | 123.976562 -0.477234 m 2168 | h 2169 | 125.050423 -0.477234 m 2170 | 125.050423 7.159130 l 2171 | 126.183952 7.159130 l 2172 | 126.183952 5.965948 l 2173 | 126.283379 5.965948 l 2174 | 126.442474 6.373619 126.699341 6.690143 127.053978 6.915522 c 2175 | 127.408615 7.144215 127.834518 7.258562 128.331680 7.258562 c 2176 | 128.835464 7.258562 129.254730 7.144215 129.589493 6.915522 c 2177 | 129.927551 6.690143 130.191055 6.373619 130.379974 5.965948 c 2178 | 130.459518 5.965948 l 2179 | 130.655060 6.360361 130.948395 6.673571 131.339493 6.905579 c 2180 | 131.730591 7.140901 132.199570 7.258562 132.746445 7.258562 c 2181 | 133.429214 7.258562 133.987686 7.044783 134.421875 6.617227 c 2182 | 134.856064 6.192984 135.073151 5.531763 135.073151 4.633562 c 2183 | 135.073151 -0.477234 l 2184 | 133.899857 -0.477234 l 2185 | 133.899857 4.633562 l 2186 | 133.899857 5.197009 133.745743 5.599708 133.437500 5.841658 c 2187 | 133.129257 6.083609 132.766342 6.204585 132.348724 6.204585 c 2188 | 131.811783 6.204585 131.395828 6.042179 131.100845 5.717369 c 2189 | 130.805878 5.395872 130.658386 4.988202 130.658386 4.494357 c 2190 | 130.658386 -0.477234 l 2191 | 129.465195 -0.477234 l 2192 | 129.465195 4.752880 l 2193 | 129.465195 5.187066 129.324341 5.536734 129.042618 5.801886 c 2194 | 128.760895 6.070352 128.397964 6.204585 127.953835 6.204585 c 2195 | 127.648911 6.204585 127.363876 6.123382 127.098724 5.960977 c 2196 | 126.836884 5.798571 126.624763 5.573193 126.462357 5.284841 c 2197 | 126.303268 4.999803 126.223724 4.670020 126.223724 4.295493 c 2198 | 126.223724 -0.477234 l 2199 | 125.050423 -0.477234 l 2200 | h 2201 | 136.144531 -0.477234 m 2202 | h 2203 | 140.420105 -0.636325 m 2204 | 139.684311 -0.636325 139.049591 -0.473920 138.515976 -0.149109 c 2205 | 137.985672 0.179016 137.576340 0.636402 137.287994 1.223050 c 2206 | 137.002960 1.813012 136.860443 2.499092 136.860443 3.281289 c 2207 | 136.860443 4.063486 137.002960 4.752880 137.287994 5.349471 c 2208 | 137.576340 5.949376 137.977386 6.416706 138.491119 6.751460 c 2209 | 139.008163 7.089528 139.611389 7.258562 140.300781 7.258562 c 2210 | 140.698502 7.258562 141.091263 7.192274 141.479050 7.059699 c 2211 | 141.866837 6.927123 142.219818 6.711687 142.537994 6.413391 c 2212 | 142.856186 6.118410 143.109726 5.727312 143.298645 5.240096 c 2213 | 143.487564 4.752880 143.582031 4.152974 143.582031 3.440380 c 2214 | 143.582031 2.943221 l 2215 | 137.695663 2.943221 l 2216 | 137.695663 3.957425 l 2217 | 142.388855 3.957425 l 2218 | 142.388855 4.388296 142.302673 4.772766 142.130325 5.110834 c 2219 | 141.961288 5.448903 141.719345 5.715712 141.404480 5.911261 c 2220 | 141.092926 6.106810 140.725021 6.204585 140.300781 6.204585 c 2221 | 139.833450 6.204585 139.429092 6.088581 139.087708 5.856573 c 2222 | 138.749649 5.627880 138.489471 5.329585 138.307175 4.961687 c 2223 | 138.124878 4.593789 138.033737 4.199376 138.033737 3.778448 c 2224 | 138.033737 3.102311 l 2225 | 138.033737 2.525607 138.133163 2.036735 138.332031 1.635693 c 2226 | 138.534210 1.237966 138.814270 0.934698 139.172226 0.725891 c 2227 | 139.530182 0.520398 139.946152 0.417652 140.420105 0.417652 c 2228 | 140.728348 0.417652 141.006744 0.460739 141.255325 0.546914 c 2229 | 141.507217 0.636402 141.724304 0.768978 141.906601 0.944641 c 2230 | 142.088898 1.123618 142.229752 1.345683 142.329193 1.610834 c 2231 | 143.462708 1.292652 l 2232 | 143.343399 0.908183 143.142868 0.570115 142.861145 0.278448 c 2233 | 142.579422 -0.009904 142.231415 -0.235283 141.817123 -0.397689 c 2234 | 141.402817 -0.556780 140.937149 -0.636325 140.420105 -0.636325 c 2235 | h 2236 | 144.292969 -0.477234 m 2237 | h 2238 | 146.540131 4.116516 m 2239 | 146.540131 -0.477234 l 2240 | 145.366837 -0.477234 l 2241 | 145.366837 7.159130 l 2242 | 146.500351 7.159130 l 2243 | 146.500351 5.965948 l 2244 | 146.599792 5.965948 l 2245 | 146.778763 6.353732 147.050552 6.665285 147.415131 6.900607 c 2246 | 147.779709 7.139244 148.250351 7.258562 148.827057 7.258562 c 2247 | 149.344101 7.258562 149.796524 7.152501 150.184311 6.940380 c 2248 | 150.572083 6.731573 150.873688 6.413391 151.089127 5.985835 c 2249 | 151.304565 5.561592 151.412292 5.024660 151.412292 4.375039 c 2250 | 151.412292 -0.477234 l 2251 | 150.238998 -0.477234 l 2252 | 150.238998 4.295493 l 2253 | 150.238998 4.895399 150.083221 5.362728 149.771667 5.697482 c 2254 | 149.460114 6.035551 149.032562 6.204585 148.488998 6.204585 c 2255 | 148.114471 6.204585 147.779709 6.123382 147.484726 5.960977 c 2256 | 147.193054 5.798571 146.962708 5.561592 146.793686 5.250039 c 2257 | 146.624649 4.938486 146.540131 4.560645 146.540131 4.116516 c 2258 | h 2259 | 152.482422 -0.477234 m 2260 | h 2261 | 156.877304 7.159130 m 2262 | 156.877304 6.164812 l 2263 | 152.919922 6.164812 l 2264 | 152.919922 7.159130 l 2265 | 156.877304 7.159130 l 2266 | h 2267 | 154.073334 8.988675 m 2268 | 155.246628 8.988675 l 2269 | 155.246628 1.710266 l 2270 | 155.246628 1.378827 155.294693 1.130247 155.390808 0.964527 c 2271 | 155.490234 0.802122 155.616180 0.692747 155.768646 0.636402 c 2272 | 155.924423 0.583372 156.088486 0.556857 156.260834 0.556857 c 2273 | 156.390091 0.556857 156.496155 0.563486 156.579010 0.576743 c 2274 | 156.661880 0.593315 156.728165 0.606573 156.777878 0.616516 c 2275 | 157.016510 -0.437461 l 2276 | 156.936966 -0.467291 156.825943 -0.497120 156.683411 -0.526950 c 2277 | 156.540894 -0.560094 156.360260 -0.576666 156.141510 -0.576666 c 2278 | 155.810074 -0.576666 155.485260 -0.505406 155.167084 -0.362887 c 2279 | 154.852219 -0.220368 154.590378 -0.003276 154.381577 0.288391 c 2280 | 154.176086 0.580058 154.073334 0.947955 154.073334 1.392084 c 2281 | 154.073334 8.988675 l 2282 | h 2283 | 157.568359 -0.477234 m 2284 | h 2285 | 159.497330 -0.556779 m 2286 | 159.252075 -0.556779 159.041611 -0.468947 158.865952 -0.293285 c 2287 | 158.690277 -0.117622 158.602448 0.092842 158.602448 0.338107 c 2288 | 158.602448 0.583372 158.690277 0.793837 158.865952 0.969499 c 2289 | 159.041611 1.145162 159.252075 1.232993 159.497330 1.232993 c 2290 | 159.742599 1.232993 159.953064 1.145162 160.128723 0.969499 c 2291 | 160.304398 0.793837 160.392227 0.583372 160.392227 0.338107 c 2292 | 160.392227 0.175701 160.350800 0.026554 160.267929 -0.109336 c 2293 | 160.188385 -0.245226 160.080673 -0.354601 159.944778 -0.437461 c 2294 | 159.812195 -0.517006 159.663055 -0.556779 159.497330 -0.556779 c 2295 | h 2296 | f 2297 | n 2298 | Q 2299 | q 2300 | 1.000000 0.000000 -0.000000 1.000000 54.000000 513.477234 cm 2301 | BT 2302 | 14.000000 0.000000 0.000000 14.000000 0.000000 -0.477234 Tm 2303 | /F1 1.000000 Tf 2304 | [ (\040) (\034) (\033) (\031) (\032) (\027) (\030) (\026) 22.904805 (\036) (\035) (\030) (\025) -11.363438 (\034) (\023) (\030) (\037) (\033) 19.975390 (\022) (\024) (\023) (\031) (\025) (\021) ] TJ 2305 | ET 2306 | Q 2307 | q 2308 | 1.000000 0.000000 -0.000000 1.000000 54.000000 54.000000 cm 2309 | 0.112500 0.112500 0.112500 scn 2310 | 0.000000 406.000000 m 2311 | 0.000000 409.313721 2.686292 412.000000 6.000000 412.000000 c 2312 | 386.000000 412.000000 l 2313 | 389.313721 412.000000 392.000000 409.313721 392.000000 406.000000 c 2314 | 392.000000 6.000000 l 2315 | 392.000000 2.686279 389.313721 0.000000 386.000000 0.000000 c 2316 | 5.999997 0.000000 l 2317 | 2.686289 0.000000 0.000000 2.686279 0.000000 6.000000 c 2318 | 0.000000 406.000000 l 2319 | h 2320 | f 2321 | n 2322 | Q 2323 | 2324 | endstream 2325 | endobj 2326 | 2327 | 89 0 obj 2328 | 59172 2329 | endobj 2330 | 2331 | 90 0 obj 2332 | << /Annots [] 2333 | /Type /Page 2334 | /MediaBox [ 0.000000 0.000000 500.000000 700.000000 ] 2335 | /Resources 87 0 R 2336 | /Contents 88 0 R 2337 | /Parent 91 0 R 2338 | >> 2339 | endobj 2340 | 2341 | 91 0 obj 2342 | << /Kids [ 90 0 R ] 2343 | /Count 1 2344 | /Type /Pages 2345 | >> 2346 | endobj 2347 | 2348 | 92 0 obj 2349 | << /Pages 91 0 R 2350 | /Type /Catalog 2351 | >> 2352 | endobj 2353 | 2354 | xref 2355 | 0 93 2356 | 0000000000 65535 f 2357 | 0000000010 00000 n 2358 | 0000000117 00000 n 2359 | 0000000138 00000 n 2360 | 0000000245 00000 n 2361 | 0000000266 00000 n 2362 | 0000000372 00000 n 2363 | 0000000393 00000 n 2364 | 0000000500 00000 n 2365 | 0000000521 00000 n 2366 | 0000000628 00000 n 2367 | 0000000650 00000 n 2368 | 0000000759 00000 n 2369 | 0000000781 00000 n 2370 | 0000000889 00000 n 2371 | 0000000911 00000 n 2372 | 0000001019 00000 n 2373 | 0000001041 00000 n 2374 | 0000001149 00000 n 2375 | 0000001171 00000 n 2376 | 0000001280 00000 n 2377 | 0000001302 00000 n 2378 | 0000001411 00000 n 2379 | 0000001433 00000 n 2380 | 0000001541 00000 n 2381 | 0000001563 00000 n 2382 | 0000001672 00000 n 2383 | 0000001694 00000 n 2384 | 0000001802 00000 n 2385 | 0000001824 00000 n 2386 | 0000001933 00000 n 2387 | 0000001955 00000 n 2388 | 0000002063 00000 n 2389 | 0000002085 00000 n 2390 | 0000002194 00000 n 2391 | 0000002216 00000 n 2392 | 0000002324 00000 n 2393 | 0000002346 00000 n 2394 | 0000002454 00000 n 2395 | 0000002476 00000 n 2396 | 0000002584 00000 n 2397 | 0000002606 00000 n 2398 | 0000002714 00000 n 2399 | 0000002736 00000 n 2400 | 0000002845 00000 n 2401 | 0000002867 00000 n 2402 | 0000002976 00000 n 2403 | 0000002998 00000 n 2404 | 0000003106 00000 n 2405 | 0000003128 00000 n 2406 | 0000003237 00000 n 2407 | 0000003259 00000 n 2408 | 0000003368 00000 n 2409 | 0000003390 00000 n 2410 | 0000003499 00000 n 2411 | 0000003521 00000 n 2412 | 0000003629 00000 n 2413 | 0000003651 00000 n 2414 | 0000003760 00000 n 2415 | 0000003782 00000 n 2416 | 0000003891 00000 n 2417 | 0000003913 00000 n 2418 | 0000004021 00000 n 2419 | 0000004043 00000 n 2420 | 0000004151 00000 n 2421 | 0000004173 00000 n 2422 | 0000004281 00000 n 2423 | 0000004303 00000 n 2424 | 0000004623 00000 n 2425 | 0000006165 00000 n 2426 | 0000006189 00000 n 2427 | 0000007751 00000 n 2428 | 0000008050 00000 n 2429 | 0000008073 00000 n 2430 | 0000010230 00000 n 2431 | 0000010254 00000 n 2432 | 0000010553 00000 n 2433 | 0000010576 00000 n 2434 | 0000013851 00000 n 2435 | 0000013875 00000 n 2436 | 0000014397 00000 n 2437 | 0000014420 00000 n 2438 | 0000014942 00000 n 2439 | 0000014965 00000 n 2440 | 0000015687 00000 n 2441 | 0000015710 00000 n 2442 | 0000016432 00000 n 2443 | 0000016455 00000 n 2444 | 0000019462 00000 n 2445 | 0000078692 00000 n 2446 | 0000078717 00000 n 2447 | 0000078896 00000 n 2448 | 0000078972 00000 n 2449 | trailer 2450 | << /ID [ (some) (id) ] 2451 | /Root 92 0 R 2452 | /Size 93 2453 | >> 2454 | startxref 2455 | 79033 2456 | %%EOF -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import "os" 4 | 5 | // BytesToIntArray converts a byte array to rune array 6 | // ie: []byte(`hello`) becomes []int{104,101,108,108,111} 7 | // which will then be properly marshalled into JSON 8 | // in the way Resend supports 9 | func BytesToIntArray(a []byte) []int { 10 | res := make([]int, len(a)) 11 | for i, v := range a { 12 | res[i] = int(v) 13 | } 14 | return res 15 | } 16 | 17 | func getEnv(key, df string) string { 18 | if value, ok := os.LookupEnv(key); ok { 19 | return value 20 | } 21 | return df 22 | } 23 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package resend 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestBytesToIntArray(t *testing.T) { 10 | assert.Equal(t, BytesToIntArray([]byte{44, 45, 46}), []int{44, 45, 46}) 11 | } 12 | --------------------------------------------------------------------------------