├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── main.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── core.go ├── core_test.go ├── data_api.go ├── example_test.go ├── fixtures └── fixtures.go ├── go.mod ├── http_client.go ├── http_client_decl.go ├── http_client_mock.go ├── mailjet_client.go ├── mailjet_client_decl.go ├── mailjet_client_test.go ├── mailjet_resources.go ├── resources ├── resources.go └── time_test.go ├── smtp_client.go ├── smtp_client_decl.go ├── smtp_client_mock.go └── tests └── main.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Code snippet(preferably) or steps to reproduce the behavior: 15 | 1. Do this 16 | 2. Then do that 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Go version** 22 | Specify the go version. 23 | Since the Go release is supported until there are two newer major releases, there is no guarantee that the bug will be fixed for older Go versions(https://go.dev/doc/devel/release#policy). 24 | 25 | **mailjet-apiv3-go release** 26 | Specify mailjet-apiv3-go release version. 27 | It should be the latest from https://github.com/mailjet/mailjet-apiv3-go/releases. If not, update to the latest and try to reproduce the bug. 28 | 29 | **Additional context** 30 | Please feel free to add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: [ master, main ] 6 | push: 7 | branches: [ master, main ] 8 | 9 | jobs: 10 | test: 11 | name: test 12 | strategy: 13 | matrix: 14 | go-version: 15 | - 1.22.x 16 | - 1.23.x 17 | os: [ ubuntu-latest ] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 # for golangci-lint's -new-from-rev 24 | 25 | - name: Set up Go 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version: ${{ matrix.go-version }} 29 | cache: true # caching and restoring go modules and build outputs 30 | 31 | - run: go env 32 | 33 | - name: Install deps 34 | run: go mod download 35 | 36 | - name: nilaway 37 | run: make nilaway 38 | 39 | - name: lint 40 | run: make lint 41 | 42 | - name: Test 43 | run: go test ./... -race -count=1 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | .idea/ 3 | *.o 4 | *.a 5 | *.so 6 | 7 | *.exe 8 | *.test 9 | *.prof 10 | 11 | # MacOS 12 | /.DS_Store 13 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | # Please, do not use `enable-all`: it's deprecated and will be removed soon. 3 | # Inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint. 4 | # Full list of linters - https://golangci-lint.run/usage/linters 5 | disable-all: true 6 | enable: 7 | - typecheck 8 | - errcheck # Mandatory. Do not disable. 9 | - ineffassign # Mandatory. Do not disable. 10 | - staticcheck # Mandatory. Do not disable. 11 | - govet 12 | - gosimple 13 | - gosec 14 | - bodyclose # https://github.com/timakin/bodyclose 15 | - goimports 16 | - stylecheck 17 | - gocritic 18 | - gomodguard 19 | - nolintlint 20 | 21 | # TODO: 22 | # - unused 23 | 24 | # TODO(v5): enable 25 | # - noctx 26 | 27 | linters-settings: 28 | errcheck: 29 | # List of functions to exclude from checking, where each entry is a single function to exclude. 30 | # See https://github.com/kisielk/errcheck#excluding-functions for details. 31 | exclude-functions: 32 | - (io.Closer).Close 33 | - (io.ReadCloser).Close 34 | 35 | govet: 36 | enable-all: true 37 | disable: 38 | - shadow 39 | - fieldalignment 40 | 41 | staticcheck: 42 | # SAxxxx checks in https://staticcheck.dev/docs/configuration/options/#checks 43 | # Example (to disable some checks): [ "all", "-SA1000", "-SA1001"] 44 | # Default: ["*"] 45 | # TODO(Go1.20+): enable SA1019 46 | checks: ["all", "-SA1019"] 47 | 48 | stylecheck: 49 | # https://staticcheck.io/docs/options#checks 50 | # TODO(v5): enable all: 51 | checks: ["all", "-ST1003", "-ST1005"] 52 | 53 | gomodguard: 54 | blocked: 55 | # List of blocked modules. 56 | modules: 57 | - github.com/pkg/errors: 58 | recommendations: 59 | - errors 60 | - github.com/mailgun/errors 61 | reason: "Deprecated" 62 | 63 | issues: 64 | # Maximum issues count per one linter. Set to 0 to disable. Default is 50. 65 | max-issues-per-linter: 0 66 | 67 | # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. 68 | max-same-issues: 50 69 | 70 | run: 71 | # include test files or not, default is true 72 | tests: true 73 | 74 | # Timeout for analysis, e.g. 30s, 5m. 75 | # Default: 1m 76 | timeout: 5m 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mailjet 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 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := all 2 | 3 | PACKAGE := github.com/mailjet/mailjet-apiv3-go 4 | GOPATH=$(shell go env GOPATH) 5 | 6 | NILAWAY = $(GOPATH)/bin/nilaway 7 | $(NILAWAY): 8 | go install go.uber.org/nilaway/cmd/nilaway@latest 9 | 10 | .PHONY: all 11 | all: test 12 | 13 | .PHONY: test 14 | test: 15 | go test . -race -count=1 16 | 17 | .PHONY: nilaway 18 | nilaway: $(NILAWAY) 19 | $(NILAWAY) -include-pkgs="$(PACKAGE)" -test=false ./... 20 | 21 | # linter: 22 | GOLINT = $(GOPATH)/bin/golangci-lint 23 | $(GOLINT): 24 | curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.61.0 25 | 26 | .PHONY: lint 27 | lint: $(GOLINT) 28 | $(GOLINT) run 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [mailjet]: http://www.mailjet.com 2 | [api_credential]: https://app.mailjet.com/account/api_keys 3 | [doc]: http://dev.mailjet.com/guides/?go# 4 | 5 | 6 | ![alt text](https://www.mailjet.com/images/email/transac/logo_header.png "Mailjet") 7 | 8 | # Official Mailjet Go Client 9 | 10 | [![CI](https://github.com/mailjet/mailjet-apiv3-go/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/mailjet/mailjet-apiv3-go/actions/workflows/main.yml) 11 | [![GoDoc](https://godoc.org/github.com/mailjet/mailjet-apiv3-go?status.svg)](https://pkg.go.dev/github.com/mailjet/mailjet-apiv3-go/v4) 12 | [![Go Report Card](https://goreportcard.com/badge/github.com/mailjet/mailjet-apiv3-go/v4)](https://goreportcard.com/report/github.com/mailjet/mailjet-apiv3-go/v4) 13 | 14 | ## Overview 15 | 16 | This repository contains the official Go wrapper for the Mailjet API. 17 | 18 | Check out all the resources and in the [Official Documentation][doc]. 19 | 20 | ## Table of contents 21 | 22 | - [Compatibility](#compatibility) 23 | - [Installation](#installation) 24 | - [Authentication](#authentication) 25 | - [Functional test](#functional-test) 26 | - [Make your first call](#make-your-first-call) 27 | - [Client / Call configuration specifics](#client--call-configuration-specifics) 28 | - [Send emails through proxy](#send-emails-through-proxy) 29 | - [Request examples](#request-examples) 30 | - [POST request](#post-request) 31 | - [Simple POST request](#simple-post-request) 32 | - [Using actions](#using-actions) 33 | - [GET request](#get-request) 34 | - [Retrieve all objects](#retrieve-all-objects) 35 | - [Use filtering](#use-filtering) 36 | - [Retrieve a single object](#retrieve-a-single-object) 37 | - [PUT request](#put-request) 38 | - [DELETE request](#delete-request) 39 | - [Contribute](#contribute) 40 | 41 | ## Compatibility 42 | 43 | Our library requires Go version 1.13 or higher. 44 | But since [each major Go release is supported until there are two newer major releases](https://go.dev/doc/devel/release#policy), there is no guarantee that it will be working on unsupported Go versions. 45 | 46 | **NOTE: Backward compatibility has been broken with the `v3.0` release which includes versioned paths required by go modules (See [Releasing Modules](https://github.com/golang/go/wiki/Modules#releasing-modules-v2-or-higher)).** 47 | 48 | ### Installation 49 | 50 | Get package: 51 | 52 | ``` 53 | go get github.com/mailjet/mailjet-apiv3-go/v4 54 | ``` 55 | 56 | And import the Mailjet wrapper: 57 | 58 | ```go 59 | import ( 60 | "github.com/mailjet/mailjet-apiv3-go/v4" 61 | "github.com/mailjet/mailjet-apiv3-go/v4/resources" 62 | ) 63 | ``` 64 | 65 | ## Authentication 66 | 67 | The Mailjet Email API uses your API and Secret keys for authentication. [Grab][api_credential] and save your Mailjet API credentials. 68 | 69 | ```bash 70 | export MJ_APIKEY_PUBLIC='your API key' 71 | export MJ_APIKEY_PRIVATE='your API secret' 72 | ``` 73 | 74 | Then initialize your Mailjet client: 75 | 76 | ```go 77 | // Get your environment Mailjet keys and connect 78 | publicKey := os.Getenv("MJ_APIKEY_PUBLIC") 79 | secretKey := os.Getenv("MJ_APIKEY_PRIVATE") 80 | 81 | mj := mailjet.NewMailjetClient(publicKey, secretKey) 82 | ``` 83 | 84 | ### Functional test 85 | 86 | In the `tests` folder you will find a small program using the wrapper. It can be used to check whether the Mailjet API keys in your environment are valid and active. 87 | 88 | ``` 89 | go run main.go 90 | ``` 91 | 92 | ## Make your first call 93 | 94 | Here's an example on how to send an email: 95 | 96 | ```go 97 | package main 98 | 99 | import ( 100 | "fmt" 101 | "log" 102 | "os" 103 | 104 | "github.com/mailjet/mailjet-apiv3-go/v4" 105 | ) 106 | 107 | func main() { 108 | mailjetClient := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 109 | messagesInfo := []mailjet.InfoMessagesV31{ 110 | { 111 | From: &mailjet.RecipientV31{ 112 | Email: "pilot@mailjet.com", 113 | Name: "Mailjet Pilot", 114 | }, 115 | To: &mailjet.RecipientsV31{ 116 | mailjet.RecipientV31{ 117 | Email: "passenger1@mailjet.com", 118 | Name: "passenger 1", 119 | }, 120 | }, 121 | Subject: "Your email flight plan!", 122 | TextPart: "Dear passenger 1, welcome to Mailjet! May the delivery force be with you!", 123 | HTMLPart: "

Dear passenger 1, welcome to Mailjet!


May the delivery force be with you!", 124 | }, 125 | } 126 | messages := mailjet.MessagesV31{Info: messagesInfo} 127 | res, err := mailjetClient.SendMailV31(&messages) 128 | if err != nil { 129 | log.Fatal(err) 130 | } 131 | fmt.Printf("Data: %+v\n", res) 132 | } 133 | ``` 134 | 135 | ## Client / Call configuration specifics 136 | 137 | ### Base URL 138 | 139 | The default base domain name for the Mailjet API is `https://api.mailjet.com`. You can modify this base URL by adding a different URL in the client configuration for your call: 140 | 141 | ```go 142 | mailjetClient := NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE"), "https://api.us.mailjet.com") 143 | ``` 144 | 145 | If your account has been moved to Mailjet's **US architecture**, the URL value you need to set is `https://api.us.mailjet.com`. 146 | 147 | ### Send emails through proxy 148 | 149 | ```go 150 | package main 151 | 152 | import ( 153 | "fmt" 154 | "log" 155 | "net/http" 156 | "net/url" 157 | "os" 158 | 159 | "github.com/mailjet/mailjet-apiv3-go/v4" 160 | ) 161 | 162 | // Set the http client with the given proxy url 163 | func setupProxy(proxyURLStr string) *http.Client { 164 | proxyURL, err := url.Parse(proxyURLStr) 165 | if err != nil { 166 | log.Fatal(err) 167 | } 168 | tr := &http.Transport{Proxy: http.ProxyURL(proxyURL)} 169 | client := &http.Client{} 170 | client.Transport = tr 171 | 172 | return client 173 | } 174 | 175 | func main() { 176 | publicKey := os.Getenv("MJ_APIKEY_PUBLIC") 177 | secretKey := os.Getenv("MJ_APIKEY_PRIVATE") 178 | proxyURL := os.Getenv("HTTP_PROXY") 179 | 180 | mj := mailjet.NewMailjetClient(publicKey, secretKey) 181 | 182 | // Here we inject our http client configured with our proxy 183 | client := setupProxy(proxyURL) 184 | mj.SetClient(client) 185 | 186 | messagesInfo := []mailjet.InfoMessagesV31{ 187 | { 188 | From: &mailjet.RecipientV31{ 189 | Email: "qwe@qwe.com", 190 | Name: "Bob Patrick", 191 | }, 192 | To: &mailjet.RecipientsV31{ 193 | mailjet.RecipientV31{ 194 | Email: "qwe@qwe.com", 195 | }, 196 | }, 197 | Subject: "Hello World!", 198 | TextPart: "Hi there !", 199 | }, 200 | } 201 | 202 | messages := &mailjet.MessagesV31{Info: messagesInfo} 203 | 204 | res, err := mj.SendMailV31(messages) 205 | if err != nil { 206 | fmt.Println(err) 207 | } else { 208 | fmt.Println("Success") 209 | fmt.Println(res) 210 | } 211 | } 212 | ``` 213 | 214 | ## Request examples 215 | 216 | ### POST request 217 | 218 | #### Simple POST request 219 | 220 | ```go 221 | // Create a new contact. 222 | package main 223 | 224 | import ( 225 | "fmt" 226 | "os" 227 | 228 | "github.com/mailjet/mailjet-apiv3-go/v4" 229 | "github.com/mailjet/mailjet-apiv3-go/v4/resources" 230 | ) 231 | 232 | func main() { 233 | mailjetClient := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 234 | var data []resources.Contact 235 | mr := &mailjet.Request{ 236 | Resource: "contact", 237 | } 238 | fmr := &mailjet.FullRequest{ 239 | Info: mr, 240 | Payload: &resources.Contact{ 241 | Email: "passenger@mailjet.com", 242 | IsExcludedFromCampaigns: true, 243 | Name: "New Contact", 244 | }, 245 | } 246 | err := mailjetClient.Post(fmr, &data) 247 | if err != nil { 248 | fmt.Println(err) 249 | } 250 | fmt.Printf("Data array: %+v\n", data) 251 | } 252 | ``` 253 | 254 | #### Using actions 255 | 256 | ```go 257 | // Create : Manage a contact subscription to a list 258 | package main 259 | 260 | import ( 261 | "fmt" 262 | "os" 263 | 264 | "github.com/mailjet/mailjet-apiv3-go/v4" 265 | "github.com/mailjet/mailjet-apiv3-go/v4/resources" 266 | ) 267 | 268 | func main() { 269 | mailjetClient := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 270 | var data []resources.ContactManagecontactslists 271 | mr := &mailjet.Request{ 272 | Resource: "contact", 273 | ID: 423, // replace with your contact ID here 274 | Action: "managecontactslists", 275 | } 276 | fmr := &mailjet.FullRequest{ 277 | Info: mr, 278 | Payload: &resources.ContactManagecontactslists{ 279 | ContactsLists: []resources.ContactsListAction{ // replace with your contact lists here 280 | { 281 | ListID: 432, 282 | Action: "addnoforce", 283 | }, 284 | { 285 | ListID: 553, 286 | Action: "addforce", 287 | }, 288 | }, 289 | }, 290 | } 291 | err := mailjetClient.Post(fmr, &data) 292 | if err != nil { 293 | fmt.Println(err) 294 | } 295 | fmt.Printf("Data array: %+v\n", data) 296 | } 297 | ``` 298 | 299 | ### GET request 300 | 301 | #### Retrieve all objects 302 | 303 | ```go 304 | // Retrieve all contacts: 305 | package main 306 | 307 | import ( 308 | "fmt" 309 | "os" 310 | 311 | "github.com/mailjet/mailjet-apiv3-go/v4" 312 | "github.com/mailjet/mailjet-apiv3-go/v4/resources" 313 | ) 314 | 315 | func main() { 316 | mailjetClient := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 317 | var data []resources.Contact 318 | _, _, err := mailjetClient.List("contact", &data) 319 | if err != nil { 320 | fmt.Println(err) 321 | } 322 | fmt.Printf("Data array: %+v\n", data) 323 | } 324 | ``` 325 | 326 | #### Use filtering 327 | 328 | ```go 329 | // Retrieve all contacts that are not in the campaign exclusion list: 330 | package main 331 | 332 | import ( 333 | "fmt" 334 | "os" 335 | 336 | "github.com/mailjet/mailjet-apiv3-go/v4" 337 | "github.com/mailjet/mailjet-apiv3-go/v4/resources" 338 | ) 339 | 340 | func main() { 341 | mailjetClient := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 342 | var data []resources.Contact 343 | _, _, err := mailjetClient.List("contact", &data, mailjet.Filter("IsExcludedFromCampaigns", "false")) 344 | if err != nil { 345 | fmt.Println(err) 346 | } 347 | fmt.Printf("Data array: %+v\n", data) 348 | } 349 | ``` 350 | 351 | #### Retrieve a single object 352 | 353 | ```go 354 | // Retrieve a specific contact ID: 355 | package main 356 | 357 | import ( 358 | "fmt" 359 | "os" 360 | 361 | "github.com/mailjet/mailjet-apiv3-go/v4" 362 | "github.com/mailjet/mailjet-apiv3-go/v4/resources" 363 | ) 364 | 365 | func main() { 366 | mailjetClient := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 367 | var data []resources.Contact 368 | mr := &mailjet.Request{ 369 | Resource: "contact", 370 | ID: 5234, // replace with your contact ID here 371 | } 372 | err := mailjetClient.Get(mr, &data) 373 | if err != nil { 374 | fmt.Println(err) 375 | } 376 | fmt.Printf("Data array: %+v\n", data) 377 | } 378 | ``` 379 | 380 | ### PUT request 381 | 382 | A `PUT` request in the Mailjet API will work as a `PATCH` request - the update will affect only the specified properties. The other properties of an existing resource will neither be modified, nor deleted. It also means that all non-mandatory properties can be omitted from your payload. 383 | 384 | Here's an example of a `PUT` request: 385 | 386 | ```go 387 | // Update the contact properties for a contact: 388 | package main 389 | 390 | import ( 391 | "fmt" 392 | "os" 393 | 394 | "github.com/mailjet/mailjet-apiv3-go/v4" 395 | "github.com/mailjet/mailjet-apiv3-go/v4/resources" 396 | ) 397 | 398 | func main() { 399 | mailjetClient := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 400 | mr := &mailjet.Request{ 401 | Resource: "contactdata", 402 | ID: 325, // replace with your contact ID here 403 | //AltID: "user1@example.com", // alternatively you can use contact's email 404 | } 405 | fmr := &mailjet.FullRequest{ 406 | Info: mr, 407 | Payload: &resources.Contactdata{ 408 | Data: resources.KeyValueList{ 409 | { 410 | "Name": "name", 411 | "Value": "John", 412 | }, 413 | { 414 | "Name": "country", 415 | "Value": "Canada", 416 | }, 417 | }, 418 | }, 419 | } 420 | err := mailjetClient.Put(fmr, nil) 421 | if err != nil { 422 | fmt.Println(err) 423 | } 424 | } 425 | ``` 426 | 427 | ### DELETE request 428 | 429 | Upon a successful DELETE request the response will not include a response body, but only a 204 No Content response code. 430 | 431 | Here's an example of a DELETE request: 432 | 433 | ```go 434 | // Delete an email template: 435 | package main 436 | 437 | import ( 438 | "fmt" 439 | "os" 440 | 441 | "github.com/mailjet/mailjet-apiv3-go/v4" 442 | ) 443 | 444 | func main() { 445 | mailjetClient := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 446 | 447 | mr := &mailjet.Request{ 448 | Resource: "template", 449 | ID: 423, // replace with your template ID here 450 | } 451 | 452 | err := mailjetClient.Delete(mr) 453 | if err != nil { 454 | fmt.Println(err) 455 | } 456 | } 457 | ``` 458 | 459 | ## Contribute 460 | 461 | Mailjet loves developers. You can be part of this project! 462 | 463 | This wrapper is a great introduction to the open source world, check out the code! 464 | 465 | Feel free to ask anything, and contribute: 466 | 467 | - Fork the project. 468 | - Create a new branch. 469 | - Implement your feature or bug fix. 470 | - Add documentation to it. 471 | - Commit, push, open a pull request and voilà. 472 | 473 | If you have suggestions on how to improve the guides, please submit an issue in our [Official API Documentation repo](https://github.com/mailjet/api-documentation). 474 | -------------------------------------------------------------------------------- /core.go: -------------------------------------------------------------------------------- 1 | package mailjet 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "reflect" 12 | "runtime" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | // DebugLevel defines the verbosity of the debug. 18 | var DebugLevel int 19 | 20 | // These are the different level of debug. 21 | const ( 22 | LevelNone = iota // No debug. 23 | LevelDebug // Debug without body. 24 | LevelDebugFull // Debug with body. 25 | ) 26 | 27 | // User-Agent is formated as "UserAgentBase/UserAgentVersion;runtime.Version()". 28 | const ( 29 | UserAgentBase = "mailjet-api-v3-go" 30 | UserAgentVersion = "4.0.1" 31 | ) 32 | 33 | const ( 34 | apiBase = "https://api.mailjet.com/v3" 35 | apiPath = "REST" 36 | dataPath = "DATA" 37 | ) 38 | 39 | // createRequest is the main core function. 40 | func createRequest(method string, url string, 41 | payload interface{}, onlyFields []string, 42 | options ...RequestOptions) (req *http.Request, err error) { 43 | 44 | body, err := convertPayload(payload, onlyFields) 45 | if err != nil { 46 | return req, fmt.Errorf("creating request: %s\n", err) 47 | } 48 | req, err = http.NewRequest(method, url, bytes.NewBuffer(body)) 49 | if err != nil { 50 | return req, fmt.Errorf("creating request: %s\n", err) 51 | } 52 | for _, option := range options { 53 | option(req) 54 | } 55 | userAgent(req) 56 | req.Header.Add("Accept", "application/json") 57 | return req, err 58 | } 59 | 60 | // converPayload returns payload casted in []byte. 61 | // If the payload is a structure, it's encoded to JSON. 62 | func convertPayload(payload interface{}, onlyFields []string) (body []byte, err error) { 63 | if payload != nil { 64 | switch t := payload.(type) { 65 | case string: 66 | body = []byte(t) 67 | case []byte: 68 | body = t 69 | default: 70 | v := reflect.Indirect(reflect.ValueOf(payload)) 71 | if v.Kind() == reflect.Ptr { 72 | return convertPayload(v.Interface(), onlyFields) 73 | } else if v.Kind() == reflect.Struct { 74 | body, err = json.Marshal(buildMap(v, onlyFields)) 75 | if err != nil { 76 | return body, err 77 | } 78 | } 79 | } 80 | if DebugLevel == LevelDebugFull { 81 | log.Println("Body:", string(body)) 82 | } 83 | } 84 | return body, err 85 | } 86 | 87 | // buildMap returns a map with fields specified in onlyFields (all fields if nil) 88 | // and without the read_only fields. 89 | func buildMap(v reflect.Value, onlyFields []string) map[string]interface{} { 90 | res := make(map[string]interface{}) 91 | if onlyFields != nil { 92 | for _, onlyField := range onlyFields { 93 | fieldType, exist := v.Type().FieldByName(onlyField) 94 | if exist { 95 | addFieldToMap(true, fieldType, v.FieldByName(onlyField), res) 96 | } 97 | } 98 | } else { 99 | for i := 0; i < v.NumField(); i++ { 100 | addFieldToMap(false, v.Type().Field(i), v.Field(i), res) 101 | } 102 | } 103 | return res 104 | } 105 | 106 | func addFieldToMap(onlyField bool, fieldType reflect.StructField, 107 | fieldValue reflect.Value, res map[string]interface{}) { 108 | if fieldType.Tag.Get("mailjet") != "read_only" { 109 | name, second := parseTag(fieldType.Tag.Get("json")) 110 | if name == "" { 111 | name = fieldType.Name 112 | } 113 | if !onlyField && second == "omitempty" && 114 | isEmptyValue(fieldValue) { 115 | return 116 | } 117 | res[name] = fieldValue.Interface() 118 | } 119 | } 120 | 121 | func parseTag(tag string) (string, string) { 122 | if idx := strings.Index(tag, ","); idx != -1 { 123 | return tag[:idx], tag[idx+1:] 124 | } 125 | return tag, "" 126 | } 127 | 128 | func isEmptyValue(v reflect.Value) bool { 129 | switch v.Kind() { 130 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 131 | return v.Len() == 0 132 | case reflect.Bool: 133 | return !v.Bool() 134 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 135 | return v.Int() == 0 136 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 137 | return v.Uint() == 0 138 | case reflect.Float32, reflect.Float64: 139 | return v.Float() == 0 140 | case reflect.Interface, reflect.Ptr: 141 | return v.IsNil() 142 | } 143 | return false 144 | } 145 | 146 | // userAgent add the User-Agent value to the request header. 147 | func userAgent(req *http.Request) { 148 | ua := fmt.Sprintf("%s/%s;%s", 149 | UserAgentBase, 150 | UserAgentVersion, 151 | runtime.Version(), 152 | ) 153 | req.Header.Add("User-Agent", ua) 154 | } 155 | 156 | func buildURL(baseURL string, info *Request) string { 157 | tokens := []string{baseURL, apiPath, info.Resource} 158 | if info.ID != 0 { 159 | id := strconv.FormatInt(info.ID, 10) 160 | tokens = append(tokens, id) 161 | } else if info.AltID != "" { 162 | tokens = append(tokens, string(info.AltID)) 163 | } 164 | if info.Action != "" { 165 | tokens = append(tokens, info.Action) 166 | } 167 | if info.ActionID != 0 { 168 | actionID := strconv.FormatInt(info.ActionID, 10) 169 | tokens = append(tokens, actionID) 170 | } 171 | return strings.Join(tokens, "/") 172 | } 173 | 174 | func buildDataURL(baseURL string, info *DataRequest) string { 175 | tokens := []string{baseURL, dataPath, info.SourceType} 176 | if info.SourceTypeID != 0 { 177 | id := strconv.FormatInt(info.SourceTypeID, 10) 178 | tokens = append(tokens, id) 179 | } 180 | if info.DataType != "" { 181 | tokens = append(tokens, info.DataType) 182 | if info.MimeType != "" { 183 | tokens = append(tokens, info.MimeType) 184 | } 185 | } 186 | if info.DataTypeID != 0 { 187 | DataTypeID := strconv.FormatInt(info.DataTypeID, 10) 188 | tokens = append(tokens, DataTypeID) 189 | } else if info.LastID { 190 | tokens = append(tokens, "LAST") 191 | } 192 | return strings.Join(tokens, "/") 193 | } 194 | 195 | // readJsonResult decodes the API response, returns Count and Total values 196 | // and stores the Data in the value pointed to by data. 197 | func readJSONResult(r io.Reader, data interface{}) (int, int, error) { 198 | var res RequestResult 199 | res.Data = data 200 | 201 | jsonBlob, err := ioutil.ReadAll(r) // ReadAll and store in jsonBlob (mandatory if we want to unmarshal two times) 202 | if err != nil { 203 | return 0, 0, fmt.Errorf("Error reading API response: %s", err) 204 | } 205 | if DebugLevel == LevelDebugFull { 206 | log.Println("Body: ", string(jsonBlob)) // DEBUG 207 | } 208 | 209 | err = json.Unmarshal(jsonBlob, &res) // First try with the RequestResult struct 210 | if err != nil { 211 | return 0, 0, fmt.Errorf("Error decoding API response: %s", err) 212 | } else if _, ok := data.(**SentResult); ok { // Send API case 213 | err = json.Unmarshal(jsonBlob, data) // Trying directly with struct specified in parameter 214 | if err != nil { 215 | return 0, 0, fmt.Errorf("Error decoding API response: %s", err) 216 | } 217 | return 0, 0, nil // Count and Total are undetermined 218 | } 219 | return res.Count, res.Total, nil 220 | } 221 | 222 | // NbAttempt defines the number of attempt 223 | // for a request as long as StatusCode == 500. 224 | var NbAttempt = 5 225 | 226 | // doRequest is called to execute the request. Authentification is set 227 | // with the public key and the secret key specified in MailjetClient. 228 | func (c *HTTPClient) doRequest(req *http.Request) (resp *http.Response, err error) { 229 | if req == nil { 230 | return nil, fmt.Errorf("req is nil") 231 | } 232 | 233 | debugRequest(req) // DEBUG 234 | req.SetBasicAuth(c.apiKeyPublic, c.apiKeyPrivate) 235 | for attempt := 0; attempt < NbAttempt; attempt++ { 236 | if resp != nil { 237 | resp.Body.Close() 238 | } 239 | resp, err = c.client.Do(req) 240 | if err != nil || (resp != nil && resp.StatusCode != 500) { 241 | break 242 | } 243 | } 244 | defer debugResponse(resp) // DEBUG 245 | if err != nil { 246 | return resp, fmt.Errorf("Error getting %s: %s", req.URL, err) 247 | } 248 | err = checkResponseError(resp) 249 | return resp, err 250 | } 251 | 252 | // checkResponseError returns response error if the statuscode is < 200 or >= 400. 253 | func checkResponseError(resp *http.Response) error { 254 | if resp == nil { 255 | return fmt.Errorf("resp is nil") 256 | } 257 | 258 | if resp.StatusCode < 200 || resp.StatusCode >= 400 { 259 | var mailjetErr RequestError 260 | mailjetErr.StatusCode = resp.StatusCode 261 | 262 | b, err := ioutil.ReadAll(resp.Body) 263 | if err != nil { 264 | mailjetErr.ErrorMessage = "unable to read response body" 265 | mailjetErr.ErrorInfo = err.Error() 266 | return mailjetErr 267 | } 268 | 269 | err = json.Unmarshal(b, &mailjetErr) 270 | if err != nil { 271 | mailjetErr.ErrorMessage = "unexpected server response: %s" + string(b) 272 | mailjetErr.ErrorInfo = "json unmarshal error: " + err.Error() 273 | return mailjetErr 274 | } 275 | return mailjetErr 276 | } 277 | 278 | return nil 279 | } 280 | 281 | // debugRequest is a custom dump of the request. 282 | // Method used, final URl called, and Header content are logged. 283 | func debugRequest(req *http.Request) { 284 | if DebugLevel > LevelNone && req != nil { 285 | log.Printf("Method used is: %s\n", req.Method) 286 | log.Printf("Final URL is: %s\n", req.URL) 287 | log.Printf("Header is: %s\n", req.Header) 288 | } 289 | } 290 | 291 | // debugResponse is a custom dump of the response. 292 | // Status and Header content are logged. 293 | func debugResponse(resp *http.Response) { 294 | if DebugLevel > LevelNone && resp != nil { 295 | log.Printf("Status is: %s\n", resp.Status) 296 | log.Printf("Header is: %s\n", resp.Header) 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /core_test.go: -------------------------------------------------------------------------------- 1 | package mailjet 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "runtime" 11 | "strings" 12 | "testing" 13 | ) 14 | 15 | func TestCreateRequest(t *testing.T) { 16 | req, err := createRequest("GET", apiBase, nil, nil) 17 | if err != nil { 18 | t.Fatal("Unexpected error:", err) 19 | } 20 | if req.Method != "GET" { 21 | t.Fatal("Wrong method:", req.Method) 22 | } 23 | if req.URL.String() != apiBase { 24 | t.Fatal("Wrong URL:", req.URL.String()) 25 | } 26 | ua := fmt.Sprintf("%s/%s;%s", 27 | UserAgentBase, 28 | UserAgentVersion, 29 | runtime.Version(), 30 | ) 31 | if req.Header["User-Agent"] == nil || req.Header["User-Agent"][0] != ua { 32 | t.Fatal("Wrong User-agent:", req.Header["User-Agent"][0]) 33 | } 34 | } 35 | 36 | func TestConvertPayload(t *testing.T) { 37 | type Test struct { 38 | ID int64 `mailjet:"read_only"` 39 | Name string 40 | Email string 41 | Address string `json:",omitempty" mailjet:"read_only"` 42 | TextPart string `json:"Text-Part,omitempty"` 43 | Header map[string]string `json:",omitempty"` 44 | MjCampaignID int64 `json:"Mj-CampaignID,omitempty" mailjet:"read_only"` 45 | } 46 | test := &Test{ 47 | ID: -42, 48 | Email: "ex@mple.com", 49 | TextPart: "This is text", 50 | } 51 | resMap := make(map[string]interface{}) 52 | resMap["Email"] = "ex@mple.com" 53 | resMap["Text-Part"] = "This is text" 54 | body, err := convertPayload(test, []string{"Email", "TextPart"}) 55 | if err != nil { 56 | t.Fatal("Unexpected error:", err) 57 | } 58 | res, _ := json.Marshal(resMap) 59 | if !bytes.Equal(body, res) { 60 | t.Fatal("Wrong body:", string(body), string(res)) 61 | } 62 | 63 | resMap["Name"] = "" 64 | body, err = convertPayload(&test, nil) 65 | if err != nil { 66 | t.Fatal("Unexpected error:", err) 67 | } 68 | res, _ = json.Marshal(resMap) 69 | if !bytes.Equal(body, res) { 70 | t.Fatal("Wrong body:", string(body), string(res)) 71 | } 72 | } 73 | 74 | func TestBuildUrl(t *testing.T) { 75 | info := &Request{ 76 | Resource: "contactslist", 77 | ID: 1, 78 | Action: "managemanycontacts", 79 | ActionID: 5, 80 | } 81 | expected := "https://api.mailjet.com/v3/REST/contactslist/1/managemanycontacts/5" 82 | res := buildURL(apiBase, info) 83 | if res != expected { 84 | t.Fatal("Fail to build URL:", res) 85 | } 86 | } 87 | 88 | func TestReadJsonEmptyResult(t *testing.T) { 89 | type TestStruct struct { 90 | Email string 91 | } 92 | var data []TestStruct 93 | body := `{"Count":0,"Data":[],"Total":0}` 94 | _, _, err := readJSONResult(strings.NewReader(body), &data) 95 | if err != nil { 96 | t.Fatal("Unexpected error:", err) 97 | } 98 | } 99 | 100 | func TestReadJsonResult(t *testing.T) { 101 | type TestStruct struct { 102 | Email string 103 | } 104 | var data []TestStruct 105 | body := `{"Count":2,"Data":[{"Email":"qwe@qwe.com"},{"Email":"aze@aze.com"}],"Total":1}` 106 | count, total, err := readJSONResult(strings.NewReader(body), &data) 107 | if err != nil { 108 | t.Fatal("Unexpected error:", err) 109 | } 110 | if count != 2 { 111 | t.Fatalf("Wrong count: %d != %d", count, 2) 112 | } 113 | if total != 1 { 114 | t.Fatalf("Wrong total: %d != %d", total, 2) 115 | } 116 | if data != nil { 117 | if data[0].Email != "qwe@qwe.com" { 118 | t.Fatalf("Fail to unmarshal JSON: %s != %s", data[0].Email, "qwe@qwe.com") 119 | } 120 | if data[1].Email != "aze@aze.com" { 121 | t.Fatalf("Fail to unmarshal JSON: %s != %s", data[1].Email, "aze@aze.com") 122 | } 123 | } else { 124 | t.Fatal("Fail to unmarshal JSON: empty res") 125 | } 126 | } 127 | 128 | func Test_checkResponseError(t *testing.T) { 129 | t.Run("4xx", func(t *testing.T) { 130 | const statusCode = 404 131 | 132 | resp := &http.Response{ 133 | StatusCode: statusCode, 134 | Body: io.NopCloser(strings.NewReader(`{"ErrorMessage":"foo not found"}`)), 135 | } 136 | 137 | err := checkResponseError(resp) 138 | if err == nil { 139 | t.Fatal("Expected error") 140 | } 141 | 142 | var mailjetErr RequestError 143 | if errors.As(err, &mailjetErr) { 144 | if mailjetErr.StatusCode != statusCode { 145 | t.Fatalf("Status code: exptected(%d) but got(%d)", statusCode, mailjetErr.StatusCode) 146 | } 147 | } else { 148 | t.Fatalf("err(%v) must be a RequestError type", err) 149 | } 150 | }) 151 | } 152 | -------------------------------------------------------------------------------- /data_api.go: -------------------------------------------------------------------------------- 1 | package mailjet 2 | 3 | import "strings" 4 | 5 | // ListData issues a GET to list the specified data resource 6 | // and stores the result in the value pointed to by res. 7 | // Filters can be add via functional options. 8 | func (c *Client) ListData(resource string, resp interface{}, options ...RequestOptions) (count, total int, err error) { 9 | url := buildDataURL(c.apiBase, &DataRequest{SourceType: resource}) 10 | req, err := createRequest("GET", url, nil, nil, options...) 11 | if err != nil { 12 | return count, total, err 13 | } 14 | 15 | return c.httpClient.Send(req).Read(resp).Call() 16 | } 17 | 18 | // GetData issues a GET to view a resource specifying an id 19 | // and stores the result in the value pointed to by res. 20 | // Filters can be add via functional options. 21 | // Without an specified SourceTypeID in MailjetDataRequest, it is the same as ListData. 22 | func (c *Client) GetData(mdr *DataRequest, res interface{}, options ...RequestOptions) (err error) { 23 | url := buildDataURL(c.apiBase, mdr) 24 | req, err := createRequest("GET", url, nil, nil, options...) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | _, _, err = c.httpClient.Send(req).Read(res).Call() 30 | return err 31 | } 32 | 33 | // PostData issues a POST to create a new data resource 34 | // and stores the result in the value pointed to by res. 35 | // Filters can be add via functional options. 36 | func (c *Client) PostData(fmdr *FullDataRequest, res interface{}, options ...RequestOptions) (err error) { 37 | url := buildDataURL(c.apiBase, fmdr.Info) 38 | req, err := createRequest("POST", url, fmdr.Payload, nil, options...) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | headers := map[string]string{"Content-Type": "application/json"} 44 | if fmdr.Info.MimeType != "" { 45 | contentType := strings.Replace(fmdr.Info.MimeType, ":", "/", 1) 46 | headers = map[string]string{"Content-Type": contentType} 47 | } 48 | 49 | _, _, err = c.httpClient.Send(req).With(headers).Read(res).Call() 50 | return err 51 | } 52 | 53 | // PutData is used to update a data resource. 54 | // Fields to be updated must be specified by the string array onlyFields. 55 | // If onlyFields is nil, all fields except these with the tag read_only, are updated. 56 | // Filters can be add via functional options. 57 | func (c *Client) PutData(fmr *FullDataRequest, onlyFields []string, options ...RequestOptions) (err error) { 58 | url := buildDataURL(c.apiBase, fmr.Info) 59 | req, err := createRequest("PUT", url, fmr.Payload, onlyFields, options...) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | headers := map[string]string{"Content-Type": "application/json"} 65 | _, _, err = c.httpClient.Send(req).With(headers).Call() 66 | 67 | return err 68 | } 69 | 70 | // DeleteData is used to delete a data resource. 71 | func (c *Client) DeleteData(mdr *DataRequest, options ...RequestOptions) (err error) { 72 | url := buildDataURL(c.apiBase, mdr) 73 | req, err := createRequest("DELETE", url, nil, nil, options...) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | _, _, err = c.httpClient.Send(req).Call() 79 | 80 | return err 81 | } 82 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package mailjet_test 2 | 3 | import ( 4 | "fmt" 5 | "net/textproto" 6 | "os" 7 | 8 | "github.com/mailjet/mailjet-apiv3-go/v4" 9 | "github.com/mailjet/mailjet-apiv3-go/v4/resources" 10 | ) 11 | 12 | var ( 13 | publicKey = os.Getenv("MJ_APIKEY_PUBLIC") 14 | privateKey = os.Getenv("MJ_APIKEY_PRIVATE") 15 | ) 16 | 17 | func exampleMailjetClientList() { 18 | mj := mailjet.NewMailjetClient(publicKey, privateKey) 19 | 20 | var res []resources.Metadata 21 | count, total, err := mj.List("metadata", &res) 22 | if err != nil { 23 | fmt.Fprintln(os.Stderr, err) 24 | os.Exit(1) 25 | } 26 | fmt.Printf("Count: %d\nTotal: %d\n", count, total) 27 | 28 | fmt.Println("Resources:") 29 | for _, resource := range res { 30 | fmt.Println(resource.Name) 31 | } 32 | } 33 | 34 | func exampleMailjetClientGet() { 35 | publicKey := os.Getenv("MJ_APIKEY_PUBLIC") 36 | secretKey := os.Getenv("MJ_APIKEY_PRIVATE") 37 | 38 | mj := mailjet.NewMailjetClient(publicKey, secretKey) 39 | 40 | var senders []resources.Sender 41 | info := &mailjet.Request{ 42 | Resource: "sender", 43 | AltID: "qwe@qwe.com", 44 | } 45 | err := mj.Get(info, &senders) 46 | if err != nil { 47 | fmt.Fprintln(os.Stderr, err) 48 | os.Exit(1) 49 | } 50 | if senders != nil { 51 | fmt.Printf("Sender struct: %+v\n", senders[0]) 52 | } 53 | } 54 | 55 | func exampleMailjetClientPost() { 56 | publicKey := os.Getenv("MJ_APIKEY_PUBLIC") 57 | secretKey := os.Getenv("MJ_APIKEY_PRIVATE") 58 | 59 | mj := mailjet.NewMailjetClient(publicKey, secretKey) 60 | 61 | var senders []resources.Sender 62 | fmr := &mailjet.FullRequest{ 63 | Info: &mailjet.Request{Resource: "sender"}, 64 | Payload: &resources.Sender{Name: "Default", Email: "qwe@qwe.com"}, 65 | } 66 | err := mj.Post(fmr, &senders) 67 | if err != nil { 68 | fmt.Fprintln(os.Stderr, err) 69 | os.Exit(1) 70 | } 71 | if senders != nil { 72 | fmt.Printf("Data struct: %+v\n", senders[0]) 73 | } 74 | } 75 | 76 | func exampleMailjetClientPut() { 77 | publicKey := os.Getenv("MJ_APIKEY_PUBLIC") 78 | secretKey := os.Getenv("MJ_APIKEY_PRIVATE") 79 | 80 | mj := mailjet.NewMailjetClient(publicKey, secretKey) 81 | 82 | fmr := &mailjet.FullRequest{ 83 | Info: &mailjet.Request{Resource: "sender", AltID: "qwe@qwe.com"}, 84 | Payload: &resources.Sender{Name: "Bob", IsDefaultSender: true}, 85 | } 86 | err := mj.Put(fmr, []string{"Name", "IsDefaultSender"}) 87 | if err != nil { 88 | fmt.Fprintln(os.Stderr, err) 89 | os.Exit(1) 90 | } else { 91 | fmt.Println("Success") 92 | } 93 | } 94 | 95 | func exampleMailjetClientDelete() { 96 | publicKey := os.Getenv("MJ_APIKEY_PUBLIC") 97 | secretKey := os.Getenv("MJ_APIKEY_PRIVATE") 98 | 99 | mj := mailjet.NewMailjetClient(publicKey, secretKey) 100 | 101 | info := &mailjet.Request{ 102 | Resource: "sender", 103 | AltID: "qwe@qwe.com", 104 | } 105 | err := mj.Delete(info) 106 | if err != nil { 107 | fmt.Fprintln(os.Stderr, err) 108 | os.Exit(1) 109 | } else { 110 | fmt.Println("Success") 111 | } 112 | } 113 | 114 | func exampleMailjetClientSendMail() { 115 | publicKey := os.Getenv("MJ_APIKEY_PUBLIC") 116 | secretKey := os.Getenv("MJ_APIKEY_PRIVATE") 117 | 118 | mj := mailjet.NewMailjetClient(publicKey, secretKey) 119 | 120 | param := &mailjet.InfoSendMail{ 121 | FromEmail: "qwe@qwe.com", 122 | FromName: "Bob Patrick", 123 | Recipients: []mailjet.Recipient{ 124 | { 125 | Email: "qwe@qwe.com", 126 | }, 127 | }, 128 | Subject: "Hello World!", 129 | TextPart: "Hi there !", 130 | } 131 | res, err := mj.SendMail(param) 132 | if err != nil { 133 | fmt.Println(err) 134 | } else { 135 | fmt.Println("Success") 136 | fmt.Println(res) 137 | } 138 | } 139 | 140 | func exampleMailjetClientSendMailSMTP() { 141 | mj := mailjet.NewMailjetClient(publicKey, privateKey) 142 | 143 | header := make(textproto.MIMEHeader) 144 | header.Add("From", "qwe@qwe.com") 145 | header.Add("To", "qwe@qwe.com") 146 | header.Add("Subject", "Hello World!") 147 | header.Add("X-Mailjet-Campaign", "test") 148 | content := []byte("Hi there !") 149 | info := &mailjet.InfoSMTP{ 150 | From: "qwe@qwe.com", 151 | Recipients: header["To"], 152 | Header: header, 153 | Content: content, 154 | } 155 | err := mj.SendMailSMTP(info) 156 | if err != nil { 157 | fmt.Println(err) 158 | } else { 159 | fmt.Println("Success") 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /fixtures/fixtures.go: -------------------------------------------------------------------------------- 1 | // Package fixtures provid fake data so we can mock the `Client` struct in mailjet_client.go 2 | package fixtures 3 | 4 | import ( 5 | "encoding/json" 6 | "errors" 7 | "reflect" 8 | 9 | "github.com/mailjet/mailjet-apiv3-go/v4/resources" 10 | ) 11 | 12 | // Fixtures definition 13 | type Fixtures struct { 14 | data map[interface{}][]byte 15 | } 16 | 17 | // New loads fixtures in memory by iterating through its fixture method 18 | func New() *Fixtures { 19 | f := new(Fixtures) 20 | f.data = make(map[interface{}][]byte) 21 | fix := reflect.ValueOf(f) 22 | for i := 0; i < fix.NumMethod(); i++ { 23 | method := fix.Method(i) 24 | if method.Type().NumIn() == 0 && method.Type().NumOut() == 2 { 25 | values := method.Call([]reflect.Value{}) 26 | reflect.ValueOf(f.data).SetMapIndex(values[0], values[1]) 27 | } 28 | } 29 | 30 | return f 31 | } 32 | 33 | func (f *Fixtures) Read(v interface{}) error { 34 | for t, val := range f.data { 35 | if reflect.ValueOf(v).Type().String() == reflect.ValueOf(t).Type().String() { 36 | return json.Unmarshal(val, v) 37 | } 38 | } 39 | return errors.New("not found") 40 | } 41 | 42 | // User fixture info 43 | func (f *Fixtures) User() (*[]resources.User, []byte) { 44 | return &[]resources.User{}, []byte(`[{"ID":24, "Email": "passenger@mailjet.com", "LastIP": "127.0.0.1", "Username": "passenger", "MaxAllowedAPIKeys": 5}]`) 45 | } 46 | 47 | // Contact fixture info 48 | func (f *Fixtures) Contact() (*[]resources.Contact, []byte) { 49 | return &[]resources.Contact{}, []byte(`[{"ID":42, "Email": "contact@mailjet.com", "DeliveredCount": 42}]`) 50 | } 51 | 52 | // ContactList fixture info 53 | func (f *Fixtures) ContactList() (*[]resources.Contactslist, []byte) { 54 | return &[]resources.Contactslist{}, []byte(`[{"ID":84, "Address": "contact@mailjet.com", "Name": "John Doe", "SubscriberCount": 1000}]`) 55 | } 56 | 57 | // ListRecipient fixture info 58 | func (f *Fixtures) ListRecipient() (*[]resources.Listrecipient, []byte) { 59 | return &[]resources.Listrecipient{}, []byte(`[{"ID":168}]`) 60 | } 61 | 62 | // Sender fixture info 63 | func (f *Fixtures) Sender() (*[]resources.Sender, []byte) { 64 | return &[]resources.Sender{}, []byte(`[{"ID":336, "Name": "Mansa Musa", "Status": "Active", "EmailType": "transactional"}]`) 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mailjet/mailjet-apiv3-go/v4 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /http_client.go: -------------------------------------------------------------------------------- 1 | package mailjet 2 | 3 | import ( 4 | "encoding/csv" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | // HTTPClient is a wrapper around http.Client 12 | type HTTPClient struct { 13 | client *http.Client 14 | apiKeyPublic string 15 | apiKeyPrivate string 16 | headers map[string]string 17 | request *http.Request 18 | response interface{} 19 | mu sync.RWMutex 20 | } 21 | 22 | // NewHTTPClient returns a new httpClient 23 | func NewHTTPClient(apiKeyPublic, apiKeyPrivate string) *HTTPClient { 24 | return &HTTPClient{ 25 | apiKeyPublic: apiKeyPublic, 26 | apiKeyPrivate: apiKeyPrivate, 27 | client: &http.Client{}, 28 | } 29 | } 30 | 31 | // APIKeyPublic returns the public key. 32 | func (c *HTTPClient) APIKeyPublic() string { 33 | return c.apiKeyPublic 34 | } 35 | 36 | // APIKeyPrivate returns the secret key. 37 | func (c *HTTPClient) APIKeyPrivate() string { 38 | return c.apiKeyPrivate 39 | } 40 | 41 | // Client returns the underlying http client 42 | func (c *HTTPClient) Client() *http.Client { 43 | c.mu.RLock() 44 | defer c.mu.RUnlock() 45 | 46 | return c.client 47 | } 48 | 49 | // SetClient sets the underlying http client 50 | func (c *HTTPClient) SetClient(client *http.Client) { 51 | c.mu.Lock() 52 | defer c.mu.Unlock() 53 | 54 | c.client = client 55 | } 56 | 57 | // Send binds the request to the underlying http client 58 | func (c *HTTPClient) Send(req *http.Request) HTTPClientInterface { 59 | c.mu.Lock() 60 | defer c.mu.Unlock() 61 | 62 | c.request = req 63 | return c 64 | } 65 | 66 | // With binds the header to the underlying http client 67 | func (c *HTTPClient) With(headers map[string]string) HTTPClientInterface { 68 | c.mu.Lock() 69 | defer c.mu.Unlock() 70 | 71 | c.headers = headers 72 | return c 73 | } 74 | 75 | // SendMailV31 simply calls the underlying http client.Do function 76 | func (c *HTTPClient) SendMailV31(req *http.Request) (*http.Response, error) { 77 | res, err := c.Client().Do(req) 78 | return res, err 79 | } 80 | 81 | // Read binds the response to the underlying http client 82 | func (c *HTTPClient) Read(response interface{}) HTTPClientInterface { 83 | c.mu.Lock() 84 | defer c.mu.Unlock() 85 | 86 | c.response = response 87 | return c 88 | } 89 | 90 | // Call execute the HTTP call to the API 91 | func (c *HTTPClient) Call() (count, total int, err error) { 92 | c.mu.RLock() 93 | defer c.mu.RUnlock() 94 | 95 | if c.request == nil { 96 | return 0, 0, fmt.Errorf("request is nil") 97 | } 98 | 99 | defer c.reset() 100 | for key, value := range c.headers { 101 | c.request.Header.Add(key, value) 102 | } 103 | 104 | resp, err := c.doRequest(c.request) 105 | if resp != nil { 106 | defer resp.Body.Close() 107 | } 108 | 109 | if err != nil { 110 | return count, total, err 111 | } else if resp == nil { 112 | return count, total, fmt.Errorf("empty response") 113 | } 114 | 115 | if c.response != nil { 116 | if resp.Header["Content-Type"] != nil { 117 | contentType := strings.ToLower(resp.Header["Content-Type"][0]) 118 | if strings.Contains(contentType, "application/json") { 119 | return readJSONResult(resp.Body, c.response) 120 | } else if strings.Contains(contentType, "text/csv") { 121 | c.response, err = csv.NewReader(resp.Body).ReadAll() 122 | } 123 | } 124 | } 125 | 126 | return count, total, err 127 | } 128 | 129 | func (c *HTTPClient) reset() { 130 | c.headers = make(map[string]string) 131 | c.request = nil 132 | c.response = nil 133 | } 134 | -------------------------------------------------------------------------------- /http_client_decl.go: -------------------------------------------------------------------------------- 1 | package mailjet 2 | 3 | import "net/http" 4 | 5 | // HTTPClientInterface method definition 6 | type HTTPClientInterface interface { 7 | APIKeyPublic() string 8 | APIKeyPrivate() string 9 | Client() *http.Client 10 | SetClient(client *http.Client) 11 | Send(req *http.Request) HTTPClientInterface 12 | SendMailV31(req *http.Request) (*http.Response, error) 13 | With(headers map[string]string) HTTPClientInterface 14 | Read(response interface{}) HTTPClientInterface 15 | Call() (count int, total int, err error) 16 | } 17 | -------------------------------------------------------------------------------- /http_client_mock.go: -------------------------------------------------------------------------------- 1 | package mailjet 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | 9 | "github.com/mailjet/mailjet-apiv3-go/v4/fixtures" 10 | ) 11 | 12 | // HTTPClientMock definition 13 | type HTTPClientMock struct { 14 | client *http.Client 15 | apiKeyPublic string 16 | apiKeyPrivate string 17 | headers map[string]string 18 | request *http.Request 19 | response interface{} 20 | validCreds bool 21 | fx *fixtures.Fixtures 22 | CallFunc func() (int, int, error) 23 | SendMailV31Func func(req *http.Request) (*http.Response, error) 24 | } 25 | 26 | // NewhttpClientMock instanciate new httpClientMock 27 | func NewhttpClientMock(valid bool) *HTTPClientMock { 28 | return &HTTPClientMock{ 29 | apiKeyPublic: "apiKeyPublic", 30 | apiKeyPrivate: "apiKeyPrivate", 31 | client: http.DefaultClient, 32 | validCreds: valid, 33 | fx: fixtures.New(), 34 | CallFunc: func() (int, int, error) { 35 | if valid { 36 | return 1, 1, nil 37 | } 38 | return 0, 0, errors.New("Unexpected error: Unexpected server response code: 401: EOF") 39 | }, 40 | SendMailV31Func: func(req *http.Request) (*http.Response, error) { 41 | return nil, errors.New("mock send mail function not implemented yet") 42 | }, 43 | } 44 | } 45 | 46 | // APIKeyPublic returns the public key. 47 | func (c *HTTPClientMock) APIKeyPublic() string { 48 | return c.apiKeyPublic 49 | } 50 | 51 | // APIKeyPrivate returns the secret key. 52 | func (c *HTTPClientMock) APIKeyPrivate() string { 53 | return c.apiKeyPrivate 54 | } 55 | 56 | // Client returns the underlying http client 57 | func (c *HTTPClientMock) Client() *http.Client { 58 | return c.client 59 | } 60 | 61 | // SetClient allow to set the underlying http client 62 | func (c *HTTPClientMock) SetClient(client *http.Client) { 63 | c.client = client 64 | } 65 | 66 | // Send data through HTTP with the current configuration 67 | func (c *HTTPClientMock) Send(req *http.Request) HTTPClientInterface { 68 | c.request = req 69 | return c 70 | } 71 | 72 | // With lets you set the http header and returns the httpClientMock with the header modified 73 | func (c *HTTPClientMock) With(headers map[string]string) HTTPClientInterface { 74 | c.headers = headers 75 | return c 76 | } 77 | 78 | // Read allow you to bind the response received through the underlying http client 79 | func (c *HTTPClientMock) Read(response interface{}) HTTPClientInterface { 80 | err := c.fx.Read(response) 81 | if err != nil { 82 | log.Println(fmt.Errorf("c.fx.Read: %w", err)) 83 | } 84 | 85 | return c 86 | } 87 | 88 | // SendMailV31 mock function 89 | func (c *HTTPClientMock) SendMailV31(req *http.Request) (*http.Response, error) { 90 | return c.SendMailV31Func(req) 91 | } 92 | 93 | // Call the mailjet API 94 | func (c *HTTPClientMock) Call() (int, int, error) { 95 | return c.CallFunc() 96 | } 97 | -------------------------------------------------------------------------------- /mailjet_client.go: -------------------------------------------------------------------------------- 1 | // Package mailjet provides methods for interacting with the last version of the Mailjet API. 2 | // The goal of this component is to simplify the usage of the MailJet API for GO developers. 3 | // 4 | // For more details, see the full API Documentation at http://dev.mailjet.com/ 5 | package mailjet 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "encoding/json" 11 | "fmt" 12 | "io" 13 | "log" 14 | "net/http" 15 | "net/textproto" 16 | "os" 17 | "strings" 18 | ) 19 | 20 | // NewMailjetClient returns a new MailjetClient using an public apikey 21 | // and an secret apikey to be used when authenticating to API. 22 | func NewMailjetClient(apiKeyPublic, apiKeyPrivate string, baseURL ...string) *Client { 23 | httpClient := NewHTTPClient(apiKeyPublic, apiKeyPrivate) 24 | smtpClient := NewSMTPClient(apiKeyPublic, apiKeyPrivate) 25 | 26 | client := &Client{ 27 | httpClient: httpClient, 28 | smtpClient: smtpClient, 29 | apiBase: apiBase, 30 | } 31 | 32 | if len(baseURL) > 0 { 33 | client.apiBase = baseURL[0] 34 | } 35 | return client 36 | } 37 | 38 | // NewClient function 39 | func NewClient(httpCl HTTPClientInterface, smtpCl SMTPClientInterface, baseURL ...string) *Client { 40 | 41 | client := &Client{ 42 | httpClient: httpCl, 43 | smtpClient: smtpCl, 44 | apiBase: apiBase, 45 | } 46 | 47 | if len(baseURL) > 0 { 48 | client.apiBase = baseURL[0] 49 | } 50 | return client 51 | } 52 | 53 | // SetBaseURL sets the base URL 54 | func (c *Client) SetBaseURL(baseURL string) { 55 | c.apiBase = baseURL 56 | } 57 | 58 | // APIKeyPublic returns the public key. 59 | func (c *Client) APIKeyPublic() string { 60 | return c.httpClient.APIKeyPublic() 61 | } 62 | 63 | // APIKeyPrivate returns the secret key. 64 | func (c *Client) APIKeyPrivate() string { 65 | return c.httpClient.APIKeyPrivate() 66 | 67 | } 68 | 69 | // SetURL function to set the base url of the wrapper instance 70 | func (c *Client) SetURL(baseURL string) { 71 | c.Lock() 72 | defer c.Unlock() 73 | c.apiBase = baseURL 74 | } 75 | 76 | // Client returns the underlying http client 77 | func (c *Client) Client() *http.Client { 78 | return c.httpClient.Client() 79 | } 80 | 81 | // SetClient allows to customize http client. 82 | func (c *Client) SetClient(client *http.Client) { 83 | c.Lock() 84 | defer c.Unlock() 85 | c.httpClient.SetClient(client) 86 | } 87 | 88 | // Filter applies a filter with the defined key and value. 89 | func Filter(key, value string) RequestOptions { 90 | return func(req *http.Request) { 91 | q := req.URL.Query() 92 | q.Add(key, value) 93 | req.URL.RawQuery = strings.Replace(q.Encode(), "%2B", "+", 1) 94 | } 95 | } 96 | 97 | // WithContext sets the request context 98 | func WithContext(ctx context.Context) RequestOptions { 99 | return func(req *http.Request) { 100 | *req = *(req.WithContext(ctx)) 101 | } 102 | } 103 | 104 | // SortOrder defines the order of the result. 105 | type SortOrder int 106 | 107 | // These are the two possible order. 108 | const ( 109 | SortDesc = SortOrder(iota) 110 | SortAsc 111 | ) 112 | 113 | var debugOut io.Writer = os.Stderr 114 | 115 | // SetDebugOutput sets the output destination for the debug. 116 | func SetDebugOutput(w io.Writer) { 117 | debugOut = w 118 | log.SetOutput(w) 119 | } 120 | 121 | // Sort applies the Sort filter to the request. 122 | func Sort(value string, order SortOrder) RequestOptions { 123 | if order == SortDesc { 124 | value += "+DESC" 125 | } 126 | return Filter("Sort", value) 127 | } 128 | 129 | // List issues a GET to list the specified resource 130 | // and stores the result in the value pointed to by res. 131 | // Filters can be add via functional options. 132 | func (c *Client) List(resource string, resp interface{}, options ...RequestOptions) (count, total int, err error) { 133 | url := buildURL(c.apiBase, &Request{Resource: resource}) 134 | req, err := createRequest("GET", url, nil, nil, options...) 135 | if err != nil { 136 | return count, total, err 137 | } 138 | 139 | c.Lock() 140 | defer c.Unlock() 141 | return c.httpClient.Send(req).Read(resp).Call() 142 | } 143 | 144 | // Get issues a GET to view a resource specifying an id 145 | // and stores the result in the value pointed to by res. 146 | // Filters can be add via functional options. 147 | // Without an specified ID in MailjetRequest, it is the same as List. 148 | func (c *Client) Get(mr *Request, resp interface{}, options ...RequestOptions) (err error) { 149 | url := buildURL(c.apiBase, mr) 150 | req, err := createRequest("GET", url, nil, nil, options...) 151 | if err != nil { 152 | return err 153 | } 154 | 155 | c.Lock() 156 | defer c.Unlock() 157 | _, _, err = c.httpClient.Send(req).Read(resp).Call() 158 | return err 159 | } 160 | 161 | // Post issues a POST to create a new resource 162 | // and stores the result in the value pointed to by res. 163 | // Filters can be add via functional options. 164 | func (c *Client) Post(fmr *FullRequest, resp interface{}, options ...RequestOptions) (err error) { 165 | url := buildURL(c.apiBase, fmr.Info) 166 | req, err := createRequest("POST", url, fmr.Payload, nil, options...) 167 | if err != nil { 168 | return err 169 | } 170 | 171 | headers := map[string]string{"Content-Type": "application/json"} 172 | c.Lock() 173 | defer c.Unlock() 174 | _, _, err = c.httpClient.Send(req).With(headers).Read(resp).Call() 175 | return err 176 | } 177 | 178 | // Put is used to update a resource. 179 | // Fields to be updated must be specified by the string array onlyFields. 180 | // If onlyFields is nil, all fields except these with the tag read_only, are updated. 181 | // Filters can be add via functional options. 182 | func (c *Client) Put(fmr *FullRequest, onlyFields []string, options ...RequestOptions) (err error) { 183 | url := buildURL(c.apiBase, fmr.Info) 184 | req, err := createRequest("PUT", url, fmr.Payload, onlyFields, options...) 185 | if err != nil { 186 | return err 187 | } 188 | 189 | headers := map[string]string{"Content-Type": "application/json"} 190 | c.Lock() 191 | defer c.Unlock() 192 | _, _, err = c.httpClient.Send(req).With(headers).Call() 193 | return err 194 | } 195 | 196 | // Delete is used to delete a resource. 197 | func (c *Client) Delete(mr *Request) (err error) { 198 | url := buildURL(c.apiBase, mr) 199 | req, err := createRequest("DELETE", url, nil, nil) 200 | if err != nil { 201 | return err 202 | } 203 | 204 | c.Lock() 205 | defer c.Unlock() 206 | _, _, err = c.httpClient.Send(req).Call() 207 | return err 208 | } 209 | 210 | // SendMail send mail via API. 211 | func (c *Client) SendMail(data *InfoSendMail, options ...RequestOptions) (res *SentResult, err error) { 212 | url := c.apiBase + "/send/message" 213 | req, err := createRequest("POST", url, data, nil, options...) 214 | if err != nil { 215 | return res, err 216 | } 217 | 218 | headers := map[string]string{"Content-Type": "application/json"} 219 | 220 | c.Lock() 221 | defer c.Unlock() 222 | _, _, err = c.httpClient.Send(req).With(headers).Read(&res).Call() 223 | return res, err 224 | } 225 | 226 | // SendMailSMTP send mail via SMTP. 227 | func (c *Client) SendMailSMTP(info *InfoSMTP) error { 228 | return c.smtpClient.SendMail( 229 | info.From, 230 | info.Recipients, 231 | buildMessage(info.Header, info.Content)) 232 | } 233 | 234 | func buildMessage(header textproto.MIMEHeader, content []byte) []byte { 235 | buff := bytes.NewBuffer(nil) 236 | for key, values := range header { 237 | buff.WriteString(fmt.Sprintf("%s: %s\r\n", key, strings.Join(values, ", "))) 238 | } 239 | buff.WriteString("\r\n") 240 | buff.Write(content) 241 | 242 | return buff.Bytes() 243 | } 244 | 245 | // SendMailV31 sends a mail to the send API v3.1 246 | func (c *Client) SendMailV31(data *MessagesV31, options ...RequestOptions) (*ResultsV31, error) { 247 | url := c.apiBase + ".1/send" 248 | req, err := createRequest("POST", url, data, nil, options...) 249 | if err != nil { 250 | return nil, err 251 | } 252 | 253 | req.Header.Set("Content-Type", "application/json") 254 | req.SetBasicAuth(c.APIKeyPublic(), c.APIKeyPrivate()) 255 | 256 | r, err := c.httpClient.SendMailV31(req) 257 | if err != nil { 258 | return nil, err 259 | } 260 | defer r.Body.Close() 261 | 262 | decoder := json.NewDecoder(r.Body) 263 | 264 | switch r.StatusCode { 265 | case http.StatusOK: 266 | 267 | var res ResultsV31 268 | if err := decoder.Decode(&res); err != nil { 269 | return nil, err 270 | } 271 | return &res, nil 272 | 273 | case http.StatusBadRequest, http.StatusForbidden: 274 | 275 | var apiFeedbackErr APIFeedbackErrorsV31 276 | if err := decoder.Decode(&apiFeedbackErr); err != nil { 277 | return nil, err 278 | } 279 | return nil, &apiFeedbackErr 280 | 281 | default: 282 | 283 | var errInfo ErrorInfoV31 284 | if err := decoder.Decode(&errInfo); err != nil { 285 | return nil, err 286 | } 287 | return nil, &errInfo 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /mailjet_client_decl.go: -------------------------------------------------------------------------------- 1 | // Package mailjet provides methods for interacting with the last version of the Mailjet API. 2 | // The goal of this component is to simplify the usage of the MailJet API for GO developers. 3 | // 4 | // For more details, see the full API Documentation at http://dev.mailjet.com/ 5 | package mailjet 6 | 7 | import "net/http" 8 | 9 | // ClientInterface defines all Client functions. 10 | type ClientInterface interface { 11 | APIKeyPublic() string 12 | APIKeyPrivate() string 13 | Client() *http.Client 14 | SetClient(client *http.Client) 15 | List(resource string, resp interface{}, options ...RequestOptions) (count, total int, err error) 16 | Get(mr *Request, resp interface{}, options ...RequestOptions) error 17 | Post(fmr *FullRequest, resp interface{}, options ...RequestOptions) error 18 | Put(fmr *FullRequest, onlyFields []string, options ...RequestOptions) error 19 | Delete(mr *Request, options ...RequestOptions) error 20 | SendMail(data *InfoSendMail) (*SentResult, error) 21 | SendMailSMTP(info *InfoSMTP) (err error) 22 | } 23 | 24 | // ClientInterfaceV31 defines the Client functions, including SendMailV31 25 | type ClientInterfaceV31 interface { 26 | ClientInterface 27 | SendMailV31(data *MessagesV31) (*ResultsV31, error) 28 | } 29 | -------------------------------------------------------------------------------- /mailjet_client_test.go: -------------------------------------------------------------------------------- 1 | package mailjet_test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "math/rand" 9 | "net/http" 10 | "net/http/httptest" 11 | "reflect" 12 | "testing" 13 | "time" 14 | 15 | "github.com/mailjet/mailjet-apiv3-go/v4" 16 | "github.com/mailjet/mailjet-apiv3-go/v4/resources" 17 | ) 18 | 19 | var ( 20 | letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 21 | // defaultMessages is the default message passed to the server when an email 22 | // is sent. 23 | defaultMessages = mailjet.MessagesV31{ 24 | Info: []mailjet.InfoMessagesV31{ 25 | { 26 | From: &mailjet.RecipientV31{ 27 | Email: "passenger@mailjet.com", 28 | Name: "passenger", 29 | }, 30 | To: &mailjet.RecipientsV31{ 31 | mailjet.RecipientV31{ 32 | Email: "recipient@company.com", 33 | }, 34 | }, 35 | Subject: "Send API testing", 36 | TextPart: "SendMail is working!", 37 | }, 38 | }, 39 | } 40 | ) 41 | 42 | var ( 43 | mux *http.ServeMux 44 | server *httptest.Server 45 | client *mailjet.Client 46 | ) 47 | 48 | func fakeServer() func() { 49 | mux = http.NewServeMux() 50 | server = httptest.NewServer(mux) 51 | client = mailjet.NewMailjetClient("apiKeyPublic", "apiKeyPrivate", server.URL+"/v3") 52 | 53 | return func() { 54 | server.Close() 55 | } 56 | } 57 | 58 | func handle(path, response string) { 59 | mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { 60 | w.Header().Set("Content-Type", "application/json") 61 | w.WriteHeader(http.StatusOK) 62 | fmt.Fprint(w, response) 63 | }) 64 | } 65 | 66 | func randSeq(n int) string { 67 | rand.Seed(time.Now().UnixNano()) 68 | b := make([]rune, n) 69 | for i := range b { 70 | //nolint:gosec // G404 crypto random is not required here 71 | b[i] = letters[rand.Intn(len(letters))] 72 | } 73 | return string(b) 74 | } 75 | 76 | // NewMockedMailjetClient returns an instance of `Client` with mocked http and smtp clients injected 77 | func newMockedMailjetClient() *mailjet.Client { 78 | httpClientMocked := mailjet.NewhttpClientMock(true) 79 | smtpClientMocked := mailjet.NewSMTPClientMock(true) 80 | client := mailjet.NewClient(httpClientMocked, smtpClientMocked) 81 | 82 | return client 83 | } 84 | 85 | func TestCreateListrecipient(t *testing.T) { 86 | teardown := fakeServer() 87 | defer teardown() 88 | 89 | mux.HandleFunc("/v3/REST/listrecipient", func(w http.ResponseWriter, r *http.Request) { 90 | if r.Method != "POST" { 91 | w.WriteHeader(http.StatusNotFound) 92 | return 93 | } 94 | 95 | b, err := ioutil.ReadAll(r.Body) 96 | if err != nil { 97 | t.Fatal("Unexpected error:", err) 98 | } 99 | 100 | body := make(map[string]interface{}) 101 | if err = json.Unmarshal(b, &body); err != nil { 102 | t.Fatal("Invalid body:", err) 103 | } 104 | 105 | _, id := body["ContactID"] 106 | _, alt := body["ContactALT"] 107 | _, listID := body["ListID"] 108 | if !id && !alt || !listID { 109 | w.WriteHeader(http.StatusBadRequest) 110 | fmt.Fprint(w, `{"ErrorMessage": "Missing required parameters"}`) 111 | } 112 | }) 113 | 114 | t.Run("successfully create list", func(t *testing.T) { 115 | req := &mailjet.Request{ 116 | Resource: "listrecipient", 117 | } 118 | fullRequest := &mailjet.FullRequest{ 119 | Info: req, 120 | Payload: resources.Listrecipient{ 121 | IsUnsubscribed: true, 122 | ContactID: 124409882, 123 | ContactALT: "joe.doe@mailjet.com", 124 | ListID: 32964, 125 | }, 126 | } 127 | 128 | var resp []resources.Listrecipient 129 | err := client.Post(fullRequest, &resp) 130 | if err != nil { 131 | t.Fatal(err) 132 | } 133 | }) 134 | 135 | t.Run("failure when required parameters missing", func(t *testing.T) { 136 | req := &mailjet.Request{ 137 | Resource: "listrecipient", 138 | } 139 | fullRequest := &mailjet.FullRequest{ 140 | Info: req, 141 | Payload: resources.Listrecipient{ 142 | IsUnsubscribed: true, 143 | ListID: 32964, 144 | }, 145 | } 146 | 147 | var resp []resources.Listrecipient 148 | err := client.Post(fullRequest, &resp) 149 | if err == nil { 150 | t.Fatal("Expected error") 151 | } 152 | }) 153 | } 154 | 155 | func TestMessage(t *testing.T) { 156 | teardown := fakeServer() 157 | defer teardown() 158 | 159 | handle("/v3/REST/message", ` 160 | { 161 | "Count": 1, 162 | "Data": [ 163 | { 164 | "ArrivedAt": "2020-10-08T06:36:35Z", 165 | "AttachmentCount": 0, 166 | "AttemptCount": 0, 167 | "CampaignID": 426400, 168 | "ContactAlt": "", 169 | "ContactID": 124409882, 170 | "Delay": 0, 171 | "DestinationID": 124879, 172 | "FilterTime": 0, 173 | "ID": 94294117474376580, 174 | "IsClickTracked": false, 175 | "IsHTMLPartIncluded": false, 176 | "IsOpenTracked": true, 177 | "IsTextPartIncluded": false, 178 | "IsUnsubTracked": false, 179 | "MessageSize": 810, 180 | "SenderID": 52387, 181 | "SpamassassinScore": 0, 182 | "SpamassRules": "", 183 | "StatePermanent": false, 184 | "Status": "sent", 185 | "Subject": "", 186 | "UUID": "6f66806a-c4d6-4a33-99dc-bedbc7c4217f" 187 | } 188 | ], 189 | "Total": 1 190 | } 191 | `) 192 | 193 | request := &mailjet.Request{ 194 | Resource: "message", 195 | } 196 | 197 | var data []resources.Message 198 | 199 | err := client.Get(request, &data) 200 | if err != nil { 201 | t.Fatal(err) 202 | } 203 | } 204 | 205 | func TestMessageinformation(t *testing.T) { 206 | t.Run("empty SpamAssassinRules", func(t *testing.T) { 207 | teardown := fakeServer() 208 | defer teardown() 209 | 210 | handle("/v3/REST/messageinformation", ` 211 | { 212 | "Count": 1, 213 | "Data": [ 214 | { 215 | "CampaignID": 0, 216 | "ClickTrackedCount": 0, 217 | "ContactID": 124409882, 218 | "CreatedAt": "2020-10-09T06:07:56Z", 219 | "ID": 288230380871887400, 220 | "MessageSize": 434, 221 | "OpenTrackedCount": 0, 222 | "QueuedCount": 0, 223 | "SendEndAt": "2020-10-09T06:07:56Z", 224 | "SentCount": 1602223677, 225 | "SpamAssassinRules": { 226 | "ALT": "", 227 | "ID": -1 228 | }, 229 | "SpamAssassinScore": 0 230 | } 231 | ], 232 | "Total": 1 233 | } 234 | `) 235 | 236 | request := &mailjet.Request{ 237 | Resource: "messageinformation", 238 | } 239 | 240 | var data []resources.Messageinformation 241 | 242 | err := client.Get(request, &data) 243 | if err != nil { 244 | t.Fatal(err) 245 | } 246 | }) 247 | 248 | t.Run("not empty SpamAssassinRules", func(t *testing.T) { 249 | teardown := fakeServer() 250 | defer teardown() 251 | 252 | handle("/v3/REST/messageinformation", ` 253 | { 254 | "Count": 1, 255 | "Data": [ 256 | { 257 | "CampaignID": 0, 258 | "ClickTrackedCount": 0, 259 | "ContactID": 124409882, 260 | "CreatedAt": "2020-10-09T06:07:56Z", 261 | "ID": 288230380871887400, 262 | "MessageSize": 434, 263 | "OpenTrackedCount": 0, 264 | "QueuedCount": 0, 265 | "SendEndAt": "2020-10-09T06:07:56Z", 266 | "SentCount": 1602223677, 267 | "SpamAssassinRules": { 268 | "ALT": "", 269 | "ID": -1, 270 | "Items": [ 271 | { 272 | "ALT": "MISSING_DATE", 273 | "HitCount": 81115, 274 | "ID": 1, 275 | "Name": "MISSING_DATE", 276 | "Score": 2.739 277 | }, 278 | { 279 | "ALT": "MISSING_HEADERS", 280 | "HitCount": 48433743, 281 | "ID": 2, 282 | "Name": "MISSING_HEADERS", 283 | "Score": 0.915 284 | } 285 | ] 286 | }, 287 | "SpamAssassinScore": 0 288 | } 289 | ], 290 | "Total": 1 291 | } 292 | `) 293 | 294 | request := &mailjet.Request{ 295 | Resource: "messageinformation", 296 | } 297 | 298 | var data []resources.Messageinformation 299 | 300 | err := client.Get(request, &data) 301 | if err != nil { 302 | t.Fatal(err) 303 | } 304 | }) 305 | } 306 | 307 | func TestUnitList(t *testing.T) { 308 | m := newMockedMailjetClient() 309 | 310 | var data []resources.Sender 311 | count, _, err := m.List("sender", &data) 312 | if err != nil { 313 | t.Fatal("Unexpected error:", err) 314 | } 315 | if count < 1 { 316 | t.Fatal("At least one sender expected !") 317 | } 318 | 319 | httpClientMocked := mailjet.NewhttpClientMock(false) 320 | smtpClientMocked := mailjet.NewSMTPClientMock(true) 321 | cl := mailjet.NewClient(httpClientMocked, smtpClientMocked, "custom") 322 | 323 | _, _, err = cl.List("sender", &data) 324 | if err == nil { 325 | t.Fail() 326 | } 327 | } 328 | 329 | func TestUnitGet(t *testing.T) { 330 | m := newMockedMailjetClient() 331 | 332 | var data []resources.User 333 | resource := "user" 334 | count, _, err := m.List(resource, &data) 335 | if err != nil { 336 | t.Fatal("Unexpected error:", err) 337 | } 338 | if count < 1 { 339 | t.Fatal("At least one user expected !") 340 | } 341 | if data == nil { 342 | t.Fatal("Empty result") 343 | } 344 | 345 | mr := &mailjet.Request{Resource: resource, ID: data[0].ID} 346 | data = make([]resources.User, 0) 347 | err = m.Get(mr, &data) 348 | if err != nil { 349 | t.Fatal("Unexpected error:", err) 350 | } 351 | } 352 | 353 | func TestUnitPost(t *testing.T) { 354 | m := newMockedMailjetClient() 355 | 356 | var data []resources.Contact 357 | rstr := randSeq(10) 358 | t.Logf("Create new contact: \"%s@mailjet.com\"\n", rstr) 359 | fmr := &mailjet.FullRequest{ 360 | Info: &mailjet.Request{Resource: "contact"}, 361 | Payload: &resources.Contact{Name: rstr, Email: rstr + "@mailjet.com"}, 362 | } 363 | err := m.Post(fmr, &data) 364 | if err != nil { 365 | t.Fatal("Unexpected error:", err) 366 | } 367 | if data == nil { 368 | t.Fatal("Empty result") 369 | } 370 | t.Logf("Created contact: %+v\n", data[0]) 371 | } 372 | 373 | func TestUnitPut(t *testing.T) { 374 | m := newMockedMailjetClient() 375 | 376 | var data []resources.Contactslist 377 | resource := "contactslist" 378 | count, _, err := m.List(resource, &data) 379 | if err != nil { 380 | t.Fatal("Unexpected error:", err) 381 | } 382 | if count < 1 { 383 | t.Fatal("At least one contact list expected on test account!") 384 | } 385 | if data == nil { 386 | t.Fatal("Empty result") 387 | } 388 | 389 | rstr := randSeq(10) 390 | t.Logf("Update name of the contact list: %s -> %s\n", data[0].Name, rstr) 391 | data[0].Name = randSeq(10) 392 | fmr := &mailjet.FullRequest{ 393 | Info: &mailjet.Request{Resource: resource, AltID: data[0].Address}, 394 | Payload: data[0], 395 | } 396 | err = m.Put(fmr, []string{"Name"}) 397 | if err != nil { 398 | t.Fatal("Unexpected error:", err) 399 | } 400 | } 401 | 402 | func TestUnitDelete(t *testing.T) { 403 | m := newMockedMailjetClient() 404 | 405 | var data []resources.Listrecipient 406 | resource := "listrecipient" 407 | count, _, err := m.List(resource, &data) 408 | if err != nil { 409 | t.Fatal("Unexpected error:", err) 410 | } 411 | if count < 1 { 412 | return 413 | } 414 | if data == nil { 415 | t.Fatal("Empty result") 416 | } 417 | 418 | mr := &mailjet.Request{ 419 | ID: data[0].ID, 420 | Resource: resource, 421 | } 422 | err = m.Delete(mr) 423 | if err != nil { 424 | t.Error(err) 425 | } 426 | } 427 | 428 | func TestUnitSendMail(t *testing.T) { 429 | m := newMockedMailjetClient() 430 | 431 | var data []resources.Sender 432 | count, _, err := m.List("sender", &data) 433 | if err != nil { 434 | t.Fatal("Unexpected error:", err) 435 | } 436 | if count < 1 || data == nil { 437 | t.Fatal("At least one sender expected in the test account!") 438 | } 439 | 440 | param := &mailjet.InfoSendMail{ 441 | FromEmail: data[0].Email, 442 | FromName: data[0].Name, 443 | Recipients: []mailjet.Recipient{ 444 | { 445 | Email: data[0].Email, 446 | }, 447 | }, 448 | Subject: "Send API testing", 449 | TextPart: "SendMail is working !", 450 | } 451 | _, err = m.SendMail(param) 452 | if err != nil { 453 | t.Fatal("Unexpected error:", err) 454 | } 455 | } 456 | 457 | func TestSendMailV31(t *testing.T) { 458 | tests := []struct { 459 | name string 460 | messages mailjet.MessagesV31 461 | mockResponse interface{} 462 | mockStatusCode int 463 | wantResponse *mailjet.ResultsV31 464 | wantErr interface{} 465 | }{ 466 | { 467 | name: "sending successful", 468 | messages: defaultMessages, 469 | mockResponse: mailjet.ResultsV31{ 470 | ResultsV31: []mailjet.ResultV31{ 471 | { 472 | To: []mailjet.GeneratedMessageV31{ 473 | { 474 | Email: "recipient@company.com", 475 | MessageUUID: "ac93d194-1432-4e25-a215-2cb450d4a818", 476 | MessageID: 87, 477 | }, 478 | }, 479 | }, 480 | }, 481 | }, 482 | mockStatusCode: 200, 483 | wantResponse: &mailjet.ResultsV31{ 484 | ResultsV31: []mailjet.ResultV31{ 485 | { 486 | To: []mailjet.GeneratedMessageV31{ 487 | { 488 | Email: "recipient@company.com", 489 | MessageUUID: "ac93d194-1432-4e25-a215-2cb450d4a818", 490 | MessageID: 87, 491 | }, 492 | }, 493 | }, 494 | }, 495 | }, 496 | wantErr: nil, 497 | }, 498 | { 499 | name: "authorization failed", 500 | messages: defaultMessages, 501 | mockResponse: mailjet.ErrorInfoV31{ 502 | Identifier: "ac93d194-1432-4e25-a215-2cb450d4a818", 503 | StatusCode: 401, 504 | Message: "API key authentication/authorization failure. You may be unauthorized to access the API or your API key may be expired. Visit API keys management section to check your keys.", 505 | }, 506 | mockStatusCode: 401, 507 | wantResponse: nil, 508 | wantErr: &mailjet.ErrorInfoV31{ 509 | Identifier: "ac93d194-1432-4e25-a215-2cb450d4a818", 510 | StatusCode: 401, 511 | Message: "API key authentication/authorization failure. You may be unauthorized to access the API or your API key may be expired. Visit API keys management section to check your keys.", 512 | }, 513 | }, 514 | { 515 | name: "simple errors in request", 516 | messages: messagesWithAdvancedErrorHandling(), 517 | mockResponse: mailjet.APIFeedbackErrorsV31{ 518 | Messages: []mailjet.APIFeedbackErrorV31{ 519 | { 520 | Errors: []mailjet.APIErrorDetailsV31{ 521 | { 522 | ErrorCode: "mj-0013", 523 | ErrorIdentifier: "ac93d194-1432-4e25-a215-2cb450d4a818", 524 | StatusCode: 400, 525 | // It is not, but let's suppose it is. 526 | ErrorMessage: "\"recipient@company.com\" is an invalid email address.", 527 | ErrorRelatedTo: []string{"To[0].Email"}, 528 | }, 529 | }, 530 | }, 531 | }, 532 | }, 533 | mockStatusCode: 400, 534 | wantResponse: nil, 535 | wantErr: &mailjet.APIFeedbackErrorsV31{ 536 | Messages: []mailjet.APIFeedbackErrorV31{ 537 | { 538 | Errors: []mailjet.APIErrorDetailsV31{ 539 | { 540 | ErrorCode: "mj-0013", 541 | ErrorIdentifier: "ac93d194-1432-4e25-a215-2cb450d4a818", 542 | StatusCode: 400, 543 | // It is not, but let's suppose it is. 544 | ErrorMessage: "\"recipient@company.com\" is an invalid email address.", 545 | ErrorRelatedTo: []string{"To[0].Email"}, 546 | }, 547 | }, 548 | }, 549 | }, 550 | }, 551 | }, 552 | { 553 | name: "advanced error handling failed", 554 | messages: messagesWithAdvancedErrorHandling(), 555 | mockResponse: mailjet.APIFeedbackErrorsV31{ 556 | Messages: []mailjet.APIFeedbackErrorV31{ 557 | { 558 | Errors: []mailjet.APIErrorDetailsV31{ 559 | { 560 | ErrorCode: "send-0008", 561 | ErrorIdentifier: "ac93d194-1432-4e25-a215-2cb450d4a818", 562 | StatusCode: 403, 563 | ErrorMessage: "\"passenger@mailjet.com\" is not an authorized sender email address for your account.", 564 | ErrorRelatedTo: []string{"From"}, 565 | }, 566 | }, 567 | }, 568 | }, 569 | }, 570 | mockStatusCode: 403, 571 | wantResponse: nil, 572 | wantErr: &mailjet.APIFeedbackErrorsV31{ 573 | Messages: []mailjet.APIFeedbackErrorV31{ 574 | { 575 | Errors: []mailjet.APIErrorDetailsV31{ 576 | { 577 | ErrorCode: "send-0008", 578 | ErrorIdentifier: "ac93d194-1432-4e25-a215-2cb450d4a818", 579 | StatusCode: 403, 580 | ErrorMessage: "\"passenger@mailjet.com\" is not an authorized sender email address for your account.", 581 | ErrorRelatedTo: []string{"From"}, 582 | }, 583 | }, 584 | }, 585 | }, 586 | }, 587 | }, 588 | } 589 | 590 | for _, test := range tests { 591 | t.Run(test.name, func(t *testing.T) { 592 | // TODO(Go1.22+): remove: 593 | messages := test.messages // https://go.dev/wiki/CommonMistakes 594 | 595 | httpClientMocked := mailjet.NewhttpClientMock(true) 596 | httpClientMocked.SendMailV31Func = func(req *http.Request) (*http.Response, error) { 597 | if req.Header.Get("Content-Type") != "application/json" { 598 | t.Errorf("Wanted request content-type header to be: application/json, got: %s", req.Header.Get("Content-Type")) 599 | } 600 | 601 | user, pass, ok := req.BasicAuth() 602 | if !ok || user != httpClientMocked.APIKeyPublic() || pass != httpClientMocked.APIKeyPrivate() { 603 | t.Errorf("Wanted HTTP basic auth to be: %s/%s, got %s/%s", user, pass, 604 | httpClientMocked.APIKeyPublic(), httpClientMocked.APIKeyPrivate()) 605 | } 606 | 607 | var msgs mailjet.MessagesV31 608 | err := json.NewDecoder(req.Body).Decode(&msgs) 609 | if err != nil { 610 | t.Fatalf("Could not decode request body and read message information: %v", err) 611 | } 612 | 613 | if !reflect.DeepEqual(test.messages, msgs) { 614 | t.Errorf("Wanted request messages: %+v, got: %+v", test.messages, msgs) 615 | } 616 | 617 | rawBytes, _ := json.Marshal(test.mockResponse) 618 | return &http.Response{ 619 | Body: ioutil.NopCloser(bytes.NewBuffer(rawBytes)), 620 | StatusCode: test.mockStatusCode, 621 | }, nil 622 | } 623 | 624 | m := mailjet.NewClient(httpClientMocked, mailjet.NewSMTPClientMock(true)) 625 | 626 | res, err := m.SendMailV31(&messages) 627 | if !reflect.DeepEqual(err, test.wantErr) { 628 | t.Fatalf("Wanted error: %+v, got: %+v", err, test.wantErr) 629 | } 630 | 631 | if !reflect.DeepEqual(test.wantResponse, res) { 632 | t.Fatalf("Wanted response: %+v, got %+v", test.wantResponse, res) 633 | } 634 | }) 635 | } 636 | } 637 | 638 | func messagesWithAdvancedErrorHandling() mailjet.MessagesV31 { 639 | withAdvancedErrorChecking := defaultMessages 640 | withAdvancedErrorChecking.AdvanceErrorHandling = true 641 | return withAdvancedErrorChecking 642 | } 643 | -------------------------------------------------------------------------------- /mailjet_resources.go: -------------------------------------------------------------------------------- 1 | package mailjet 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/textproto" 7 | "sync" 8 | 9 | "encoding/json" 10 | ) 11 | 12 | /* 13 | ** API structures 14 | */ 15 | 16 | // Client bundles data needed by a large number 17 | // of methods in order to interact with the Mailjet API. 18 | type Client struct { 19 | apiBase string 20 | httpClient HTTPClientInterface 21 | smtpClient SMTPClientInterface 22 | sync.Mutex 23 | } 24 | 25 | // Request bundles data needed to build the URL. 26 | type Request struct { 27 | Resource string 28 | ID int64 29 | AltID string 30 | Action string 31 | ActionID int64 32 | } 33 | 34 | // DataRequest bundles data needed to build the DATA URL. 35 | type DataRequest struct { 36 | SourceType string 37 | SourceTypeID int64 38 | DataType string 39 | MimeType string 40 | DataTypeID int64 41 | LastID bool 42 | } 43 | 44 | // FullRequest is the same as a Request but with a payload. 45 | type FullRequest struct { 46 | Info *Request 47 | Payload interface{} 48 | } 49 | 50 | // FullDataRequest is the same as a DataRequest but with a payload. 51 | type FullDataRequest struct { 52 | Info *DataRequest 53 | Payload interface{} 54 | } 55 | 56 | // RequestOptions are functional options that modify the specified request. 57 | type RequestOptions func(*http.Request) 58 | 59 | // RequestResult is the JSON result sent by the API. 60 | type RequestResult struct { 61 | Count int 62 | Data interface{} 63 | Total int 64 | } 65 | 66 | // RequestError is the error returned by the API. 67 | type RequestError struct { 68 | ErrorInfo string 69 | ErrorMessage string 70 | StatusCode int 71 | } 72 | 73 | func (e RequestError) Error() string { 74 | return fmt.Sprintf("Unexpected server response code: %d: %s (%s)", e.StatusCode, e.ErrorMessage, e.ErrorInfo) 75 | } 76 | 77 | // RequestErrorV31 is the error returned by the API. 78 | type RequestErrorV31 struct { 79 | ErrorInfo string 80 | ErrorMessage string 81 | StatusCode int 82 | ErrorIdentifier string 83 | } 84 | 85 | /* 86 | ** Send API structures 87 | */ 88 | 89 | // InfoSendMail bundles data used by the Send API. 90 | type InfoSendMail struct { 91 | FromEmail string 92 | FromName string 93 | Sender string `json:",omitempty"` 94 | Recipients []Recipient `json:",omitempty"` 95 | To string `json:",omitempty"` 96 | Cc string `json:",omitempty"` 97 | Bcc string `json:",omitempty"` 98 | Subject string 99 | TextPart string `json:"Text-part,omitempty"` 100 | HTMLPart string `json:"Html-part,omitempty"` 101 | Attachments []Attachment `json:",omitempty"` 102 | InlineAttachments []Attachment `json:"Inline_attachments,omitempty"` 103 | MjPrio int `json:"Mj-prio,omitempty"` 104 | MjCampaign string `json:"Mj-campaign,omitempty"` 105 | MjDeduplicateCampaign bool `json:"Mj-deduplicatecampaign,omitempty"` 106 | MjCustomID string `json:"Mj-CustomID,omitempty"` 107 | MjTemplateID string `json:"Mj-TemplateID,omitempty"` 108 | MjTemplateErrorReporting string `json:"MJ-TemplateErrorReporting,omitempty"` 109 | MjTemplateLanguage string `json:"Mj-TemplateLanguage,omitempty"` 110 | MjTemplateErrorDeliver string `json:"MJ-TemplateErrorDeliver,omitempty"` 111 | MjEventPayLoad string `json:"Mj-EventPayLoad,omitempty"` 112 | Headers map[string]string `json:",omitempty"` 113 | Vars interface{} `json:",omitempty"` 114 | Messages []InfoSendMail `json:",omitempty"` 115 | } 116 | 117 | // Recipient bundles data on the target of the mail. 118 | type Recipient struct { 119 | Email string 120 | Name string 121 | Vars interface{} `json:",omitempty"` 122 | } 123 | 124 | // Attachment bundles data on the file attached to the mail. 125 | type Attachment struct { 126 | ContentType string `json:"Content-Type"` 127 | Content string 128 | Filename string 129 | } 130 | 131 | // SentResult is the JSON result sent by the Send API. 132 | type SentResult struct { 133 | Sent []struct { 134 | Email string 135 | MessageID int64 136 | } 137 | } 138 | 139 | /* 140 | ** SMTP mail sending structures 141 | */ 142 | 143 | // InfoSMTP contains mandatory informations to send a mail via SMTP. 144 | type InfoSMTP struct { 145 | From string 146 | Recipients []string 147 | Header textproto.MIMEHeader 148 | Content []byte 149 | } 150 | 151 | /* 152 | ** Send API v3.1 structures 153 | */ 154 | 155 | // MessagesV31 definition 156 | type MessagesV31 struct { 157 | Info []InfoMessagesV31 `json:"Messages,omitempty"` 158 | AdvanceErrorHandling bool `json:"AdvanceErrorHandling,omitempty"` 159 | SandBoxMode bool `json:",omitempty"` 160 | } 161 | 162 | // InfoMessagesV31 represents the payload input taken by send API v3.1 163 | type InfoMessagesV31 struct { 164 | From *RecipientV31 `json:",omitempty"` 165 | ReplyTo *RecipientV31 `json:",omitempty"` 166 | Sender *RecipientV31 `json:",omitempty"` 167 | To *RecipientsV31 `json:",omitempty"` 168 | Cc *RecipientsV31 `json:",omitempty"` 169 | Bcc *RecipientsV31 `json:",omitempty"` 170 | Attachments *AttachmentsV31 `json:",omitempty"` 171 | InlinedAttachments *InlinedAttachmentsV31 `json:",omitempty"` 172 | Subject string `json:",omitempty"` 173 | TextPart string `json:",omitempty"` 174 | HTMLPart string `json:",omitempty"` 175 | Priority int `json:",omitempty"` 176 | CustomCampaign string `json:",omitempty"` 177 | StatisticsContactsListID int `json:",omitempty"` 178 | MonitoringCategory string `json:",omitempty"` 179 | DeduplicateCampaign bool `json:",omitempty"` 180 | TrackClicks string `json:",omitempty"` 181 | TrackOpens string `json:",omitempty"` 182 | CustomID string `json:",omitempty"` 183 | Variables map[string]interface{} `json:",omitempty"` 184 | EventPayload string `json:",omitempty"` 185 | TemplateID int `json:",omitempty"` 186 | TemplateLanguage bool `json:",omitempty"` 187 | TemplateErrorReporting *RecipientV31 `json:",omitempty"` 188 | TemplateErrorDeliver bool `json:",omitempty"` 189 | Headers map[string]interface{} `json:",omitempty"` 190 | } 191 | 192 | // RecipientV31 struct handle users input 193 | type RecipientV31 struct { 194 | Email string `json:",omitempty"` 195 | Name string `json:",omitempty"` 196 | } 197 | 198 | // RecipientsV31 is a collection of emails 199 | type RecipientsV31 []RecipientV31 200 | 201 | // AttachmentV31 struct represent a content attachment 202 | type AttachmentV31 struct { 203 | ContentType string `json:"ContentType,omitempty"` 204 | Base64Content string `json:"Base64Content,omitempty"` 205 | Filename string `json:"Filename,omitempty"` 206 | } 207 | 208 | // AttachmentsV31 collection 209 | type AttachmentsV31 []AttachmentV31 210 | 211 | // InlinedAttachmentV31 struct represent the content of an inline attachement 212 | type InlinedAttachmentV31 struct { 213 | AttachmentV31 `json:",omitempty"` 214 | ContentID string `json:"ContentID,omitempty"` 215 | } 216 | 217 | // InlinedAttachmentsV31 collection 218 | type InlinedAttachmentsV31 []InlinedAttachmentV31 219 | 220 | // ErrorInfoV31 struct 221 | type ErrorInfoV31 struct { 222 | Identifier string `json:"ErrorIdentifier,omitempty"` 223 | Info string `json:"ErrorInfo"` 224 | Message string `json:"ErrorMessage"` 225 | StatusCode int `json:"StatusCode"` 226 | } 227 | 228 | func (err *ErrorInfoV31) Error() string { 229 | raw, _ := json.Marshal(err) 230 | return string(raw) 231 | } 232 | 233 | // APIErrorDetailsV31 contains the information details describing a specific error 234 | type APIErrorDetailsV31 struct { 235 | // Deprecated: ErrorClass is no longer populated. Use the ErrorCode to 236 | // classify the issue. 237 | ErrorClass string 238 | ErrorCode string 239 | ErrorIdentifier string 240 | ErrorMessage string 241 | ErrorRelatedTo []string 242 | StatusCode int 243 | } 244 | 245 | // APIFeedbackErrorV31 struct is composed of an error definition and the payload associated 246 | type APIFeedbackErrorV31 struct { 247 | Errors []APIErrorDetailsV31 248 | } 249 | 250 | // APIFeedbackErrorsV31 defines the error when a validation error is being sent by the API 251 | type APIFeedbackErrorsV31 struct { 252 | Messages []APIFeedbackErrorV31 253 | } 254 | 255 | func (api *APIFeedbackErrorsV31) Error() string { 256 | raw, _ := json.Marshal(api) 257 | return string(raw) 258 | } 259 | 260 | // GeneratedMessageV31 contains info to retrieve a generated email 261 | type GeneratedMessageV31 struct { 262 | Email string 263 | MessageUUID string 264 | MessageID int64 265 | MessageHref string 266 | } 267 | 268 | // ResultV31 bundles the results of a sent email 269 | type ResultV31 struct { 270 | Status string 271 | CustomID string `json:",omitempty"` 272 | To []GeneratedMessageV31 273 | Cc []GeneratedMessageV31 274 | Bcc []GeneratedMessageV31 275 | } 276 | 277 | // ResultsV31 bundles several results when several mails are sent 278 | type ResultsV31 struct { 279 | ResultsV31 []ResultV31 `json:"Messages"` 280 | } 281 | -------------------------------------------------------------------------------- /resources/resources.go: -------------------------------------------------------------------------------- 1 | // Package resources provides mailjet resources properties. This is an helper and 2 | // not a mandatory package. 3 | package resources 4 | 5 | import ( 6 | "bytes" 7 | "time" 8 | ) 9 | 10 | // 11 | // Resources Properties 12 | // 13 | 14 | // Aggregategraphstatistics: Aggregated campaign statistics grouped over intervals. 15 | type Aggregategraphstatistics struct { 16 | BlockedCount float64 `mailjet:"read_only"` 17 | BlockedStdDev float64 `mailjet:"read_only"` 18 | BouncedCount float64 `mailjet:"read_only"` 19 | BouncedStdDev float64 `mailjet:"read_only"` 20 | CampaignAggregateID int `mailjet:"read_only"` 21 | ClickedCount float64 `mailjet:"read_only"` 22 | ClickedStdDev float64 `mailjet:"read_only"` 23 | OpenedCount float64 `mailjet:"read_only"` 24 | OpenedStdDev float64 `mailjet:"read_only"` 25 | RefTimestamp int `mailjet:"read_only"` 26 | SentCount float64 `mailjet:"read_only"` 27 | SentStdDev float64 `mailjet:"read_only"` 28 | SpamComplaintCount float64 `mailjet:"read_only"` 29 | SpamcomplaintStdDev float64 `mailjet:"read_only"` 30 | UnsubscribedCount float64 `mailjet:"read_only"` 31 | UnsubscribedStdDev float64 `mailjet:"read_only"` 32 | } 33 | 34 | // Apikey: Manage your Mailjet API Keys. 35 | // API keys are used as credentials to access the API and SMTP server. 36 | type Apikey struct { 37 | ACL string `json:",omitempty"` 38 | APIKey string `mailjet:"read_only"` 39 | CreatedAt *RFC3339DateTime `mailjet:"read_only"` 40 | ID int64 `mailjet:"read_only"` 41 | IsActive bool `json:",omitempty"` 42 | IsMaster bool `mailjet:"read_only"` 43 | Name string 44 | QuarantineValue int `mailjet:"read_only"` 45 | Runlevel RunLevel `mailjet:"read_only"` 46 | SecretKey string `mailjet:"read_only"` 47 | TrackHost string `mailjet:"read_only"` 48 | UserID int64 `mailjet:"read_only"` 49 | } 50 | 51 | // Apikeyaccess: Access rights description on API keys for subaccounts/users. 52 | type Apikeyaccess struct { 53 | AllowedAccess string `json:",omitempty"` 54 | APIKeyID int64 `json:",omitempty"` 55 | APIKeyALT string `json:",omitempty"` 56 | CreatedAt *RFC3339DateTime `json:",omitempty"` 57 | CustomName string `json:",omitempty"` 58 | ID int64 `mailjet:"read_only"` 59 | IsActive bool `json:",omitempty"` 60 | LastActivityAt *RFC3339DateTime `json:",omitempty"` 61 | RealUserID int64 `json:",omitempty"` 62 | RealUserALT string `json:",omitempty"` 63 | Subaccount *SubAccount `json:",omitempty"` 64 | UserID int64 `json:",omitempty"` 65 | UserALT string `json:",omitempty"` 66 | } 67 | 68 | // Apikeytotals: Global counts for an API Key, since its creation. 69 | type Apikeytotals struct { 70 | BlockedCount int64 `mailjet:"read_only"` 71 | BouncedCount int64 `mailjet:"read_only"` 72 | ClickedCount int64 `mailjet:"read_only"` 73 | DeliveredCount int64 `mailjet:"read_only"` 74 | LastActivity int64 `mailjet:"read_only"` 75 | OpenedCount int64 `mailjet:"read_only"` 76 | ProcessedCount int64 `mailjet:"read_only"` 77 | QueuedCount int64 `mailjet:"read_only"` 78 | SpamcomplaintCount int64 `mailjet:"read_only"` 79 | UnsubscribedCount int64 `mailjet:"read_only"` 80 | } 81 | 82 | // Apitoken: Access token for API, used to give access to an API Key in conjunction with our IFrame API. 83 | type Apitoken struct { 84 | ACL string `json:",omitempty"` 85 | AllowedAccess string 86 | APIKeyID int64 `json:",omitempty"` 87 | APIKeyALT string `json:",omitempty"` 88 | CatchedIP string `json:"CatchedIp,omitempty"` 89 | CreatedAt *RFC3339DateTime `json:",omitempty"` 90 | FirstUsedAt *RFC3339DateTime `json:",omitempty"` 91 | ID int64 `mailjet:"read_only"` 92 | IsActive bool `json:",omitempty"` 93 | Lang string `json:",omitempty"` 94 | LastUsedAt *RFC3339DateTime `json:",omitempty"` 95 | SentData string `json:",omitempty"` 96 | Timezone string `json:",omitempty"` 97 | Token string `json:",omitempty"` 98 | TokenType string 99 | ValidFor int `json:",omitempty"` 100 | } 101 | 102 | // Axtesting: AX testing object 103 | type Axtesting struct { 104 | ContactListID int64 `json:",omitempty"` 105 | ContactListALT string `json:",omitempty"` 106 | CreatedAt *RFC3339DateTime `json:",omitempty"` 107 | Deleted bool `json:",omitempty"` 108 | ID int64 `mailjet:"read_only"` 109 | Mode AXTestMode `json:",omitempty"` 110 | Name string `json:",omitempty"` 111 | Percentage float64 `json:",omitempty"` 112 | RemainderAt *RFC3339DateTime `json:",omitempty"` 113 | SegmentationID int64 `json:",omitempty"` 114 | SegmentationALT string `json:",omitempty"` 115 | Starred bool `json:",omitempty"` 116 | StartAt *RFC3339DateTime `json:",omitempty"` 117 | Status string `json:",omitempty"` 118 | StatusCode int `mailjet:"read_only"` 119 | StatusString string `json:",omitempty"` 120 | WinnerClickRate float64 `json:",omitempty"` 121 | WinnerID int `json:",omitempty"` 122 | WinnerMethod WinnerMethod `json:",omitempty"` 123 | WinnerOpenRate float64 `json:",omitempty"` 124 | WinnerSpamRate float64 `json:",omitempty"` 125 | WinnerUnsubRate float64 `json:",omitempty"` 126 | } 127 | 128 | // Batchjob: Batch jobs running on the Mailjet infrastructure. 129 | type Batchjob struct { 130 | AliveAt int64 `json:",omitempty"` 131 | APIKeyID int64 `json:",omitempty"` 132 | APIKeyALT string `json:",omitempty"` 133 | Blocksize int `json:",omitempty"` 134 | Count int `json:",omitempty"` 135 | Current int `json:",omitempty"` 136 | Data *BaseData 137 | Errcount int `json:",omitempty"` 138 | ErrTreshold int `json:",omitempty"` 139 | ID int64 `mailjet:"read_only"` 140 | JobEnd int64 `json:",omitempty"` 141 | JobStart int64 `json:",omitempty"` 142 | JobType string 143 | Method string `json:",omitempty"` 144 | RefID int64 `json:"RefID,omitempty"` 145 | RequestAt int64 `json:",omitempty"` 146 | Status string `json:",omitempty"` 147 | Throttle int `json:",omitempty"` 148 | } 149 | 150 | type Contactsjob struct { 151 | Count int64 152 | Error string 153 | ErrorFile string 154 | Status string 155 | JobStart string 156 | JobEnd string 157 | } 158 | 159 | // Bouncestatistics: Statistics on the bounces generated by emails sent on a given API Key. 160 | type Bouncestatistics struct { 161 | BouncedAt *RFC3339DateTime `mailjet:"read_only"` 162 | CampaignID int64 `mailjet:"read_only"` 163 | CampaignALT string `mailjet:"read_only"` 164 | ContactID int64 `mailjet:"read_only"` 165 | ContactALT string `mailjet:"read_only"` 166 | ID int64 `mailjet:"read_only"` 167 | IsBlocked bool `mailjet:"read_only"` 168 | IsStatePermanent bool `mailjet:"read_only"` 169 | StateID int64 `mailjet:"read_only"` 170 | } 171 | 172 | // Campaign: Historical view of sent emails, both transactional and marketing. 173 | // Each e-mail going through Mailjet is attached to a Campaign. 174 | // This object is automatically generated by Mailjet. 175 | type Campaign struct { 176 | CampaignType int `mailjet:"read_only"` 177 | ClickTracked int64 `mailjet:"read_only"` 178 | CreatedAt *RFC3339DateTime `mailjet:"read_only"` 179 | CustomValue string `mailjet:"read_only"` 180 | FirstMessageID int64 `mailjet:"read_only"` 181 | FromID int64 `mailjet:"read_only"` 182 | FromALT string `mailjet:"read_only"` 183 | FromEmail string `mailjet:"read_only"` 184 | FromName string `mailjet:"read_only"` 185 | HasHtmlCount int64 `mailjet:"read_only"` 186 | HasTxtCount int64 `mailjet:"read_only"` 187 | ID int64 `mailjet:"read_only"` 188 | IsDeleted bool `json:",omitempty"` 189 | IsStarred bool `json:",omitempty"` 190 | ListID int64 `mailjet:"read_only"` 191 | ListALT string `mailjet:"read_only"` 192 | NewsLetterID int64 `mailjet:"read_only"` 193 | OpenTracked int64 `mailjet:"read_only"` 194 | SegmentationID int64 `mailjet:"read_only"` 195 | SegmentationALT string `mailjet:"read_only"` 196 | SendEndAt *RFC3339DateTime `mailjet:"read_only"` 197 | SendStartAt *RFC3339DateTime `mailjet:"read_only"` 198 | SpamassScore float64 `mailjet:"read_only"` 199 | Status string `mailjet:"read_only"` 200 | Subject string `mailjet:"read_only"` 201 | UnsubscribeTrackedCount int64 `mailjet:"read_only"` 202 | } 203 | 204 | // Campaignaggregate: User defined campaign aggregates 205 | type Campaignaggregate struct { 206 | CampaignIDS string `json:",omitempty"` 207 | ContactFilterID int64 `json:",omitempty"` 208 | ContactFilterALT string `json:",omitempty"` 209 | ContactsListID int64 `json:",omitempty"` 210 | ContactsListALT string `json:",omitempty"` 211 | Final bool `mailjet:"read_only"` 212 | FromDate *RFC3339DateTime `json:",omitempty"` 213 | ID int64 `mailjet:"read_only"` 214 | Keyword string `json:",omitempty"` 215 | Name string `json:",omitempty"` 216 | SenderID int64 `json:",omitempty"` 217 | SenderALT string `json:",omitempty"` 218 | ToDate *RFC3339DateTime `json:",omitempty"` 219 | } 220 | 221 | // Campaigndraft: Newsletter and CampaignDraft objects are differentiated by the EditMode values. 222 | type Campaigndraft struct { 223 | AXFractionName string `json:",omitempty"` 224 | AXTesting *Axtesting `json:",omitempty"` 225 | CampaignID int64 `json:",omitempty"` 226 | CampaignALT string `json:",omitempty"` 227 | ContactsListID int64 `json:",omitempty"` 228 | ContactsListALT string `json:",omitempty"` 229 | CreatedAt *RFC3339DateTime `json:",omitempty"` 230 | Current int64 `json:",omitempty"` 231 | DeliveredAt *RFC3339DateTime `json:",omitempty"` 232 | EditMode string `json:",omitempty"` 233 | ID int64 `mailjet:"read_only"` 234 | IsStarred bool `json:",omitempty"` 235 | IsTextPartIncluded bool `json:",omitempty"` 236 | Locale string 237 | ModifiedAt *RFC3339DateTime `json:",omitempty"` 238 | Preset string `json:",omitempty"` 239 | ReplyEmail string `json:",omitempty"` 240 | SegmentationID int64 `json:",omitempty"` 241 | SegmentationALT string `json:",omitempty"` 242 | Sender string 243 | SenderEmail string 244 | SenderName string `json:",omitempty"` 245 | Status int64 `mailjet:"read_only"` 246 | Subject string 247 | TemplateID int64 `json:",omitempty"` 248 | TemplateALT string `json:",omitempty"` 249 | Title string `json:",omitempty"` 250 | URL string `json:"Url,omitempty"` 251 | Used bool `json:",omitempty"` 252 | } 253 | 254 | // CampaigndraftSchedule: 255 | type CampaigndraftSchedule struct { 256 | Date *RFC3339DateTime 257 | } 258 | 259 | // CampaigndraftTest: 260 | type CampaigndraftTest struct { 261 | Recipients []Recipient 262 | } 263 | 264 | // CampaigndraftDetailcontent: 265 | type CampaigndraftDetailcontent struct { 266 | TextPart string `json:"Text-part,omitempty"` 267 | HtmlPart string `json:"Html-part,omitempty"` 268 | MJMLContent string `json:",omitempty"` 269 | Headers interface{} `json:",omitempty"` 270 | } 271 | 272 | // Campaigngraphstatistics: API Campaign statistics grouped over intervals 273 | type Campaigngraphstatistics struct { 274 | Clickcount int64 `mailjet:"read_only"` 275 | ID int64 `mailjet:"read_only"` 276 | Opencount int64 `mailjet:"read_only"` 277 | Spamcount int64 `mailjet:"read_only"` 278 | Tick int64 `mailjet:"read_only"` 279 | Unsubcount int64 `mailjet:"read_only"` 280 | } 281 | 282 | // Campaignoverview: Returns a list of campaigns, including the AX campaigns 283 | type Campaignoverview struct { 284 | ClickedCount int64 `mailjet:"read_only"` 285 | DeliveredCount int64 `mailjet:"read_only"` 286 | EditMode string `mailjet:"read_only"` 287 | EditType string `mailjet:"read_only"` 288 | ID int64 `mailjet:"read_only"` 289 | IDType string `mailjet:"read_only"` 290 | OpenedCount int64 `mailjet:"read_only"` 291 | ProcessedCount int64 `mailjet:"read_only"` 292 | SendTimeStart int64 `mailjet:"read_only"` 293 | Starred bool `mailjet:"read_only"` 294 | Status int `mailjet:"read_only"` 295 | Subject string `mailjet:"read_only"` 296 | Title string `mailjet:"read_only"` 297 | } 298 | 299 | // Campaignstatistics: Statistics related to emails processed by Mailjet, grouped in a Campaign. 300 | type Campaignstatistics struct { 301 | AXTesting *Axtesting `mailjet:"read_only"` 302 | BlockedCount int64 `mailjet:"read_only"` 303 | BouncedCount int64 `mailjet:"read_only"` 304 | CampaignID int64 `mailjet:"read_only"` 305 | CampaignALT string `mailjet:"read_only"` 306 | CampaignIsStarred bool `mailjet:"read_only"` 307 | CampaignSendStartAt *RFC3339DateTime `mailjet:"read_only"` 308 | CampaignSubject string `mailjet:"read_only"` 309 | ClickedCount int64 `mailjet:"read_only"` 310 | ContactListName string `mailjet:"read_only"` 311 | DeliveredCount int64 `mailjet:"read_only"` 312 | LastActivityAt *RFC3339DateTime `mailjet:"read_only"` 313 | NewsLetterID int64 `mailjet:"read_only"` 314 | OpenedCount int64 `mailjet:"read_only"` 315 | ProcessedCount int64 `mailjet:"read_only"` 316 | QueuedCount int64 `mailjet:"read_only"` 317 | SegmentName string `mailjet:"read_only"` 318 | SpamComplaintCount int64 `mailjet:"read_only"` 319 | UnsubscribedCount int64 `mailjet:"read_only"` 320 | } 321 | 322 | // Clickstatistics: Click statistics for messages. 323 | type Clickstatistics struct { 324 | ClickedAt string `mailjet:"read_only"` 325 | ClickedDelay int64 `mailjet:"read_only"` 326 | ContactID int64 `mailjet:"read_only"` 327 | ContactALT string `mailjet:"read_only"` 328 | ID int64 `mailjet:"read_only"` 329 | MessageID int64 `mailjet:"read_only"` 330 | URL string `json:"Url" mailjet:"read_only"` 331 | UserAgent string `mailjet:"read_only"` 332 | } 333 | 334 | // Contact: Manage the details of a Contact. 335 | type Contact struct { 336 | CreatedAt *RFC3339DateTime `mailjet:"read_only"` 337 | DeliveredCount int64 `mailjet:"read_only"` 338 | Email string 339 | ID int64 `mailjet:"read_only"` 340 | IsOptInPending bool `mailjet:"read_only"` 341 | IsSpamComplaining bool `mailjet:"read_only"` 342 | IsExcludedFromCampaigns bool 343 | LastActivityAt *RFC3339DateTime `mailjet:"read_only"` 344 | LastUpdateAt *RFC3339DateTime `mailjet:"read_only"` 345 | Name string `json:",omitempty"` 346 | UnsubscribedAt *RFC3339DateTime `mailjet:"read_only"` 347 | UnsubscribedBy string `mailjet:"read_only"` 348 | } 349 | 350 | // ContactManagecontactslists: Managing the lists for a single contact. POST is supported. 351 | type ContactManagecontactslists struct { 352 | ContactsLists []ContactsListAction 353 | } 354 | 355 | // ContactManagemanycontacts: Uploading many contacts and returns a job_id. 356 | // To monitor the upload issue a GET request to: APIBASEURL/contact/managemanycontacts/:job_id 357 | type ContactManagemanycontacts struct { 358 | ContactsLists []ContactsListAction 359 | Contacts []AddContactAction 360 | } 361 | 362 | // Contactdata: This resource can be used to examine and manipulate the associated extra static data of a contact. 363 | type Contactdata struct { 364 | ContactID int64 `json:",omitempty"` 365 | Data KeyValueList `json:",omitempty"` 366 | ID int64 `mailjet:"read_only"` 367 | } 368 | 369 | // Contactfilter: A list of filter expressions for use in newsletters. 370 | type Contactfilter struct { 371 | Description string `json:",omitempty"` 372 | Expression string `json:",omitempty"` 373 | ID int64 `mailjet:"read_only"` 374 | Name string `json:",omitempty"` 375 | Status string `json:",omitempty"` 376 | } 377 | 378 | // Contacthistorydata: This resource can be used to examine the associated extra historical data of a contact. 379 | type Contacthistorydata struct { 380 | ContactID int64 `json:",omitempty"` 381 | ContactALT string `json:",omitempty"` 382 | CreatedAt *RFC3339DateTime `json:",omitempty"` 383 | Data string `json:",omitempty"` 384 | ID int64 `mailjet:"read_only"` 385 | Name string `json:",omitempty"` 386 | } 387 | 388 | // Contactmetadata: Definition of available extra data items for contacts. 389 | type Contactmetadata struct { 390 | Datatype string 391 | ID int64 `mailjet:"read_only"` 392 | Name string 393 | NameSpace string `mailjet:"read_only"` 394 | } 395 | 396 | // Contactslist: Manage your contact lists. One Contact might be associated to one or more ContactsList. 397 | type Contactslist struct { 398 | Address string `mailjet:"read_only"` 399 | CreatedAt *RFC3339DateTime `mailjet:"read_only"` 400 | ID int64 `mailjet:"read_only"` 401 | IsDeleted bool `json:",omitempty"` 402 | Name string `json:",omitempty"` 403 | SubscriberCount int `mailjet:"read_only"` 404 | } 405 | 406 | // ContactslistManageContact: An action for adding a contact to a contact list. 407 | // The API will internally create the new contact if it does not exist, add or update the name and properties. 408 | // The properties have to be defined before they can be used. 409 | // The API then adds the contact to the contact list with active=true and unsub=specified value 410 | // if it is not already in the list, or updates the entry with these values. 411 | // On success, the API returns a packet with the same format but with all properties available for that contact. 412 | // Only POST is supported. 413 | type ContactslistManageContact struct { 414 | Email string 415 | Name string 416 | Action string 417 | Properties JSONObject 418 | } 419 | 420 | // ContactslistManageManyContacts: Multiple contacts can be uploaded asynchronously using that action. 421 | // Only POST is supported. 422 | type ContactslistManageManyContacts struct { 423 | Action string 424 | Contacts []AddContactAction 425 | } 426 | 427 | type Job struct { 428 | JobID int64 429 | } 430 | 431 | // ContactslistImportList: Import the contacts of another contact list into the current list and apply the specified action on imported contacts. 432 | // In case of conflict, the contact original subscription state is overridden. Returns the ID of the job to monitor. 433 | type ContactslistImportList struct { 434 | Action string 435 | ListID int64 436 | } 437 | 438 | // Contactslistsignup: Contacts list signup request. 439 | type Contactslistsignup struct { 440 | ConfirmAt int64 `json:",omitempty"` 441 | ConfirmIP string `json:"ConfirmIp"` 442 | ContactID int64 `json:",omitempty"` 443 | ContactALT string `json:",omitempty"` 444 | Email string 445 | ID int64 `mailjet:"read_only"` 446 | ListID int64 `json:",omitempty"` 447 | ListALT string `json:",omitempty"` 448 | SignupAt int64 `json:",omitempty"` 449 | SignupIP string `json:"SignupIp,omitempty"` 450 | SignupKey string `json:",omitempty"` 451 | Source string 452 | SourceId int64 `json:",omitempty"` 453 | } 454 | 455 | // Contactstatistics: View message statistics for a given contact. 456 | type Contactstatistics struct { 457 | BlockedCount int64 `mailjet:"read_only"` 458 | BouncedCount int64 `mailjet:"read_only"` 459 | ClickedCount int64 `mailjet:"read_only"` 460 | ContactID int64 `mailjet:"read_only"` 461 | ContactALT string `mailjet:"read_only"` 462 | DeliveredCount int64 `mailjet:"read_only"` 463 | LastActivityAt *RFC3339DateTime `mailjet:"read_only"` 464 | MarketingContacts int64 `mailjet:"read_only"` 465 | OpenedCount int64 `mailjet:"read_only"` 466 | ProcessedCount int64 `mailjet:"read_only"` 467 | QueuedCount int64 `mailjet:"read_only"` 468 | SpamComplaintCount int64 `mailjet:"read_only"` 469 | UnsubscribedCount int64 `mailjet:"read_only"` 470 | UserMarketingContacts int64 `mailjet:"read_only"` 471 | } 472 | 473 | // ContactGetcontactslists: retrieve all contact lists for a specific contact 474 | type ContactGetcontactslists struct { 475 | ListID int64 `mailjet:"read_only"` 476 | IsUnsub bool `mailjet:"read_only"` 477 | IsActive bool `mailjet:"read_only"` 478 | SubscribedAt *RFC3339DateTime `mailjet:"read_only"` 479 | } 480 | 481 | // Csvimport: A wrapper for the CSV importer 482 | type Csvimport struct { 483 | AliveAt *RFC3339DateTime `mailjet:"read_only"` 484 | ContactsListID int64 `json:",omitempty"` 485 | ContactsListALT string `json:",omitempty"` 486 | Count int `mailjet:"read_only"` 487 | Current int `mailjet:"read_only"` 488 | DataID int64 489 | Errcount int `mailjet:"read_only"` 490 | ErrTreshold int `json:",omitempty"` 491 | ID int64 `mailjet:"read_only"` 492 | ImportOptions string `json:",omitempty"` 493 | JobEnd *RFC3339DateTime `mailjet:"read_only"` 494 | JobStart *RFC3339DateTime `mailjet:"read_only"` 495 | Method string `json:",omitempty"` 496 | RequestAt *RFC3339DateTime `mailjet:"read_only"` 497 | Status string `json:",omitempty"` 498 | } 499 | 500 | // Dns: Sender Domain properties. 501 | type Dns struct { 502 | DKIMRecordName string `mailjet:"read_only"` 503 | DKIMRecordValue string `mailjet:"read_only"` 504 | DKIMStatus string `mailjet:"read_only"` 505 | Domain string `mailjet:"read_only"` 506 | ID int64 `mailjet:"read_only"` 507 | IsCheckInProgress bool `mailjet:"read_only"` 508 | LastCheckAt *RFC3339DateTime `mailjet:"read_only"` 509 | OwnerShipToken string `mailjet:"read_only"` 510 | OwnerShipTokenRecordName string `mailjet:"read_only"` 511 | SPFRecordValue string `mailjet:"read_only"` 512 | SPFStatus string `mailjet:"read_only"` 513 | } 514 | 515 | type DnsCheck struct { 516 | DKIMErrors []string `mailjet:"read_only"` 517 | DKIMStatus string `mailjet:"read_only"` 518 | DKIMRecordCurrentValue string `mailjet:"read_only"` 519 | SPFRecordCurrentValue string `mailjet:"read_only"` 520 | SPFErrors []string `mailjet:"read_only"` 521 | SPFStatus string `mailjet:"read_only"` 522 | } 523 | 524 | // Domainstatistics: View Campaign/Message/Click statistics grouped per domain. 525 | type Domainstatistics struct { 526 | BlockedCount int64 `mailjet:"read_only"` 527 | BouncedCount int64 `mailjet:"read_only"` 528 | ClickedCount int64 `mailjet:"read_only"` 529 | DeliveredCount int64 `mailjet:"read_only"` 530 | Domain string `mailjet:"read_only"` 531 | ID int64 `mailjet:"read_only"` 532 | OpenedCount int64 `mailjet:"read_only"` 533 | ProcessedCount int64 `mailjet:"read_only"` 534 | QueuedCount int64 `mailjet:"read_only"` 535 | SpamComplaintCount int64 `mailjet:"read_only"` 536 | UnsubscribedCount int64 `mailjet:"read_only"` 537 | } 538 | 539 | // Eventcallbackurl: Manage event-driven callback URLs, also called webhooks, 540 | // used by the Mailjet platform when a specific action is triggered 541 | type Eventcallbackurl struct { 542 | APIKeyID int64 `json:",omitempty"` 543 | APIKeyALT string `json:",omitempty"` 544 | EventType string `json:",omitempty"` 545 | ID int64 `mailjet:"read_only"` 546 | IsBackup bool `json:",omitempty"` 547 | Status string `json:",omitempty"` 548 | URL string `json:"Url"` 549 | Version int `json:",omitempty"` 550 | } 551 | 552 | // Geostatistics: Message click/open statistics grouped per country 553 | type Geostatistics struct { 554 | ClickedCount int64 `mailjet:"read_only"` 555 | Country string `mailjet:"read_only"` 556 | OpenedCount int64 `mailjet:"read_only"` 557 | } 558 | 559 | // Graphstatistics: API Campaign/message/click statistics grouped over intervals. 560 | type Graphstatistics struct { 561 | BlockedCount int64 `mailjet:"read_only"` 562 | BouncedCount int64 `mailjet:"read_only"` 563 | ClickedCount int64 `mailjet:"read_only"` 564 | DeliveredCount int64 `mailjet:"read_only"` 565 | OpenedCount int64 `mailjet:"read_only"` 566 | ProcessedCount int64 `mailjet:"read_only"` 567 | QueuedCount int64 `mailjet:"read_only"` 568 | RefTimestamp string `mailjet:"read_only"` 569 | SendtimeStart int64 `mailjet:"read_only"` 570 | SpamcomplaintCount int64 `mailjet:"read_only"` 571 | UnsubscribedCount int64 `mailjet:"read_only"` 572 | } 573 | 574 | // Listrecipient: Manage the relationship between a contact and a contactslists. 575 | type Listrecipient struct { 576 | ContactID int64 `json:",omitempty"` 577 | ContactALT string `json:",omitempty"` 578 | ID int64 `mailjet:"read_only"` 579 | IsActive bool `json:",omitempty"` 580 | IsUnsubscribed bool `json:",omitempty"` 581 | ListID int64 `json:",omitempty"` 582 | ListALT string `json:",omitempty"` 583 | UnsubscribedAt *RFC3339DateTime `json:",omitempty"` 584 | } 585 | 586 | // Listrecipientstatistics: View statistics on Messages sent to the recipients of a given list. 587 | type Listrecipientstatistics struct { 588 | BlockedCount int64 `mailjet:"read_only"` 589 | BouncedCount int64 `mailjet:"read_only"` 590 | ClickedCount int64 `mailjet:"read_only"` 591 | Data KeyValueList `mailjet:"read_only"` 592 | DeliveredCount int64 `mailjet:"read_only"` 593 | LastActivityAt *RFC3339DateTime `mailjet:"read_only"` 594 | ListRecipientID int64 `mailjet:"read_only"` 595 | OpenedCount int64 `mailjet:"read_only"` 596 | ProcessedCount int64 `mailjet:"read_only"` 597 | QueuedCount int64 `mailjet:"read_only"` 598 | SpamComplaintCount int64 `mailjet:"read_only"` 599 | UnsubscribedCount int64 `mailjet:"read_only"` 600 | } 601 | 602 | // Liststatistics: View Campaign/message/click statistics grouped by ContactsList. 603 | type Liststatistics struct { 604 | ActiveCount int64 `mailjet:"read_only"` 605 | ActiveUnsubscribedCount int64 `mailjet:"read_only"` 606 | Address string `mailjet:"read_only"` 607 | BlockedCount int64 `mailjet:"read_only"` 608 | BouncedCount int64 `mailjet:"read_only"` 609 | ClickedCount int64 `mailjet:"read_only"` 610 | CreatedAt *RFC3339DateTime `mailjet:"read_only"` 611 | DeliveredCount int64 `mailjet:"read_only"` 612 | ID int64 `mailjet:"read_only"` 613 | IsDeleted bool `mailjet:"read_only"` 614 | LastActivityAt *RFC3339DateTime `mailjet:"read_only"` 615 | Name string `mailjet:"read_only"` 616 | OpenedCount int64 `mailjet:"read_only"` 617 | SpamComplaintCount int64 `mailjet:"read_only"` 618 | SubscriberCount int `mailjet:"read_only"` 619 | UnsubscribedCount int64 `mailjet:"read_only"` 620 | } 621 | 622 | // Message: Allows you to list and view the details of a Message (an e-mail) processed by Mailjet 623 | type Message struct { 624 | ArrivedAt *RFC3339DateTime `json:",omitempty"` 625 | AttachmentCount int `json:",omitempty"` 626 | AttemptCount int `json:",omitempty"` 627 | CampaignID int64 `json:",omitempty"` 628 | CampaignALT string `json:",omitempty"` 629 | ContactID int64 `json:",omitempty"` 630 | ContactALT string `json:",omitempty"` 631 | Delay float64 `json:",omitempty"` 632 | Destination Destination 633 | FilterTime int `json:",omitempty"` 634 | FromID int64 `json:",omitempty"` 635 | FromALT string `json:",omitempty"` 636 | ID int64 `mailjet:"read_only"` 637 | IsClickTracked bool `json:",omitempty"` 638 | IsHTMLPartIncluded bool `json:",omitempty"` 639 | IsOpenTracked bool `json:",omitempty"` 640 | IsTextPartIncluded bool `json:",omitempty"` 641 | IsUnsubTracked bool `json:",omitempty"` 642 | MessageSize int64 `json:",omitempty"` 643 | SpamassassinScore float64 `json:",omitempty"` 644 | SpamassRules string `json:",omitempty"` 645 | Subject string `json:",omitempty"` 646 | StateID int64 `json:",omitempty"` 647 | StatePermanent bool `json:",omitempty"` 648 | Status string `json:",omitempty"` 649 | } 650 | 651 | // Messagehistory: Event history of a message. 652 | type Messagehistory struct { 653 | Comment string `mailjet:"read_only"` 654 | EventAt int64 `mailjet:"read_only"` 655 | EventType string `mailjet:"read_only"` 656 | State string `mailjet:"read_only"` 657 | UserAgent string `json:"Useragent" mailjet:"read_only"` 658 | } 659 | 660 | // Messageinformation: API Key campaign/message information. 661 | type Messageinformation struct { 662 | CampaignID int64 `mailjet:"read_only"` 663 | CampaignALT string `mailjet:"read_only"` 664 | ClickTrackedCount int64 `mailjet:"read_only"` 665 | ContactID int64 `mailjet:"read_only"` 666 | ContactALT string `mailjet:"read_only"` 667 | CreatedAt *RFC3339DateTime `mailjet:"read_only"` 668 | ID int64 `mailjet:"read_only"` 669 | MessageSize int64 `mailjet:"read_only"` 670 | OpenTrackedCount int64 `mailjet:"read_only"` 671 | QueuedCount int64 `mailjet:"read_only"` 672 | SendEndAt *RFC3339DateTime `mailjet:"read_only"` 673 | SentCount int64 `mailjet:"read_only"` 674 | SpamAssassinRules struct { 675 | Items []SpamAssassinRule 676 | } `mailjet:"read_only"` 677 | SpamAssassinScore float64 `mailjet:"read_only"` 678 | } 679 | 680 | // Messagesentstatistics: API Key Statistical campaign/message data. 681 | type Messagesentstatistics struct { 682 | ArrivalTs *RFC3339DateTime `mailjet:"read_only"` 683 | Blocked bool `mailjet:"read_only"` 684 | Bounce bool `mailjet:"read_only"` 685 | BounceDate *RFC3339DateTime `mailjet:"read_only"` 686 | BounceReason string `mailjet:"read_only"` 687 | CampaignID int64 `mailjet:"read_only"` 688 | CampaignALT string `mailjet:"read_only"` 689 | Click bool `mailjet:"read_only"` 690 | CntRecipients int64 `mailjet:"read_only"` 691 | ComplaintDate *RFC3339DateTime `mailjet:"read_only"` 692 | ContactID int64 `mailjet:"read_only"` 693 | ContactALT string `mailjet:"read_only"` 694 | Details string `mailjet:"read_only"` 695 | FBLSource string `mailjet:"read_only"` 696 | MessageID int64 `mailjet:"read_only"` 697 | Open bool `mailjet:"read_only"` 698 | Queued bool `mailjet:"read_only"` 699 | Sent bool `mailjet:"read_only"` 700 | Spam bool `mailjet:"read_only"` 701 | StateID int64 `mailjet:"read_only"` 702 | StatePermanent bool `mailjet:"read_only"` 703 | Status string `mailjet:"read_only"` 704 | ToEmail string `mailjet:"read_only"` 705 | Unsub bool `mailjet:"read_only"` 706 | } 707 | 708 | // Messagestate: Message state reference. 709 | type Messagestate struct { 710 | ID int64 `mailjet:"read_only"` 711 | RelatedTo string `json:",omitempty"` 712 | State string 713 | } 714 | 715 | // MessageStatistics: API key Campaign/Message statistics. 716 | type MessageStatistics struct { 717 | AverageClickDelay float64 `mailjet:"read_only"` 718 | AverageClickedCount float64 `mailjet:"read_only"` 719 | AverageOpenDelay float64 `mailjet:"read_only"` 720 | AverageOpenedCount float64 `mailjet:"read_only"` 721 | BlockedCount int64 `mailjet:"read_only"` 722 | BouncedCount int64 `mailjet:"read_only"` 723 | CampaignCount int64 `mailjet:"read_only"` 724 | ClickedCount int64 `mailjet:"read_only"` 725 | DeliveredCount int64 `mailjet:"read_only"` 726 | OpenedCount int64 `mailjet:"read_only"` 727 | ProcessedCount int64 `mailjet:"read_only"` 728 | QueuedCount int64 `mailjet:"read_only"` 729 | SpamComplaintCount int64 `mailjet:"read_only"` 730 | TransactionalCount int64 `mailjet:"read_only"` 731 | UnsubscribedCount int64 `mailjet:"read_only"` 732 | } 733 | 734 | // Metadata: Mailjet API meta data. 735 | type Metadata struct { 736 | APIVersion string `mailjet:"read_only"` 737 | Actions []ResourceAction `mailjet:"read_only"` 738 | Description string `mailjet:"read_only"` 739 | Filters []ResourceFilter `mailjet:"read_only"` 740 | IsReadOnly bool `mailjet:"read_only"` 741 | Name string `mailjet:"read_only"` 742 | Properties []ResourceProperty `mailjet:"read_only"` 743 | PublicOperations string `mailjet:"read_only"` 744 | SortInfo []struct { 745 | AllowDescending bool `mailjet:"read_only"` 746 | PropertyName string `mailjet:"read_only"` 747 | } `mailjet:"read_only"` 748 | UniqueKey string `mailjet:"read_only"` 749 | } 750 | 751 | type ResourceAction struct { 752 | Description string `mailjet:"read_only"` 753 | IsGlobalAction bool `mailjet:"read_only"` 754 | Name string `mailjet:"read_only"` 755 | Parameters []ResourceFilter `mailjet:"read_only"` 756 | Properties []ResourceProperty `mailjet:"read_only"` 757 | PublicOperations string `mailjet:"read_only"` 758 | } 759 | 760 | type ResourceFilter struct { 761 | DataType string `mailjet:"read_only"` 762 | DefaultValue string `mailjet:"read_only"` 763 | Description string `mailjet:"read_only"` 764 | IsRequired bool `mailjet:"read_only"` 765 | Name string `mailjet:"read_only"` 766 | ReadOnly bool `mailjet:"read_only"` 767 | } 768 | 769 | type ResourceProperty struct { 770 | DataType string `mailjet:"read_only"` 771 | DefaultValue string `mailjet:"read_only"` 772 | Description string `mailjet:"read_only"` 773 | IsRequired bool `mailjet:"read_only"` 774 | Name string `mailjet:"read_only"` 775 | ReadOnly bool `mailjet:"read_only"` 776 | } 777 | 778 | // Metasender: Management of domains used for sending messages. 779 | // A domain or address must be registered and validated before being used. 780 | // See the related Sender object if you wish to register a given e-mail address. 781 | type Metasender struct { 782 | CreatedAt *RFC3339DateTime `json:",omitempty"` 783 | Description string `json:",omitempty"` 784 | Email string 785 | Filename string `mailjet:"read_only"` 786 | ID int64 `mailjet:"read_only"` 787 | IsEnabled bool `json:",omitempty"` 788 | } 789 | 790 | // Myprofile: Manage user profile data such as address, payment information etc. 791 | type Myprofile struct { 792 | AddressCity string `json:",omitempty"` 793 | AddressCountry string `json:",omitempty"` 794 | AddressPostalCode string `json:",omitempty"` 795 | AddressState string `json:",omitempty"` 796 | AddressStreet string `json:",omitempty"` 797 | BillingEmail string `json:",omitempty"` 798 | BirthdayAt *RFC3339DateTime `json:",omitempty"` 799 | CompanyName string `json:",omitempty"` 800 | CompanyNumOfEmployees string `json:",omitempty"` 801 | ContactPhone string `json:",omitempty"` 802 | EstimatedVolume int `json:",omitempty"` 803 | Features string `json:",omitempty"` 804 | Firstname string `json:",omitempty"` 805 | ID int64 `mailjet:"read_only"` 806 | Industry string `json:",omitempty"` 807 | JobTitle string `json:",omitempty"` 808 | Lastname string `json:",omitempty"` 809 | UserID int64 `json:",omitempty"` 810 | UserALT string `json:",omitempty"` 811 | VAT float64 `mailjet:"read_only"` 812 | VATNumber string `json:",omitempty"` 813 | Website string `json:",omitempty"` 814 | } 815 | 816 | // Newsletter: Newsletter data. 817 | type Newsletter struct { 818 | AXFraction float64 `json:",omitempty"` 819 | AXFractionName string `json:",omitempty"` 820 | AXTesting *Axtesting `json:",omitempty"` 821 | Callback string `json:",omitempty"` 822 | CampaignID int64 `mailjet:"read_only"` 823 | CampaignALT string `mailjet:"read_only"` 824 | ContactsListID int64 `json:",omitempty"` 825 | ContactsListALT string `json:",omitempty"` 826 | CreatedAt *RFC3339DateTime `json:",omitempty"` 827 | DeliveredAt *RFC3339DateTime `json:",omitempty"` 828 | EditMode string `json:",omitempty"` 829 | EditType string `json:",omitempty"` 830 | Footer string `json:",omitempty"` 831 | FooterAddress string `json:",omitempty"` 832 | FooterWYSIWYGType int `json:",omitempty"` 833 | HeaderFilename string `json:",omitempty"` 834 | HeaderLink string `json:",omitempty"` 835 | HeaderText string `json:",omitempty"` 836 | HeaderURL string `json:"HeaderUrl,omitempty"` 837 | ID int64 `mailjet:"read_only"` 838 | IP string `json:"Ip,omitempty"` 839 | IsHandled bool `json:",omitempty"` 840 | IsStarred bool `json:",omitempty"` 841 | IsTextPartIncluded bool `json:",omitempty"` 842 | Locale string 843 | ModifiedAt *RFC3339DateTime `json:",omitempty"` 844 | Permalink string `json:",omitempty"` 845 | PermalinkHost string `json:",omitempty"` 846 | PermalinkWYSIWYGType int `json:",omitempty"` 847 | PolitenessMode int `json:",omitempty"` 848 | ReplyEmail string `json:",omitempty"` 849 | SegmentationID int64 `json:",omitempty"` 850 | SegmentationALT string `json:",omitempty"` 851 | Sender string 852 | SenderEmail string 853 | SenderName string `json:",omitempty"` 854 | Status string `json:",omitempty"` 855 | Subject string 856 | TemplateID int64 `json:",omitempty"` 857 | TestAddress string `json:",omitempty"` 858 | Title string `json:",omitempty"` 859 | URL string `json:"Url,omitempty"` 860 | } 861 | 862 | // NewsletterDetailcontent: An action to upload the content of the newsletter 863 | type NewsletterDetailcontent struct { 864 | TextPart string `json:"Text-part,omitempty"` 865 | HtmlPart string `json:"Html-part,omitempty"` 866 | } 867 | 868 | // NewsletterSchedule: An action to schedule a newsletters. 869 | type NewsLetterSchedule struct { 870 | Date *RFC3339DateTime 871 | } 872 | 873 | // NewsletterTest: An action to test a newsletter. 874 | type NewsletterTest struct { 875 | Recipients []Recipient 876 | } 877 | 878 | // Newslettertemplate: Manages a Newsletter Template Properties. 879 | type Newslettertemplate struct { 880 | CategoryID int64 `json:",omitempty"` 881 | CreatedAt *RFC3339DateTime `json:",omitempty"` 882 | Footer string `json:",omitempty"` 883 | FooterAddress string `json:",omitempty"` 884 | FooterWYSIWYGType int `json:",omitempty"` 885 | HeaderFilename string `json:",omitempty"` 886 | HeaderLink string `json:",omitempty"` 887 | HeaderText string `json:",omitempty"` 888 | HeaderURL string `json:"HeaderUrl,omitempty"` 889 | ID int64 `mailjet:"read_only"` 890 | Locale string 891 | Name string `json:",omitempty"` 892 | Permalink string `json:",omitempty"` 893 | PermalinkWYSIWYGType int `json:",omitempty"` 894 | SourceNewsLetterID int64 `json:",omitempty"` 895 | Status string `json:",omitempty"` 896 | } 897 | 898 | // Newslettertemplatecategory: Manage categories for your newsletters. 899 | // Allows you to group newsletters by category. 900 | type Newslettertemplatecategory struct { 901 | Description string 902 | ID int64 `mailjet:"read_only"` 903 | Locale string 904 | ParentCategoryID int64 905 | Value string 906 | } 907 | 908 | // Openinformation: Retrieve informations about messages opened at least once by their recipients. 909 | type Openinformation struct { 910 | ArrivedAt *RFC3339DateTime `mailjet:"read_only"` 911 | CampaignID int64 `mailjet:"read_only"` 912 | CampaignALT string `mailjet:"read_only"` 913 | ContactID int64 `mailjet:"read_only"` 914 | ContactALT string `mailjet:"read_only"` 915 | ID int64 `mailjet:"read_only"` 916 | MessageID int64 `mailjet:"read_only"` 917 | OpenedAt *RFC3339DateTime `mailjet:"read_only"` 918 | UserAgent string `mailjet:"read_only"` 919 | UserAgentFull string `mailjet:"read_only"` 920 | } 921 | 922 | // Openstatistics: Retrieve statistics on e-mails opened at least once by their recipients. 923 | type Openstatistics struct { 924 | OpenedCount int64 `mailjet:"read_only"` 925 | OpenedDelay float64 `mailjet:"read_only"` 926 | ProcessedCount int64 `mailjet:"read_only"` 927 | } 928 | 929 | // Parseroute: ParseRoute description 930 | type Parseroute struct { 931 | APIKeyID int64 `json:",omitempty"` 932 | APIKeyALT string `json:",omitempty"` 933 | Email string `json:",omitempty"` 934 | ID int64 `mailjet:"read_only"` 935 | URL string `json:"Url"` 936 | } 937 | 938 | // Preferences: User preferences in key=value format. 939 | type Preferences struct { 940 | ID int64 `mailjet:"read_only"` 941 | Key string 942 | UserID int64 `json:",omitempty"` 943 | UserALT string `json:",omitempty"` 944 | Value string `json:",omitempty"` 945 | } 946 | 947 | // Preset: The preset object contains global and user defined presets (styles) independent from templates or newsletters. 948 | // Access is similar to template and depends on OwnerType, Owner. No versioning is done. Presets are never referenced by their ID. 949 | // The preset value is copied into the template or newsletter. 950 | type Preset struct { 951 | Author string `json:",omitempty"` 952 | Copyright string `json:",omitempty"` 953 | Description string `json:",omitempty"` 954 | ID int64 `mailjet:"read_only"` 955 | Name string `json:",omitempty"` 956 | OwnerID int64 `json:",omitempty"` 957 | OwnerType string `json:",omitempty"` 958 | Preset string `json:",omitempty"` 959 | } 960 | 961 | // Sender: Manage an email sender for a single API key. 962 | // An e-mail address or a complete domain (*) has to be registered and validated before being used to send e-mails. 963 | // In order to manage a sender available across multiple API keys, see the related MetaSender resource. 964 | type Sender struct { 965 | CreatedAt *RFC3339DateTime `mailjet:"read_only"` 966 | DNS string `mailjet:"read_only"` // deprecated 967 | DNSID int64 `mailjet:"read_only"` 968 | Email string 969 | EmailType string `json:",omitempty"` 970 | Filename string `mailjet:"read_only"` 971 | ID int64 `mailjet:"read_only"` 972 | IsDefaultSender bool `json:",omitempty"` 973 | Name string `json:",omitempty"` 974 | Status string `mailjet:"read_only"` 975 | } 976 | 977 | // Senderstatistics: API Key sender email address message/open/click statistical information. 978 | type Senderstatistics struct { 979 | BlockedCount int64 `mailjet:"read_only"` 980 | BouncedCount int64 `mailjet:"read_only"` 981 | ClickedCount int64 `mailjet:"read_only"` 982 | DeliveredCount int64 `mailjet:"read_only"` 983 | LastActivityAt *RFC3339DateTime `mailjet:"read_only"` 984 | OpenedCount int64 `mailjet:"read_only"` 985 | ProcessedCount int64 `mailjet:"read_only"` 986 | QueuedCount int64 `mailjet:"read_only"` 987 | SenderID int64 `mailjet:"read_only"` 988 | SenderALT string `mailjet:"read_only"` 989 | SpamComplaintCount int64 `mailjet:"read_only"` 990 | UnsubscribedCount int64 `mailjet:"read_only"` 991 | } 992 | 993 | // SenderValidate: validation result for a sender or domain 994 | type SenderValidate struct { 995 | Errors map[string]string `mailjet:"read_only"` 996 | ValidationMethod string `mailjet:"read_only"` 997 | GlobalError string `mailjet:"read_only"` 998 | } 999 | 1000 | // Template: template description 1001 | type Template struct { 1002 | Author string `json:",omitempty"` 1003 | Categories []string `json:",omitempty"` 1004 | Copyright string `json:",omitempty"` 1005 | Description string `json:",omitempty"` 1006 | EditMode int `json:",omitempty"` 1007 | ID int64 `mailjet:"read_only"` 1008 | IsStarred bool `json:",omitempty"` 1009 | Name string `json:",omitempty"` 1010 | OwnerId int `mailjet:"read_only"` 1011 | OwnerType string `json:",omitempty"` 1012 | Presets string `json:",omitempty"` 1013 | Previews []int64 `mailjet:"read_only"` 1014 | Purposes []string `json:",omitempty"` 1015 | } 1016 | 1017 | // TemplateDetailcontent: GET, POST are supported to read, create, modify and delete the content of a template 1018 | type TemplateDetailcontent struct { 1019 | TextPart string `json:"Text-part,omitempty"` 1020 | HtmlPart string `json:"Html-part,omitempty"` 1021 | MJMLContent MJMLContent `json:",omitempty"` 1022 | Headers interface{} `json:",omitempty"` 1023 | } 1024 | 1025 | // MJMLContent: Structure of Passport template. 1026 | type MJMLContent struct { 1027 | tagName string 1028 | attributes map[string]interface{} 1029 | id string 1030 | } 1031 | 1032 | // Toplinkclicked: Top links clicked historgram. 1033 | type Toplinkclicked struct { 1034 | ClickedCount int64 `mailjet:"read_only"` 1035 | ID int64 `mailjet:"read_only"` 1036 | LinkID int64 `json:"LinkId" mailjet:"read_only"` 1037 | URL string `json:"Url" mailjet:"read_only"` 1038 | } 1039 | 1040 | // Trigger: Triggers for outgoing events. 1041 | type Trigger struct { 1042 | AddedTs int64 `json:",omitempty"` 1043 | APIKey int `json:",omitempty"` 1044 | Details string `json:",omitempty"` 1045 | Event string `json:",omitempty"` 1046 | ID int64 `mailjet:"read_only"` 1047 | User int `json:",omitempty"` 1048 | } 1049 | 1050 | // User: User account definition for Mailjet. 1051 | type User struct { 1052 | ACL string `json:",omitempty"` 1053 | CreatedAt *RFC3339DateTime `mailjet:"read_only"` 1054 | Email string `json:",omitempty"` 1055 | ID int64 `mailjet:"read_only"` 1056 | LastIp string 1057 | LastLoginAt *RFC3339DateTime `json:",omitempty"` 1058 | Locale string `json:",omitempty"` 1059 | MaxAllowedAPIKeys int `mailjet:"read_only"` 1060 | Timezone string `json:",omitempty"` 1061 | Username string 1062 | WarnedRatelimitAt *RFC3339DateTime `json:",omitempty"` 1063 | } 1064 | 1065 | // Useragentstatistics: View statistics on User Agents. 1066 | // See total counts or filter per Campaign or Contacts List. 1067 | // API Key message Open/Click statistical data grouped per user agent (browser). 1068 | type Useragentstatistics struct { 1069 | Count int64 `mailjet:"read_only"` 1070 | DistinctCount int64 `mailjet:"read_only"` 1071 | Platform string `mailjet:"read_only"` 1072 | UserAgent string `mailjet:"read_only"` 1073 | } 1074 | 1075 | // Widget: Manage settings for Widgets. 1076 | // Widgets are small registration forms that you may include on your website to ease the process of subscribing to a Contacts List. 1077 | // Mailjet widget definitions. 1078 | type Widget struct { 1079 | CreatedAt int64 `json:",omitempty"` 1080 | FromID int64 `json:",omitempty"` 1081 | FromALT string `json:",omitempty"` 1082 | ID int64 `mailjet:"read_only"` 1083 | IsActive bool `json:",omitempty"` 1084 | ListID int64 `json:",omitempty"` 1085 | ListALT string `json:",omitempty"` 1086 | Locale string 1087 | Name string `json:",omitempty"` 1088 | Replyto string `json:",omitempty"` 1089 | Sendername string `json:",omitempty"` 1090 | Subject string `json:",omitempty"` 1091 | Template *MessageTemplate `json:",omitempty"` 1092 | } 1093 | 1094 | // Widgetcustomvalue: Specifics settings for a given Mailjet Widget. See Widget.Mailjet widget settings. 1095 | type Widgetcustomvalue struct { 1096 | APIKeyID int64 `json:",omitempty"` 1097 | APIKeyALT string `json:",omitempty"` 1098 | Display bool `json:",omitempty"` 1099 | ID int64 `mailjet:"read_only"` 1100 | Name string 1101 | Value string `json:",omitempty"` 1102 | WidgetID int64 1103 | } 1104 | 1105 | type AXTestMode string 1106 | 1107 | const ( 1108 | AXTestAutomatic = AXTestMode("automatic") 1109 | AXTestManual = "manual" 1110 | ) 1111 | 1112 | type RunLevel string 1113 | 1114 | const ( 1115 | RunNormal = RunLevel("Normal") 1116 | RunSoftlock = "Softlock" 1117 | RunHardlock = "Hardlock" 1118 | ) 1119 | 1120 | type WinnerMethod string 1121 | 1122 | const ( 1123 | WinnerOpenRate = WinnerMethod("OpenRate") 1124 | WinnerClickRate = "ClickRate" 1125 | WinnerSpamRate = "SpamRate" 1126 | WinnerUnsubRate = "UnsubRate" 1127 | WinnerMJScore = "MJScore" 1128 | ) 1129 | 1130 | type BaseData struct { 1131 | DataAsString string 1132 | DataType string 1133 | } 1134 | 1135 | type SubAccount struct { 1136 | IsActive bool 1137 | CreatedAt *RFC3339DateTime 1138 | Email string 1139 | FirstIP string `json:"FirstIp"` 1140 | Firstname string 1141 | LastIP string `json:"LastIp"` 1142 | LastLoginAt *RFC3339DateTime 1143 | Lastname string 1144 | NewPasswordKey string 1145 | Password string 1146 | } 1147 | 1148 | type Destination struct { 1149 | Domain string 1150 | IsNeverBlocked bool 1151 | } 1152 | 1153 | type SpamAssassinRule struct { 1154 | HitCount int64 1155 | Name string 1156 | Score float64 1157 | } 1158 | 1159 | type MessageTemplate struct { 1160 | Category string 1161 | ContentHtml string 1162 | ContentText string 1163 | DefaultType string 1164 | Footer string 1165 | FromAddr string 1166 | FromName string 1167 | Name string 1168 | Locale string 1169 | ReplyTo string 1170 | Subject string 1171 | Subtitle string 1172 | Template string 1173 | Title string 1174 | } 1175 | 1176 | type KeyValueList []map[string]string 1177 | 1178 | type JSONObject interface{} 1179 | 1180 | type ContactsListAction struct { 1181 | ListID int64 1182 | Action string 1183 | } 1184 | 1185 | type AddContactAction struct { 1186 | Email string 1187 | Name string 1188 | IsExcludedFromCampaigns *bool `json:",omitempty"` 1189 | Properties JSONObject 1190 | } 1191 | 1192 | type Recipient struct { 1193 | Email string 1194 | Name string 1195 | } 1196 | 1197 | type RFC3339DateTime struct { 1198 | time.Time 1199 | } 1200 | 1201 | func (dt *RFC3339DateTime) UnmarshalJSON(b []byte) (err error) { 1202 | b = bytes.Trim(b, `" `) 1203 | if b == nil { 1204 | return nil 1205 | } 1206 | 1207 | dt.Time, err = time.Parse(time.RFC3339, string(b)) 1208 | 1209 | return err 1210 | } 1211 | 1212 | func (dt *RFC3339DateTime) MarshalJSON() ([]byte, error) { 1213 | return []byte(dt.Format(`"` + time.RFC3339 + `"`)), nil 1214 | } 1215 | -------------------------------------------------------------------------------- /resources/time_test.go: -------------------------------------------------------------------------------- 1 | package resources 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestMarshalJSON(t *testing.T) { 10 | dateTime, _ := time.Parse(time.RFC3339, "2016-10-14T12:42:05Z") 11 | dt := RFC3339DateTime{dateTime} 12 | 13 | want := []byte(`"2016-10-14T12:42:05Z"`) 14 | got, err := dt.MarshalJSON() 15 | 16 | if err != nil { 17 | t.Error("unexpected error:", err) 18 | } 19 | 20 | if !bytes.Equal(got, want) { 21 | t.Errorf("expected %v", want) 22 | t.Errorf(" got %v", got) 23 | } 24 | } 25 | 26 | func TestUnmarshalJSON(t *testing.T) { 27 | data := []byte(`"2016-10-14T12:42:05Z"`) 28 | var dt RFC3339DateTime 29 | 30 | err := dt.UnmarshalJSON(data) 31 | if err != nil { 32 | t.Error("unexpected error:", err) 33 | } 34 | 35 | if dt.UnixNano() != 1476448925000000000 { 36 | t.Errorf("expected: %v", 1476448925000000000) 37 | t.Errorf(" got: %v", dt.UnixNano()) 38 | } 39 | } 40 | 41 | func TestUnmarshalEmptyJSON(t *testing.T) { 42 | var data []byte 43 | var dt RFC3339DateTime 44 | 45 | err := dt.UnmarshalJSON(data) 46 | if err != nil { 47 | t.Error("unexpected error:", err) 48 | } 49 | 50 | data = []byte{34, 34} 51 | err = dt.UnmarshalJSON(data) 52 | if err != nil { 53 | t.Error("unexpected error:", err) 54 | } 55 | } 56 | 57 | func TestUnmarshalBrokenJSON(t *testing.T) { 58 | data := []byte{1, 1} 59 | var dt RFC3339DateTime 60 | 61 | err := dt.UnmarshalJSON(data) 62 | if err == nil { 63 | t.Error("error expected") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /smtp_client.go: -------------------------------------------------------------------------------- 1 | package mailjet 2 | 3 | import ( 4 | "fmt" 5 | "net/smtp" 6 | ) 7 | 8 | // SMTPClient is the wrapper for smtp 9 | type SMTPClient struct { 10 | host string 11 | auth smtp.Auth 12 | } 13 | 14 | // Hostname and port for the SMTP client. 15 | const ( 16 | HostSMTP = "in-v3.mailjet.com" 17 | PortSMTP = 587 18 | ) 19 | 20 | // NewSMTPClient returns a new smtp client wrapper 21 | func NewSMTPClient(apiKeyPublic, apiKeyPrivate string) *SMTPClient { 22 | auth := smtp.PlainAuth( 23 | "", 24 | apiKeyPublic, 25 | apiKeyPrivate, 26 | HostSMTP, 27 | ) 28 | return &SMTPClient{ 29 | host: fmt.Sprintf("%s:%d", HostSMTP, PortSMTP), 30 | auth: auth, 31 | } 32 | } 33 | 34 | // SendMail wraps smtp.SendMail 35 | func (s SMTPClient) SendMail(from string, to []string, msg []byte) error { 36 | return smtp.SendMail(s.host, s.auth, from, to, msg) 37 | } 38 | -------------------------------------------------------------------------------- /smtp_client_decl.go: -------------------------------------------------------------------------------- 1 | package mailjet 2 | 3 | // SMTPClientInterface def 4 | type SMTPClientInterface interface { 5 | SendMail(from string, to []string, msg []byte) error 6 | } 7 | -------------------------------------------------------------------------------- /smtp_client_mock.go: -------------------------------------------------------------------------------- 1 | package mailjet 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // SMTPClientMock def 8 | type SMTPClientMock struct { 9 | valid bool 10 | } 11 | 12 | // NewSMTPClientMock returns a new smtp client mock 13 | func NewSMTPClientMock(valid bool) *SMTPClientMock { 14 | return &SMTPClientMock{ 15 | valid: valid, 16 | } 17 | } 18 | 19 | // SendMail wraps smtp.SendMail 20 | func (s SMTPClientMock) SendMail(from string, to []string, msg []byte) error { 21 | if s.valid { 22 | return nil 23 | } 24 | return errors.New("smtp send error") 25 | } 26 | -------------------------------------------------------------------------------- /tests/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net/textproto" 8 | "os" 9 | "sync" 10 | "time" 11 | 12 | "github.com/mailjet/mailjet-apiv3-go/v4" 13 | "github.com/mailjet/mailjet-apiv3-go/v4/resources" 14 | ) 15 | 16 | var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 17 | 18 | func randSeq(n int) string { 19 | rand.Seed(time.Now().UnixNano()) 20 | b := make([]rune, n) 21 | for i := range b { 22 | //nolint:gosec // G404 crypto random is not required here 23 | b[i] = letters[rand.Intn(len(letters))] 24 | } 25 | return string(b) 26 | } 27 | 28 | func testNewMailjetClient() { 29 | ak := os.Getenv("MJ_APIKEY_PUBLIC") 30 | sk := os.Getenv("MJ_APIKEY_PRIVATE") 31 | m := mailjet.NewMailjetClient(ak, sk) 32 | 33 | if ak != m.APIKeyPublic() { 34 | log.Fatal("Wrong public key:", m.APIKeyPublic()) 35 | } 36 | 37 | if sk != m.APIKeyPrivate() { 38 | log.Fatal("Wrong secret key:", m.APIKeyPrivate()) 39 | } 40 | } 41 | 42 | func testList() { 43 | m := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 44 | 45 | var data []resources.Sender 46 | count, _, err := m.List("sender", &data) 47 | if err != nil { 48 | log.Fatal("Unexpected error:", err) 49 | } 50 | if count < 1 { 51 | log.Fatal("At least one sender expected !") 52 | } 53 | } 54 | 55 | func testGet() { 56 | m := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 57 | 58 | var data []resources.User 59 | resource := "user" 60 | count, _, err := m.List(resource, &data) 61 | if err != nil { 62 | log.Fatal("Unexpected error:", err) 63 | } 64 | if count < 1 { 65 | log.Fatal("At least one user expected !") 66 | } 67 | if data == nil { 68 | log.Fatal("Empty result") 69 | } 70 | 71 | mr := &mailjet.Request{Resource: resource, ID: data[0].ID} 72 | data = make([]resources.User, 0) 73 | err = m.Get(mr, &data) 74 | if err != nil { 75 | log.Fatal("Unexpected error:", err) 76 | } 77 | fmt.Printf("Data: %+v\n", data[0]) 78 | } 79 | 80 | func testPost() { 81 | m := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 82 | 83 | var data []resources.Contact 84 | rstr := randSeq(10) 85 | fmt.Printf("Create new contact: \"%s@mailjet.com\"\n", rstr) 86 | fmr := &mailjet.FullRequest{ 87 | Info: &mailjet.Request{Resource: "contact"}, 88 | Payload: &resources.Contact{Name: rstr, Email: rstr + "@mailjet.com"}, 89 | } 90 | err := m.Post(fmr, &data) 91 | if err != nil { 92 | log.Fatal("Unexpected error:", err) 93 | } 94 | if data == nil { 95 | log.Fatal("Empty result") 96 | } 97 | fmt.Printf("Data: %+v\n", data[0]) 98 | } 99 | 100 | func testPut() { 101 | m := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 102 | 103 | var data []resources.Contactslist 104 | resource := "contactslist" 105 | count, _, err := m.List(resource, &data) 106 | if err != nil { 107 | log.Fatal("Unexpected error:", err) 108 | } 109 | if count < 1 { 110 | log.Fatal("At least one contact list expected on test account!") 111 | } 112 | if data == nil { 113 | log.Fatal("Empty result") 114 | } 115 | 116 | rstr := randSeq(10) 117 | fmt.Printf("Update name of the contact list: %s -> %s\n", data[0].Name, rstr) 118 | data[0].Name = randSeq(10) 119 | fmr := &mailjet.FullRequest{ 120 | Info: &mailjet.Request{Resource: resource, AltID: data[0].Address}, 121 | Payload: data[0], 122 | } 123 | err = m.Put(fmr, []string{"Name"}) 124 | if err != nil { 125 | log.Fatal("Unexpected error:", err) 126 | } 127 | } 128 | 129 | func testDelete() { 130 | m := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 131 | 132 | var data []resources.Listrecipient 133 | resource := "listrecipient" 134 | count, _, err := m.List(resource, &data) 135 | if err != nil { 136 | log.Fatal("Unexpected error:", err) 137 | } 138 | if count < 1 { 139 | return 140 | } 141 | if data == nil { 142 | log.Fatal("Empty result") 143 | } 144 | 145 | mr := &mailjet.Request{ 146 | ID: data[0].ID, 147 | Resource: resource, 148 | } 149 | err = m.Delete(mr) 150 | if err != nil { 151 | fmt.Println(err) 152 | } 153 | } 154 | 155 | func testSendMail() { 156 | m := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 157 | 158 | var data []resources.Sender 159 | count, _, err := m.List("sender", &data) 160 | if err != nil { 161 | log.Fatal("Unexpected error:", err) 162 | } 163 | if count < 1 || data == nil { 164 | log.Fatal("At least one sender expected in the test account!") 165 | } 166 | 167 | param := &mailjet.InfoSendMail{ 168 | FromEmail: data[0].Email, 169 | FromName: data[0].Name, 170 | Recipients: []mailjet.Recipient{ 171 | { 172 | Email: data[0].Email, 173 | }, 174 | }, 175 | Subject: "Send API testing", 176 | TextPart: "SendMail is working !", 177 | } 178 | res, err := m.SendMail(param) 179 | if err != nil { 180 | log.Fatal("Unexpected error:", err) 181 | } 182 | fmt.Printf("Data: %+v\n", res) 183 | } 184 | 185 | func testSendMailSMTP() { 186 | mj := mailjet.NewMailjetClient( 187 | os.Getenv("MJ_APIKEY_PUBLIC"), 188 | os.Getenv("MJ_APIKEY_PRIVATE")) 189 | 190 | var data []resources.Sender 191 | count, _, err := mj.List("sender", &data) 192 | if err != nil { 193 | log.Fatal("Unexpected error:", err) 194 | } 195 | if count < 1 || data == nil { 196 | log.Fatal("At least one sender expected in the test account!") 197 | } 198 | 199 | email := data[0].Email 200 | 201 | header := make(textproto.MIMEHeader) 202 | header.Add("From", email) 203 | header.Add("To", email) 204 | header.Add("Subject", "SMTP testing") 205 | header.Add("X-Mailjet-Campaign", "test") 206 | content := []byte("SendMailSmtp is working !") 207 | info := &mailjet.InfoSMTP{ 208 | From: email, 209 | Recipients: header["To"], 210 | Header: header, 211 | Content: content, 212 | } 213 | err = mj.SendMailSMTP(info) 214 | if err != nil { 215 | log.Fatal("Unexpected error:", err) 216 | } 217 | } 218 | 219 | func testDataRace() { 220 | m := mailjet.NewMailjetClient(os.Getenv("MJ_APIKEY_PUBLIC"), os.Getenv("MJ_APIKEY_PRIVATE")) 221 | 222 | var wg sync.WaitGroup 223 | wg.Add(5) 224 | 225 | for i := 0; i < 5; i++ { 226 | go func() { 227 | var data []resources.Sender 228 | count, _, err := m.List("sender", &data) 229 | if err != nil { 230 | log.Fatal("Unexpected error:", err) 231 | } 232 | if count < 1 || data == nil { 233 | log.Fatal("At least one sender expected in the test account!") 234 | } 235 | wg.Done() 236 | }() 237 | } 238 | wg.Wait() 239 | } 240 | 241 | func main() { 242 | testNewMailjetClient() 243 | testList() 244 | testGet() 245 | testPost() 246 | testPut() 247 | testDelete() 248 | testSendMail() 249 | testSendMailSMTP() 250 | testDataRace() 251 | } 252 | --------------------------------------------------------------------------------