[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\nindent_size = 4\nindent_style = tab\ntrim_trailing_whitespace = true\ninsert_final_newline = true\ncharset = utf-8\n\n[*.{md,yml}]\nindent_size = 2\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing\n\nHello, we are very happy you decided to contribute to Go Mail. But before you start with your contribution,\nplease make sure to read through our guidelines:\n\n- [Issue Reporting Guidline](#issue-reporting-guidline)\n- [Pull Request Guidlines](#pull-request-guidlines)\n\n## Issue Reporting Guideline\n\nIf you find a bug or believe that some important feature is missing you can open a new issue on the Github-Project page,\nusing our provided issue templates. Before creating a new issue, please make sure that there isn't already an issue\ncovering this problem or requesting this feature.\n\n## Pull Request Guidelines\n\n- The `main` branch always contains the latest stable released version and doesn't take PRs.\n  Instead, create dedicated feature branches and submit your PR to our `dev` branch.\n- It's okay if your PR contains several small commits as we will squash the PR before merging it.\n- Please try to use meaningful commit messages.\n- Before creating a new PR, check if your code is linted correctly.\n- If you want to add a new feature:\n    - Add a small but complete description of the new feature.\n    - Please provide a convincing reason why you think this feature needs to be added.\n- If you add a bug fix:\n    - Please refer the corresponding issue, if one exists, in your PR.\n    - If no issue exist for the bug you fix you need to provide a detailed description of the error and if possible a live demo. Or create a new issue on our [Github page](https://github.com/ainsleyclark/go-mail/issues)\n- Create unit tests for new features and use the `make all` command to test, lint and format.\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [ainsleyclark]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug Report\nabout: Create a report to help us improve Go Mail\ntitle: ''\nlabels: ''\nassignees: ''\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Version**\nPlease specify a version number you are using.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for Go Mail\ntitle: ''\nlabels: ''\nassignees: ''\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "# Description\n\nPlease include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.\n\nFixes # (issue)\n\n## Type of change\n\nPlease delete options that are not relevant.\n\n- [ ] Bug fix (non-breaking change which fixes an issue)\n- [ ] New feature (non-breaking change which adds functionality)\n- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)\n- [ ] This change requires a documentation update\n\n# How Has This Been Tested?\n\nPlease describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration\n\n- [ ] Test A\n- [ ] Test B\n\n# Checklist:\n\n- [ ] My code follows the style guidelines of this project\n- [ ] I have performed a self-review of my own code\n- [ ] I have commented my code, particularly in hard-to-understand areas\n- [ ] I have made corresponding changes to the documentation\n- [ ] My changes generate no new warnings\n- [ ] I have added tests that prove my fix is effective or that my feature works\n- [ ] New and existing unit tests pass locally with my changes\n- [ ] Any dependent changes have been merged and published in downstream modules\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\n## Reporting an Issue\n\nIf you need to report a security issue please email the author [here](mailto:info@ainsleyclark.com)\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ main ]\n  schedule:\n    - cron: '25 19 * * 1'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'go' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]\n        # Learn more about CodeQL language support at https://git.io/codeql-language-support\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v2\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v1\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n        # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v1\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 https://git.io/JvXDl\n\n    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n    #    and modify them (or add more) to build your code if your project\n    #    uses a compiled language\n\n    #- run: |\n    #   make bootstrap\n    #   make release\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v1\n"
  },
  {
    "path": ".github/workflows/email.yml",
    "content": "name: Email\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  schedule:\n    - cron: '30 1 1,15 * *'\n  workflow_dispatch:\n\njobs:\n\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Set up Go\n        uses: actions/setup-go@v2\n\n        with:\n          go-version: 1.17\n\n      - name: Test\n        env:\n          # Email\n          EMAIL_TO: ${{ secrets.EMAIL_TO }}\n          EMAIL_CC: ${{ secrets.EMAIL_CC }}\n          EMAIL_BCC: ${{ secrets.EMAIL_BCC }}\n          # MailGun\n          MAILGUN_URL: ${{ secrets.MAILGUN_URL }}\n          MAILGUN_API_KEY: ${{ secrets.MAILGUN_API_KEY }}\n          MAILGUN_FROM_ADDRESS: ${{ secrets.MAILGUN_FROM_ADDRESS }}\n          MAILGUN_FROM_NAME: ${{ secrets.MAILGUN_FROM_NAME }}\n          MAILGUN_DOMAIN: ${{ secrets.MAILGUN_DOMAIN }}\n          # Postal\n          POSTAL_URL: ${{ secrets.POSTAL_URL }}\n          POSTAL_API_KEY: ${{ secrets.POSTAL_API_KEY }}\n          POSTAL_FROM_ADDRESS: ${{ secrets.POSTAL_FROM_ADDRESS }}\n          POSTAL_FROM_NAME: ${{ secrets.POSTAL_FROM_NAME }}\n          # Postmark\n          POSTMARK_API_KEY: ${{ secrets.POSTMARK_API_KEY }}\n          POSTMARK_FROM_ADDRESS: ${{ secrets.POSTMARK_FROM_ADDRESS }}\n          POSTMARK_FROM_NAME: ${{ secrets.POSTMARK_FROM_NAME }}\n          # SendGrid\n          SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }}\n          SENDGRID_FROM_ADDRESS: ${{ secrets.SENDGRID_FROM_ADDRESS }}\n          SENDGRID_FROM_NAME: ${{ secrets.SENDGRID_FROM_NAME }}\n          # SMTP\n          SMTP_URL: ${{ secrets.SMTP_URL }}\n          SMTP_FROM_ADDRESS: ${{ secrets.SMTP_FROM_ADDRESS }}\n          SMTP_FROM_NAME: ${{ secrets.SMTP_FROM_NAME }}\n          SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}\n          SMTP_PORT: ${{ secrets.SMTP_PORT }}\n          # SparkPost\n          SPARKPOST_URL: ${{ secrets.SPARKPOST_URL }}\n          SPARKPOST_API_KEY: ${{ secrets.SPARKPOST_API_KEY }}\n          SPARKPOST_FROM_ADDRESS: ${{ secrets.SPARKPOST_FROM_ADDRESS }}\n          SPARKPOST_FROM_NAME: ${{ secrets.SPARKPOST_FROM_NAME }}\n        run: |\n          # Make file runnable, might not be necessary\n          chmod +x ./bin/tests.sh\n          # Run tests\n          # Ignore Postal, no server active.\n          ./bin/tests.sh mailgun\n          ./bin/tests.sh postmark\n          ./bin/tests.sh sendgrid\n          ./bin/tests.sh smtp\n          ./bin/tests.sh sparkpost\n\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  workflow_dispatch:\n\njobs:\n\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Set up Go\n        uses: actions/setup-go@v2\n        with:\n          go-version: 1.17\n\n      - name: Format\n        run: make format\n\n      - name: Lint\n        uses: golangci/golangci-lint-action@v2\n        with:\n          version: latest\n          skip-go-installation: true\n          skip-pkg-cache: true\n          args: --verbose\n\n      - name: Test\n        run: make test\n\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@v2.1.0\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n          file: ./coverage.out\n\n      - name: Diff\n        run: git diff\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n\n# Editors\n.idea\n.idea/*\n\n# Sensitive Info\ncredentials.md\n\n# System Files\n.DS_Store\n\n# Go Mail Specifc\n.env\n"
  },
  {
    "path": ".golangci.yml",
    "content": "linters:\n  enable:\n    - gofmt\n    - govet\n    - gocyclo\n    - ineffassign\n    - thelper\n    - tparallel\n    - unconvert\n    - unparam\n    - wastedassign\n    - revive\nrun:\n  go: '1.17'\n  skip-dirs:\n    - res\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Ainsley Clark\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "# Setup\nsetup:\n\tsudo chmod +x ./bin/tests.sh\n\tgo mod tidy\n.PHONY: setup\n\n# Run gofmt\nformat:\n\tgo fmt ./...\n.PHONY: format\n\n# Run linter\nlint:\n\tgolangci-lint run ./...\n.PHONY: lint\n\n# Test uses race and coverage\ntest:\n\tgo clean -testcache && go test -race $$(go list ./... | grep -v tests | grep -v examples | grep -v res | grep -v mocks) -coverprofile=coverage.out -covermode=atomic\n.PHONY: test\n\n# Test with -v\ntest-v:\n\tgo clean -testcache && go test -race -v $$(go list ./... | grep -v tests | grep -v examples | grep -v res | grep -v mocks) -coverprofile=coverage.out -covermode=atomic\n.PHONY: test-v\n\n# Runs real world tests for a driver or all drivers.\n# See ./bin/tests.sh for example usage.\ntest-driver:\n\tgo clean -testcache && ./bin/tests.sh $(driver)\n.PHONY: test-driver\n\n# Run all the tests and opens the coverage report\ncover: test\n\tgo tool cover -html=coverage.out\n.PHONY: cover\n\n# Make mocks keeping directory tree\nmocks:\n\trm -rf internal/mocks && mockery --all --keeptree --output ./internal/mocks && mv ./internal/mocks/internal/* ./internal/mocks\n.PHONY: mocks\n\n# Run go doc\ndoc:\n\tgodoc -http localhost:8080\n.PHONY: doc\n\n# Make format, lint and test\nall:\n\t$(MAKE) format\n\t$(MAKE) lint\n\t$(MAKE) test\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n<img height=\"300\" src=\"res/logos/go-mail.svg?size=new2\" alt=\"Go Mail Logo\" />\n\n[![made-with-Go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg)](http://golang.org)\n[![Go Report Card](https://goreportcard.com/badge/github.com/ainsleyclark/go-mail)](https://goreportcard.com/report/github.com/ainsleyclark/go-mail)\n[![Test](https://github.com/ainsleyclark/go-mail/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/ainsleyclark/go-mail/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/ainsleyclark/go-mail/branch/main/graph/badge.svg?token=1ZI9R34CHQ)](https://codecov.io/gh/ainsleyclark/go-mail)\n[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/ainsleyclark/go-mail)\n[![Twitter Handle](https://img.shields.io/twitter/follow/ainsleydev)](https://twitter.com/ainsleydev)\n</div>\n\n# 📧 Go Mail\n\nA cross-platform mail driver for GoLang. Featuring Mailgun, Postal, Postmark, SendGrid, SparkPost & SMTP.\n\n## Overview\n\n- ✅ Multiple mail drivers for your needs or even create your own custom Mailer.\n- ✅ Direct dependency free, all requests are made with the standard lib http.Client.\n- ✅ Send attachments with two struct fields, it's extremely simple.\n- ✅ Send CC & BCC messages.\n- ✅ Extremely lightweight.\n\n## Supported API's\n\n- <img align=\"left\" src=\"res/logos/mailgun.svg\" width=\"24\" />  [Mailgun](https://documentation.mailgun.com/)\n\n- <img align=\"left\" src=\"res/logos/postal.svg\" width=\"24\" /> [Postal](https://docs.postalserver.io/)\n\n- <img align=\"left\" src=\"res/logos/postmark.png\" width=\"24\" /> [Postmark](https://postmarkapp.com/)\n\n- <img align=\"left\" src=\"res/logos/sendgrid.svg\" width=\"24\" /> [SendGrid](https://sendgrid.com/)\n\n- <img align=\"left\" src=\"res/logos/sparkpost.png?new=new\" width=\"24\" /> [SparkPost](https://www.sparkpost.com/)\n\n- <img align=\"left\" src=\"res/logos/smtp.svg\" width=\"24\" /> SMTP\n\n## Introduction\n\nGo Mail aims to unify multiple popular mail APIs into a singular, easy to use interface. Email sending is seriously\nsimple and great for allowing the developer or end user to choose what platform they use.\n\n```go\ncfg := mail.Config{\n    URL:         \"https://api.eu.sparkpost.com\",\n    APIKey:      \"my-key\",\n    FromAddress: \"hello@gophers.com\",\n    FromName:    \"Gopher\",\n}\n\nmailer, err := drivers.NewSparkPost(cfg)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\ntx := &mail.Transmission{\n    Recipients:  []string{\"hello@gophers.com\"},\n    Subject:     \"My email\",\n    HTML:        \"<h1>Hello from Go Mail!</h1>\",\n}\n\nresult, err := mailer.Send(tx)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\nfmt.Printf(\"%+v\\n\", result)\n```\n\n## Installation\n\n```bash\ngo get -u github.com/ainsleyclark/go-mail\n```\n\n## Docs\n\nDocumentation can be found at the [Go Docs](https://pkg.go.dev/github.com/ainsleyclark/go-mail), but we have included a\nkick-start guide below to get you started.\n\n### Creating a new client:\n\nYou can create a new driver by calling the `drivers` package and passing in a configuration type which is required to\ncreate a new mailer. Each platform requires its own data, for example, Mailgun requires a domain, but SparkPost doesn't.\nThis is based of the requirements for the API. For more details see the [examples](#Examples) below.\n\n```go\ncfg := mail.Config{\n\tURL:         \"https://api.eu.sparkpost.com\",\n\tAPIKey:      \"my-key\",\n\tFromAddress: \"hello@gophers.com\",\n\tFromName:    \"Gopher\",\n\tClient:       http.DefaultClient, // Client is optional\n}\n\nmailer, err := drivers.NewSparkpost(cfg)\nif err != nil {\n\tlog.Fatalln(err)\n}\n```\n\n### Sending Data:\n\nA transmission is required to transmit to a mailer as shown below. Once send is called, a `mail.Response` and an `error`\nbe returned indicating if the transmission was successful.\n\n```go\ntx := &mail.Transmission{\n\tRecipients: []string{\"hello@gophers.com\"},\n\tCC:         []string{\"cc@gophers.com\"},\n\tBCC:        []string{\"bcc@gophers.com\"},\n\tSubject:    \"My email\",\n\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\tPlainText:  \"Hello from Go Mail!\",\n\tHeaders: map[string]string{\n\t\t\"X-Go-Mail\": \"Test\",\n\t},\n}\n\nresult, err := mailer.Send(tx)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\nfmt.Printf(\"%+v\\n\", result)\n```\n\n### Response:\n\nThe mail response is used for debugging and inspecting results of the mailer. Below is the `Response` type.\n\n```go\n// Response represents the data passed back from a successful transmission.\ntype Response struct {\n\tStatusCode int         // e.g. 200\n\tBody       []byte      // e.g. {\"result: success\"}\n\tHeaders    http.Header // e.g. map[X-Ratelimit-Limit:[600]]\n\tID         string      // e.g \"100\"\n\tMessage    string      // e.g \"Email sent successfully\"\n}\n```\n\n### Adding attachments:\n\nAdding attachments to the transmission is as simple as passing a byte slice and filename. Go Mail takes care of the rest\nfor you.\n\n```go\nimage, err := ioutil.ReadFile(\"gopher.jpg\")\nif err != nil {\n\tlog.Fatalln(err)\n}\n\ntx := &mail.Transmission{\n\tRecipients: []string{\"hello@gophers.com\"},\n\tSubject:    \"My email\",\n\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\tPlainText:  \"plain text\",\n\tAttachments: []mail.Attachment{\n\t\t{\n\t\t\tFilename: \"gopher.jpg\",\n\t\t\tBytes:    image,\n\t\t},\n\t},\n}\n```\n\n## Examples\n\n#### Mailgun\n\n```go\ncfg := mail.Config{\nURL:         \"https://api.eu.mailgun.net\", // Or https://api.mailgun.net\n\tAPIKey:      \"my-key\",\n\tFromAddress: \"hello@gophers.com\",\n\tFromName:    \"Gopher\",\n\tDomain:      \"my-domain.com\",\n}\n\nmailer, err := drivers.NewMailgun(cfg)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\ntx := &mail.Transmission{\n\tRecipients: []string{\"hello@gophers.com\"},\n\tCC:         []string{\"cc@gophers.com\"},\n\tBCC:        []string{\"bcc@gophers.com\"},\n\tSubject:    \"My email\",\n\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\tPlainText:  \"Hello from Go Mail!\",\n}\n\nresult, err := mailer.Send(tx)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\nfmt.Printf(\"%+v\\n\", result)\n```\n\n#### Postal\n\n```go\ncfg := mail.Config{\n\tURL:         \"https://postal.example.com\",\n\tAPIKey:      \"my-key\",\n\tFromAddress: \"hello@gophers.com\",\n\tFromName:    \"Gopher\",\n}\n\nmailer, err := drivers.NewPostal(cfg)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\ntx := &mail.Transmission{\n\tRecipients: []string{\"hello@gophers.com\"},\n\tCC:         []string{\"cc@gophers.com\"},\n\tBCC:        []string{\"bcc@gophers.com\"},\n\tSubject:    \"My email\",\n\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\tPlainText:  \"Hello from Go Mail!\",\n}\n\nresult, err := mailer.Send(tx)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\nfmt.Printf(\"%+v\\n\", result)\n```\n\n#### Postmark\n\n```go\ncfg := mail.Config{\n\tAPIKey:      \"my-key\",\n\tFromAddress: \"hello@gophers.com\",\n\tFromName:    \"Gopher\",\n}\n\nmailer, err := drivers.NewPostmark(cfg)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\ntx := &mail.Transmission{\n\tRecipients: []string{\"hello@gophers.com\"},\n\tCC:         []string{\"cc@gophers.com\"},\n\tBCC:        []string{\"bcc@gophers.com\"},\n\tSubject:    \"My email\",\n\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\tPlainText:  \"Hello from Go Mail!\",\n}\n\nresult, err := mailer.Send(tx)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\nfmt.Printf(\"%+v\\n\", result)\n```\n\n#### SendGrid\n\n```go\ncfg := mail.Config{\n\tAPIKey:      \"my-key\",\n\tFromAddress: \"hello@gophers.com\",\n\tFromName:    \"Gopher\",\n}\n\nmailer, err := drivers.NewSendGrid(cfg)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\ntx := &mail.Transmission{\n\tRecipients: []string{\"hello@gophers.com\"},\n\tCC:         []string{\"cc@gophers.com\"},\n\tBCC:        []string{\"bcc@gophers.com\"},\n\tSubject:    \"My email\",\n\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\tPlainText:  \"Hello from Go Mail!\",\n}\n\nresult, err := mailer.Send(tx)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\nfmt.Printf(\"%+v\\n\", result)\n```\n\n#### SMTP\n\n```go\ncfg := mail.Config{\n\tURL:         \"smtp.gmail.com\",\n\tFromAddress: \"hello@gophers.com\",\n\tFromName:    \"Gopher\",\n\tPassword:    \"my-password\",\n\tPort:        587,\n}\n\nmailer, err := drivers.NewSMTP(cfg)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\ntx := &mail.Transmission{\n\tRecipients: []string{\"hello@gophers.com\"},\n\tCC:         []string{\"cc@gophers.com\"},\n\tBCC:        []string{\"bcc@gophers.com\"},\n\tSubject:    \"My email\",\n\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\tPlainText:  \"Hello from Go Mail!\",\n}\n\nresult, err := mailer.Send(tx)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\nfmt.Printf(\"%+v\\n\", result)\n```\n\n#### SparkPost\n\n```go\ncfg := mail.Config{\n\tURL:         \"https://api.eu.sparkpost.com\", // Or https://api.sparkpost.com/api/v1\n\tAPIKey:      \"my-key\",\n\tFromAddress: \"hello@gophers.com\",\n\tFromName:    \"Gopher\",\n}\n\nmailer, err := drivers.NewSparkPost(cfg)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\ntx := &mail.Transmission{\n\tRecipients: []string{\"hello@gophers.com\"},\n\tCC:         []string{\"cc@gophers.com\"},\n\tBCC:        []string{\"bcc@gophers.com\"},\n\tSubject:    \"My email\",\n\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\tPlainText:  \"Hello from Go Mail!\",\n}\n\nresult, err := mailer.Send(tx)\nif err != nil {\n\tlog.Fatalln(err)\n}\n\nfmt.Printf(\"%+v\\n\", result)\n```\n\n## Writing a Mailable\n\nYou have the ability to create your own custom Mailer by implementing the singular method interface shown below.\n\n```go\ntype Mailer interface {\n\t// Send accepts a mail.Transmission to send an email through a particular\n\t// driver/provider. Transmissions will be validated before sending.\n\t//\n\t// A mail.Response or an error will be returned. In some circumstances\n\t// the body and status code will be attached to the response for debugging.\n\tSend(t *mail.Transmission) (mail.Response, error)\n}\n```\n\n## Debugging\n\nTo debug any errors or issues you are facing with Go Mail, you are able to change the `Debug` variable in the\n`mail` package. This will write the HTTP requests in curl to stdout. Additional information will also be\ndisplayed in the errors such as method operations.\n\n```go\nmail.Debug = true\n```\n\n## Development\n\n### Setup\n\nTo get set up with Go Mail simply clone the repo and run the following:\n\n```bash\ngo get github.com/vektra/mockery/v2/.../\nmake setup\nmake mocks\n```\n\n## Env\n\nAll secrets are contained within the `.env` file for testing drivers. To begin with, make a copy of the `.env.example`\nfile and name it `.env`. You can the set the environment variables to match your credentials for the mail drivers.\n\nYou can set the recipients of emails by modifying the `EMAIL` variables as show below.\n\n- `EMAIL_TO`: Recipients of test emails in a comma delimited list.\n- `EMAIL_CC`: CC recipients of test emails in a comma delimited list.\n- `EMAIL_BCC`: BCC recipients of test emails in a comma delimited list.\n\n### Testing\n\nTo run all driver tests, execute the following command:\n\n```bash\nmake test-driver\n```\n\nTo run a specific driver test, prepend the `driver` flag as show below:\n\n```bash\nmake test-driver driver=sparkpost\n```\n\nThe driver flag can be one of the following:\n\n- `mailgun`\n- `postal`\n- `postmark`\n- `sendgrid`\n- `smtp`\n- `sparkpost`\n\n## Contributing\n\nWe welcome contributors, but please read the [contributing document](CONTRIBUTING.md) before making a pull request.\n\n## Credits\n\nShout out to the incredible [Maria Letta](https://github.com/MariaLetta) for her excellent Gopher illustrations.\n\n## Licence\n\nCode Copyright 2022 Go Mail. Code released under the [MIT Licence](LICENCE).\n"
  },
  {
    "path": "bin/tag.sh",
    "content": "#!/bin/bash\n#\n# tag.sh\n#\n\n# Set variables\nversion=$1\nmessage=$2\n\n# Check version is not empty\nif [[ $version == \"\" ]]\n  then\n    echo \"Add Version number\"\n    exit\nfi\n\n# Check commit message is not empty\nif [[ $message == \"\" ]]\n  then\n    echo \"Add commit message\"\n    exit\nfi\n\necho \"Releasing version: \" $version\n\ngit tag -a \"$version\" -m \"$message\"\ngit push origin $version\n"
  },
  {
    "path": "bin/tests.sh",
    "content": "#!/usr/bin/bash\n\n# Shell script for executing tests based on input.\n# Usage:\n# ./tests.sh for all drivers\n# ./tests.sh sparkpost for a particular driver\n# Author - Ainsley Clark\n\nDRIVER=$1\n\ndeclare -A tests=(\n\t[\"mailgun\"]=\"Test_MailGun\"\n\t[\"postal\"]=\"Test_Postal\"\n\t[\"postmark\"]=\"Test_Postmark\"\n\t[\"sendgrid\"]=\"Test_SendGrid\"\n\t[\"smtp\"]=\"Test_SMTP\"\n\t[\"sparkpost\"]=\"Test_SparkPost\"\n)\n\nif [ -z \"$DRIVER\" ]\nthen\n\tfor name in \"${!tests[@]}\";\n\t\tdo go test -v ./tests/ -run \"${tests[$name]}\";\n\tdone\nelse\n\tgo test -v ./tests/ -run \"${tests[\"$DRIVER\"]}\";\nfi\n"
  },
  {
    "path": "drivers/drivers.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport \"github.com/ainsleyclark/go-mail/internal/httputil\"\n\nvar (\n\t// newJSONData is an alias for httputil.NewJSONData\n\t// for creating JSON payloads.\n\tnewJSONData = httputil.NewJSONData\n\t// formDataFn is an alias for httputil.NewFormData\n\t// for creating form data payloads.\n\tnewFormData = httputil.NewFormData\n)\n"
  },
  {
    "path": "drivers/drivers_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"errors\"\n\t\"github.com/ainsleyclark/go-mail/internal/httputil\"\n\t\"github.com/ainsleyclark/go-mail/internal/mocks/client\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/suite\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\n// DriversTestSuite defines the helper used for mail\n// testing.\ntype DriversTestSuite struct {\n\tsuite.Suite\n\tbase string\n}\n\n// Assert testing has begun.\nfunc TestMail(t *testing.T) {\n\tsuite.Run(t, new(DriversTestSuite))\n}\n\n// Assigns test base.\nfunc (t *DriversTestSuite) SetupSuite() {\n\twd, err := os.Getwd()\n\tt.NoError(err)\n\tt.base = wd\n}\n\nconst (\n\t// DataPath defines where the test data resides.\n\tDataPath = \"testdata\"\n)\n\nvar (\n\t// Trans is the transmission used for testing.\n\tTrans = &mail.Transmission{\n\t\tRecipients: []string{\"recipient@test.com\"},\n\t\tCC:         []string{\"cc@test.com\"},\n\t\tBCC:        []string{\"bcc@test.com\"},\n\t\tSubject:    \"Subject\",\n\t\tHTML:       \"<h1>HTML</h1>\",\n\t\tPlainText:  \"PlainText\",\n\t\tHeaders: map[string]string{\n\t\t\t\"X-Go-Mail\": \"Test\",\n\t\t},\n\t}\n\t// Trans is the transmission with an\n\t// attachment used for testing.\n\tTransWithAttachment = &mail.Transmission{\n\t\tRecipients:  []string{\"recipient@test.com\"},\n\t\tSubject:     \"Subject\",\n\t\tHTML:        \"<h1>HTML</h1>\",\n\t\tPlainText:   \"PlainText\",\n\t\tAttachments: []mail.Attachment{{Filename: \"test.jpg\"}},\n\t}\n\t// Config is the default configuration used\n\t// for testing.\n\tComfig = mail.Config{\n\t\tURL:         \"my-url\",\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t\tDomain:      \"my-domain\",\n\t}\n)\n\n// Returns a PNG attachment for testing.\nfunc (t *DriversTestSuite) Attachment(name string) mail.Attachment {\n\tpath := filepath.Join(t.base, DataPath, name)\n\tfile, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fail(\"error getting attachment with the path: \"+path, err)\n\t}\n\treturn mail.Attachment{\n\t\tFilename: name,\n\t\tBytes:    file,\n\t}\n}\n\nfunc (t *DriversTestSuite) UtilTestUnmarshal(r httputil.Responder, buf []byte) {\n\terrBuf := []byte(\"wrong\")\n\terr := r.Unmarshal(errBuf)\n\tt.Error(err)\n\terr = r.Unmarshal(buf)\n\tt.NoError(err)\n}\n\nfunc (t *DriversTestSuite) UtilTestMeta(r httputil.Responder, message, id string) {\n\tgot := r.Meta()\n\tt.Equal(message, got.Message)\n\tt.Equal(id, got.ID)\n}\n\nfunc (t *DriversTestSuite) UtilTestSend(fn func(m *mocks.Requester) mail.Mailer, json bool) {\n\tres := mail.Response{\n\t\tStatusCode: http.StatusOK,\n\t\tBody:       []byte(\"body\"),\n\t\tHeaders:    nil,\n\t\tID:         \"1\",\n\t\tMessage:    \"success\",\n\t}\n\n\ttt := map[string]struct {\n\t\tinput  *mail.Transmission\n\t\tmock   func(m *mocks.Requester)\n\t\tjsonFn func(obj interface{}) (*httputil.JSONData, error)\n\t\twant   interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\tTrans,\n\t\t\tfunc(m *mocks.Requester) {\n\t\t\t\tm.On(\"Do\", mock.Anything, mock.Anything, mock.Anything, mock.Anything).\n\t\t\t\t\tReturn(res, nil)\n\t\t\t},\n\t\t\thttputil.NewJSONData,\n\t\t\tres,\n\t\t},\n\t\t\"With Attachment\": {\n\t\t\tTransWithAttachment,\n\t\t\tfunc(m *mocks.Requester) {\n\t\t\t\tm.On(\"Do\", mock.Anything, mock.Anything, mock.Anything, mock.Anything).\n\t\t\t\t\tReturn(res, nil)\n\t\t\t},\n\t\t\thttputil.NewJSONData,\n\t\t\tres,\n\t\t},\n\t\t\"Validation Failed\": {\n\t\t\tnil,\n\t\t\tnil,\n\t\t\thttputil.NewJSONData,\n\t\t\t\"can't validate a nil transmission\",\n\t\t},\n\t\t\"JSON Error\": {\n\t\t\tTrans,\n\t\t\tfunc(m *mocks.Requester) {\n\t\t\t\tm.On(\"Do\", mock.Anything, mock.Anything, mock.Anything, mock.Anything).\n\t\t\t\t\tReturn(mail.Response{}, errors.New(\"send error\"))\n\t\t\t},\n\t\t\tfunc(obj interface{}) (*httputil.JSONData, error) {\n\t\t\t\treturn nil, errors.New(\"json error\")\n\t\t\t},\n\t\t\t\"json error\",\n\t\t},\n\t\t\"Send Error\": {\n\t\t\tTrans,\n\t\t\tfunc(m *mocks.Requester) {\n\t\t\t\tm.On(\"Do\", mock.Anything, mock.Anything, mock.Anything, mock.Anything).\n\t\t\t\t\tReturn(mail.Response{}, errors.New(\"send error\"))\n\t\t\t},\n\t\t\thttputil.NewJSONData,\n\t\t\t\"send error\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tif name == \"JSON Error\" && !json {\n\t\t\tcontinue\n\t\t}\n\n\t\tt.Run(name, func() {\n\t\t\torig := newJSONData\n\t\t\tdefer func() { newJSONData = orig }()\n\t\t\tnewJSONData = test.jsonFn\n\n\t\t\trequester := &mocks.Requester{}\n\t\t\tif test.mock != nil {\n\t\t\t\ttest.mock(requester)\n\t\t\t}\n\n\t\t\tm := fn(requester)\n\n\t\t\tgot, err := m.Send(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.Equal(test.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "drivers/mailgun.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/internal/client\"\n\t\"github.com/ainsleyclark/go-mail/internal/httputil\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// mailgun represents the entity for sending mail via the\n// Mailgun API.\n//\n// See:\n// https://documentation.mailgun.com/en/latest/api_reference.html\n// https://documentation.mailgun.com/en/latest/api-sending.html\ntype mailGun struct {\n\tcfg    mail.Config\n\tclient client.Requester\n}\n\nconst (\n\t// mailgunEndpoint defines the endpoint to POST to.\n\tmailgunEndpoint = \"/v3/%s/messages\"\n)\n\n// NewMailgun creates a new Mailgun client. Configuration\n// is validated before initialisation.\nfunc NewMailgun(cfg mail.Config) (mail.Mailer, error) {\n\terr := cfg.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif cfg.Domain == \"\" {\n\t\treturn nil, errors.New(\"driver requires a domain\")\n\t}\n\treturn &mailGun{\n\t\tcfg:    cfg,\n\t\tclient: client.New(cfg.Client),\n\t}, nil\n}\n\ntype (\n\t// mailGunResponse defines the data sent back from the MailGun API.\n\t// ID is included on successful transmission.\n\t//\n\t// Example JSON Responses:\n\t// {\"id\":\"<20211229082318.a988bed7abe472bd@sandboxa6807a568a404524b2b216817d7ed775.mailgun.org>\",\"message\":\"Queued. Thank you.\"}\n\t// {\"message\":\"Need at least one of 'text' or 'html' parameters specified\"}\n\t// {\"message\":\"from parameter is missing\"}\n\tmailgunResponse struct {\n\t\tMessage string `json:\"message\"`\n\t\tID      string `json:\"id,omitempty\"`\n\t}\n)\n\nfunc (r *mailgunResponse) Unmarshal(buf []byte) error {\n\tresp := &mailgunResponse{}\n\terr := json.Unmarshal(buf, resp)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*r = *resp\n\treturn nil\n}\n\nfunc (r *mailgunResponse) CheckError(response *http.Response, buf []byte) error {\n\tif client.Is2XX(response.StatusCode) {\n\t\treturn nil\n\t}\n\tif len(buf) == 0 {\n\t\treturn mail.ErrEmptyBody\n\t}\n\treturn errors.New(r.Message)\n}\n\nfunc (r *mailgunResponse) Meta() httputil.Meta {\n\treturn httputil.Meta{\n\t\tMessage: r.Message,\n\t\tID:      r.ID,\n\t}\n}\n\nfunc (m *mailGun) Send(t *mail.Transmission) (mail.Response, error) {\n\terr := t.Validate()\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\tf := newFormData()\n\tf.AddValue(\"from\", fmt.Sprintf(\"%s <%s>\", m.cfg.FromName, m.cfg.FromAddress))\n\tf.AddValue(\"subject\", t.Subject)\n\tf.AddValue(\"html\", t.HTML)\n\tf.AddValue(\"text\", t.PlainText)\n\n\tfor _, to := range t.Recipients {\n\t\tf.AddValue(\"to\", to)\n\t}\n\n\tif t.HasCC() {\n\t\tfor _, c := range t.CC {\n\t\t\tf.AddValue(\"cc\", c)\n\t\t}\n\t}\n\n\tif t.HasBCC() {\n\t\tfor _, b := range t.BCC {\n\t\t\tf.AddValue(\"bcc\", b)\n\t\t}\n\t}\n\n\tif t.HasAttachments() {\n\t\tfor _, v := range t.Attachments {\n\t\t\tf.AddBuffer(\"attachment\", v.Filename, v.Bytes)\n\t\t}\n\t}\n\n\tfor k, v := range t.Headers {\n\t\tf.AddValue(\"h:\"+k, v)\n\t}\n\n\turl := fmt.Sprintf(\"%s/%s\", m.cfg.URL, strings.TrimPrefix(fmt.Sprintf(mailgunEndpoint, m.cfg.Domain), \"/\"))\n\treq := httputil.NewHTTPRequest(http.MethodPost, url)\n\treq.SetBasicAuth(\"api\", m.cfg.APIKey)\n\n\treturn m.client.Do(context.Background(), req, f, &mailgunResponse{})\n}\n"
  },
  {
    "path": "drivers/mailgun_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"errors\"\n\tmocks \"github.com/ainsleyclark/go-mail/internal/mocks/client\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc ExampleNewMailgun() {\n\tcfg := mail.Config{\n\t\tURL:         \"https://api.eu.mailgun.net\", // Or https://api.mailgun.net\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t\tDomain:      \"my-domain.com\",\n\t}\n\n\t_, err := NewMailgun(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n}\n\nfunc (t *DriversTestSuite) TestNewMailGun() {\n\ttt := map[string]struct {\n\t\tinput mail.Config\n\t\twant  interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\tmail.Config{\n\t\t\t\tURL:         \"https://mailgun.example.com\",\n\t\t\t\tFromAddress: \"addr\",\n\t\t\t\tFromName:    \"name\",\n\t\t\t\tAPIKey:      \"key\",\n\t\t\t\tDomain:      \"domain\",\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t\"Validation Failed\": {\n\t\t\tmail.Config{},\n\t\t\t\"driver requires from address\",\n\t\t},\n\t\t\"No Domain\": {\n\t\t\tmail.Config{\n\t\t\t\tFromName:    \"name\",\n\t\t\t\tFromAddress: \"hello@gophers.com\",\n\t\t\t\tAPIKey:      \"key\",\n\t\t\t},\n\t\t\t\"driver requires a domain\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tgot, err := NewMailgun(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.NotNil(got)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestMailgunResponse_Unmarshal() {\n\tt.UtilTestUnmarshal(&mailgunResponse{}, []byte(`{\"message\": \"Hello\"}`))\n}\n\nfunc (t *DriversTestSuite) TestMailgunResponse_CheckError() {\n\ttt := map[string]struct {\n\t\tresponse *http.Response\n\t\tbuf      []byte\n\t\twant     error\n\t}{\n\t\t\"2xx\": {\n\t\t\t&http.Response{StatusCode: http.StatusOK},\n\t\t\t[]byte(\"test\"),\n\t\t\tnil,\n\t\t},\n\t\t\"Empty Body\": {\n\t\t\t&http.Response{StatusCode: http.StatusInternalServerError},\n\t\t\tnil,\n\t\t\tmail.ErrEmptyBody,\n\t\t},\n\t\t\"Error\": {\n\t\t\t&http.Response{StatusCode: http.StatusInternalServerError},\n\t\t\t[]byte(\"test\"),\n\t\t\terrors.New(\"error\"),\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tresp := mailgunResponse{Message: \"error\"}\n\t\t\terr := resp.CheckError(test.response, test.buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.Equal(test.want, err)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestMailgunResponse_Meta() {\n\td := &mailgunResponse{Message: \"Success\", ID: \"id\"}\n\tt.UtilTestMeta(d, d.Message, d.ID)\n}\n\nfunc (t *DriversTestSuite) TestMailGun_Send() {\n\tt.UtilTestSend(func(m *mocks.Requester) mail.Mailer {\n\t\treturn &mailGun{cfg: Comfig, client: m}\n\t}, false)\n}\n"
  },
  {
    "path": "drivers/postal.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/internal/client\"\n\t\"github.com/ainsleyclark/go-mail/internal/httputil\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"net/http\"\n)\n\n// postal represents the entity for sending mail via the\n// Postal API.\n//\n// See:\n// https://docs.postalserver.io/developer/api\n// https://apiv1.postalserver.io/controllers/send/message.html\ntype postal struct {\n\tcfg    mail.Config\n\tclient client.Requester\n}\n\nconst (\n\t// postalEndpoint defines the endpoint to POST to.\n\tpostalEndpoint = \"%s/api/v1/send/message\"\n\t// postalErrorMessage defines the message when an error occurred\n\t// when sending mail via the Postal API.\n\tpostalErrorMessage = \"error sending transmission to Postal API\"\n)\n\n// NewPostal creates a new Postal client. Configuration\n// is validated before initialisation.\nfunc NewPostal(cfg mail.Config) (mail.Mailer, error) {\n\terr := cfg.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &postal{\n\t\tcfg:    cfg,\n\t\tclient: client.New(cfg.Client),\n\t}, nil\n}\n\ntype (\n\t// postalTransmission defines the data to be sent to the Postal API.\n\tpostalTransmission struct {\n\t\tTo          []string           `json:\"to\"`\n\t\tCC          []string           `json:\"cc\"`\n\t\tBCC         []string           `json:\"bcc\"`\n\t\tFrom        string             `json:\"from\"`\n\t\tSender      string             `json:\"sender\"`\n\t\tSubject     string             `json:\"subject\"`\n\t\tHTML        string             `json:\"html_body\"`\n\t\tPlainText   string             `json:\"plain_body\"`\n\t\tAttachments []postalAttachment `json:\"attachments\"`\n\t\tHeaders     map[string]string  `json:\"headers\"`\n\t}\n\t// postalAttachment defines a singular Postal mail attachment.\n\tpostalAttachment struct {\n\t\tName        string `json:\"name\"`\n\t\tContentType string `json:\"content_type\"`\n\t\tData        string `json:\"data\"`\n\t}\n\t// postalResponse defines the data sent back from the Postal API.\n\t// Status can either be \"success\" or \"error\" and data is\n\t// dynamic dependent on if an error occurred during processing.\n\t//\n\t// Example JSON Responses:\n\t// {\"status\":\"success\",\"time\":0.08,\"flags\":{},\"data\":{\"message_id\":\"080c21de-52f9-4be1-9cbe-19d63450949c@rp.postal.example.com\",\"messages\":{\"info@ainsleyclark.com\":{\"id\":28,\"token\":\"WEjrFfpnynRm\"}}}}\n\t// {\"status\":\"error\",\"time\":0.0,\"flags\":{},\"data\":{\"code\":\"NoRecipients\",\"message\":\"There are no recipients defined to receive this message\"}}\n\tpostalResponse struct {\n\t\tStatus string                 `json:\"status\"`\n\t\tTime   float32                `json:\"time\"`\n\t\tFlags  map[string]interface{} `json:\"flags\"`\n\t\tData   map[string]interface{} `json:\"data\"`\n\t}\n)\n\nfunc (r *postalResponse) Unmarshal(buf []byte) error {\n\tresp := &postalResponse{}\n\terr := json.Unmarshal(buf, resp)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*r = *resp\n\treturn nil\n}\n\nfunc (r *postalResponse) CheckError(response *http.Response, buf []byte) error {\n\tif r.Status == \"success\" {\n\t\treturn nil\n\t}\n\tif len(buf) == 0 {\n\t\treturn mail.ErrEmptyBody\n\t}\n\tmsg := postalErrorMessage\n\tif code, ok := r.Data[\"code\"]; ok {\n\t\tmsg = fmt.Sprintf(\"%s - code: %s\", msg, code)\n\t}\n\tif message, ok := r.Data[\"message\"]; ok {\n\t\tmsg = fmt.Sprintf(\"%s, message: %s\", msg, message)\n\t}\n\treturn errors.New(msg)\n}\n\nfunc (r *postalResponse) Meta() httputil.Meta {\n\tm := httputil.Meta{\n\t\tMessage: \"Successfully sent Postal email\",\n\t}\n\tif val, ok := r.Data[\"message_id\"]; ok {\n\t\tm.ID = fmt.Sprintf(\"%v\", val)\n\t}\n\treturn m\n}\n\nfunc (d *postal) Send(t *mail.Transmission) (mail.Response, error) {\n\terr := t.Validate()\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\ttx := postalTransmission{\n\t\tTo:        t.Recipients,\n\t\tCC:        t.CC,\n\t\tBCC:       t.BCC,\n\t\tFrom:      d.cfg.FromAddress,\n\t\tSender:    d.cfg.FromName,\n\t\tSubject:   t.Subject,\n\t\tHTML:      t.HTML,\n\t\tPlainText: t.PlainText,\n\t}\n\n\tif t.HasAttachments() {\n\t\tfor _, v := range t.Attachments {\n\t\t\ttx.Attachments = append(tx.Attachments, postalAttachment{\n\t\t\t\tName:        v.Filename,\n\t\t\t\tContentType: v.Mime(),\n\t\t\t\tData:        v.B64(),\n\t\t\t})\n\t\t}\n\t}\n\n\ttx.Headers = t.Headers\n\n\tpl, err := newJSONData(tx)\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\treq := httputil.NewHTTPRequest(http.MethodPost, fmt.Sprintf(postalEndpoint, d.cfg.URL))\n\treq.AddHeader(\"X-Server-API-Key\", d.cfg.APIKey)\n\n\treturn d.client.Do(context.Background(), req, pl, &postalResponse{})\n}\n"
  },
  {
    "path": "drivers/postal_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"fmt\"\n\tmocks \"github.com/ainsleyclark/go-mail/internal/mocks/client\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc ExampleNewPostal() {\n\tcfg := mail.Config{\n\t\tURL:         \"https://postal.example.com\",\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t}\n\n\t_, err := NewPostal(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n}\n\nfunc (t *DriversTestSuite) TestNewPostal() {\n\ttt := map[string]struct {\n\t\tinput mail.Config\n\t\twant  interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\tmail.Config{\n\t\t\t\tURL:         \"https://postal.example.com\",\n\t\t\t\tAPIKey:      \"key\",\n\t\t\t\tFromAddress: \"addr\",\n\t\t\t\tFromName:    \"name\",\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t\"Validation Failed\": {\n\t\t\tmail.Config{},\n\t\t\t\"driver requires from address\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tgot, err := NewPostal(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.NotNil(got)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestPostalResponse_Unmarshal() {\n\tt.UtilTestUnmarshal(&postalResponse{}, []byte(`{\"status\": \"success\"}`))\n}\n\nfunc (t *DriversTestSuite) TestPostalResponse_CheckError() {\n\ttt := map[string]struct {\n\t\tinput    postalResponse\n\t\tresponse *http.Response\n\t\tbuf      []byte\n\t\twant     error\n\t}{\n\t\t\"Success\": {\n\t\t\tpostalResponse{Status: \"success\"},\n\t\t\t&http.Response{StatusCode: http.StatusOK},\n\t\t\t[]byte(\"test\"),\n\t\t\tnil,\n\t\t},\n\t\t\"Empty Body\": {\n\t\t\tpostalResponse{},\n\t\t\t&http.Response{StatusCode: http.StatusInternalServerError},\n\t\t\tnil,\n\t\t\tmail.ErrEmptyBody,\n\t\t},\n\t\t\"Error\": {\n\t\t\tpostalResponse{Data: map[string]interface{}{\"code\": \"code\", \"message\": \"message\"}},\n\t\t\t&http.Response{StatusCode: http.StatusInternalServerError},\n\t\t\t[]byte(\"test\"),\n\t\t\tfmt.Errorf(\"%s - code: code, message: message\", postalErrorMessage),\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\terr := test.input.CheckError(test.response, test.buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.Equal(test.want, err)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestPostalResponse_Meta() {\n\td := &postalResponse{\n\t\tData: map[string]interface{}{\"message_id\": 10},\n\t}\n\tt.UtilTestMeta(d, \"Successfully sent Postal email\", \"10\")\n}\n\nfunc (t *DriversTestSuite) TestPostal_Send() {\n\tt.UtilTestSend(func(m *mocks.Requester) mail.Mailer {\n\t\treturn &postal{cfg: Comfig, client: m}\n\t}, true)\n}\n"
  },
  {
    "path": "drivers/postmark.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/internal/client\"\n\t\"github.com/ainsleyclark/go-mail/internal/httputil\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// postal represents the entity for sending mail via the\n// Postmark API.\n//\n// See: https://postmarkapp.com/developer/api/email-api\ntype postmark struct {\n\tcfg    mail.Config\n\tclient client.Requester\n}\n\nconst (\n\t// postalEndpoint defines the endpoint to POST to.\n\tpostmarkEndpoint = \"https://api.postmarkapp.com/email\"\n\t// postmarkErrorMessage defines the message when an error occurred\n\t// when sending mail via the Postmark API.\n\tpostmarkErrorMessage = \"error sending transmission to Postmark API\"\n)\n\n// NewPostmark creates a new Postmark client. Configuration\n// is validated before initialisation.\nfunc NewPostmark(cfg mail.Config) (mail.Mailer, error) {\n\terr := cfg.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &postmark{\n\t\tcfg:    cfg,\n\t\tclient: client.New(cfg.Client),\n\t}, nil\n}\n\ntype (\n\t// postmarkTransmission defines the data to be sent to the Postmark API.\n\tpostmarkTransmission struct {\n\t\tFrom        string               `json:\"From\"`\n\t\tTo          string               `json:\"To\"`\n\t\tCC          string               `json:\"Cc\"`\n\t\tBCC         string               `json:\"Bcc\"`\n\t\tSubject     string               `json:\"Subject\"`\n\t\tTag         string               `json:\"Tag\"`\n\t\tHTML        string               `json:\"HtmlBody\"`\n\t\tPlainText   string               `json:\"TextBody\"`\n\t\tReplyTo     string               `json:\"ReplyTo\"`\n\t\tHeaders     []postmarkHeader     `json:\"headers\"`\n\t\tTrackOpens  bool                 `json:\"TrackOpens\"`\n\t\tTrackLinks  string               `json:\"TrackLinks\"`\n\t\tAttachments []postmarkAttachment `json:\"Attachments\"`\n\t\tMetadata    struct {\n\t\t\tColor    string `json:\"color\"`\n\t\t\tClientID string `json:\"client-id\"`\n\t\t} `json:\"Metadata\"`\n\t\tMessageStream string `json:\"MessageStream\"`\n\t}\n\t// postmarkHeaders defines the key value pair of custom headers\n\t// to send with the email.\n\tpostmarkHeader struct {\n\t\tName  string `json:\"Name\"`\n\t\tValue string `json:\"Value\"`\n\t}\n\t// postmarkAttachment defines a singular Postmark mail attachment.\n\tpostmarkAttachment struct {\n\t\tName        string `json:\"Name\"`\n\t\tContent     string `json:\"Content\"`\n\t\tContentType string `json:\"ContentType\"`\n\t\tContentID   string `json:\"ContentID,omitempty\"`\n\t}\n\t// postmarkResponse defines the data sent back from the Postmark API.\n\t// An error code of 0 represents a successful transmission.\n\t//\n\t// Example JSON Responses:\n\t// {\"To\":\"info@ainsleyclark.com\",\"SubmittedAt\":\"2021-12-29T15:58:17.8637679Z\",\"MessageID\":\"947125ed-9e43-4dce-b66c-def49198b3d3\",\"ErrorCode\":0,\"Message\":\"OK\"}\n\t// {\"ErrorCode\":300,\"Message\":\"Zero recipients specified\"}\n\tpostmarkResponse struct {\n\t\tTo          string    `json:\"To\"`\n\t\tSubmittedAt time.Time `json:\"SubmittedAt\"`\n\t\tID          string    `json:\"MessageID\"`\n\t\tErrorCode   int       `json:\"ErrorCode\"`\n\t\tMessage     string    `json:\"Message\"`\n\t}\n)\n\nfunc (r *postmarkResponse) Unmarshal(buf []byte) error {\n\tresp := &postmarkResponse{}\n\terr := json.Unmarshal(buf, resp)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*r = *resp\n\treturn nil\n}\n\nfunc (r *postmarkResponse) CheckError(response *http.Response, buf []byte) error {\n\tif r.ErrorCode == 0 {\n\t\treturn nil\n\t}\n\tif len(buf) == 0 {\n\t\treturn mail.ErrEmptyBody\n\t}\n\treturn fmt.Errorf(\"%s - code: %d, message: %s\", postmarkErrorMessage, r.ErrorCode, r.Message)\n}\n\nfunc (r *postmarkResponse) Meta() httputil.Meta {\n\treturn httputil.Meta{\n\t\tMessage: r.Message,\n\t\tID:      r.ID,\n\t}\n}\n\nfunc (d *postmark) Send(t *mail.Transmission) (mail.Response, error) {\n\terr := t.Validate()\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\ttx := postmarkTransmission{\n\t\tTo:            strings.Join(t.Recipients, \",\"),\n\t\tCC:            strings.Join(t.CC, \",\"),\n\t\tBCC:           strings.Join(t.BCC, \",\"),\n\t\tFrom:          fmt.Sprintf(\"%s <%s>\", d.cfg.FromName, d.cfg.FromAddress),\n\t\tSubject:       t.Subject,\n\t\tHTML:          t.HTML,\n\t\tPlainText:     t.PlainText,\n\t\tMessageStream: \"outbound\",\n\t}\n\n\tif t.HasAttachments() {\n\t\tfor _, v := range t.Attachments {\n\t\t\ttx.Attachments = append(tx.Attachments, postmarkAttachment{\n\t\t\t\tName:        v.Filename,\n\t\t\t\tContentType: v.Mime(),\n\t\t\t\tContent:     v.B64(),\n\t\t\t})\n\t\t}\n\t}\n\n\tfor k, v := range t.Headers {\n\t\ttx.Headers = append(tx.Headers, postmarkHeader{\n\t\t\tName:  k,\n\t\t\tValue: v,\n\t\t})\n\t}\n\n\tpl, err := newJSONData(tx)\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\treq := httputil.NewHTTPRequest(http.MethodPost, postmarkEndpoint)\n\treq.AddHeader(\"X-Postmark-Server-Token\", d.cfg.APIKey)\n\n\treturn d.client.Do(context.Background(), req, pl, &postmarkResponse{})\n}\n"
  },
  {
    "path": "drivers/postmark_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"fmt\"\n\tmocks \"github.com/ainsleyclark/go-mail/internal/mocks/client\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc ExampleNewPostmark() {\n\tcfg := mail.Config{\n\t\tURL:         \"https://postal.example.com\",\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t}\n\n\t_, err := NewPostal(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n}\n\nfunc (t *DriversTestSuite) TestNewPostmark() {\n\ttt := map[string]struct {\n\t\tinput mail.Config\n\t\twant  interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\tmail.Config{\n\t\t\t\tAPIKey:      \"key\",\n\t\t\t\tFromAddress: \"addr\",\n\t\t\t\tFromName:    \"name\",\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t\"Validation Failed\": {\n\t\t\tmail.Config{},\n\t\t\t\"driver requires from address\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tgot, err := NewPostmark(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.NotNil(got)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestPostmarkResponse_Unmarshal() {\n\tt.UtilTestUnmarshal(&postmarkResponse{}, []byte(`{\"message\": \"Hello\"}`))\n}\n\nfunc (t *DriversTestSuite) TestPostmarkResponse_CheckError() {\n\ttt := map[string]struct {\n\t\tinput    postmarkResponse\n\t\tresponse *http.Response\n\t\tbuf      []byte\n\t\twant     error\n\t}{\n\t\t\"Success\": {\n\t\t\tpostmarkResponse{ErrorCode: 0},\n\t\t\t&http.Response{StatusCode: http.StatusOK},\n\t\t\t[]byte(\"test\"),\n\t\t\tnil,\n\t\t},\n\t\t\"Empty Body\": {\n\t\t\tpostmarkResponse{ErrorCode: 10},\n\t\t\t&http.Response{StatusCode: http.StatusInternalServerError},\n\t\t\tnil,\n\t\t\tmail.ErrEmptyBody,\n\t\t},\n\t\t\"Error\": {\n\t\t\tpostmarkResponse{ErrorCode: 10, Message: \"message\"},\n\t\t\t&http.Response{StatusCode: http.StatusInternalServerError},\n\t\t\t[]byte(\"test\"),\n\t\t\tfmt.Errorf(\"%s - code: 10, message: message\", postmarkErrorMessage),\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\terr := test.input.CheckError(test.response, test.buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.Equal(test.want, err)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestPostmarkResponse_Meta() {\n\td := &postmarkResponse{Message: \"Success\", ID: \"id\"}\n\tt.UtilTestMeta(d, d.Message, d.ID)\n}\n\nfunc (t *DriversTestSuite) TestPostmark_Send() {\n\tt.UtilTestSend(func(m *mocks.Requester) mail.Mailer {\n\t\treturn &postmark{cfg: Comfig, client: m}\n\t}, true)\n}\n"
  },
  {
    "path": "drivers/sendgrid.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/internal/client\"\n\t\"github.com/ainsleyclark/go-mail/internal/httputil\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"net/http\"\n)\n\n// sendGrid represents the entity for sending mail via the\n// SendGrid API.\n//\n// See:\n// https://docs.sendgrid.com/api-reference/how-to-use-the-sendgrid-v3-api\n// https://docs.sendgrid.com/api-reference/mail-send/mail-send\ntype sendGrid struct {\n\tcfg    mail.Config\n\tclient client.Requester\n}\n\nconst (\n\t// sendGridEndpoint defines the endpoint to POST to.\n\t// The host for Web API v3 requests is always https://sendgrid.com/v3/\n\tsendGridEndpoint = \"https://api.sendgrid.com/v3/mail/send\"\n\t// sendgridErrorMessage defines the message when an error occurred\n\t// when sending mail via the SendGrid API.\n\tsendgridErrorMessage = \"error sending transmission to SendGrid API\"\n)\n\n// NewSendGrid creates a new sendGrid client. Configuration\n// is validated before initialisation.\nfunc NewSendGrid(cfg mail.Config) (mail.Mailer, error) {\n\terr := cfg.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &sendGrid{\n\t\tcfg:    cfg,\n\t\tclient: client.New(cfg.Client),\n\t}, nil\n}\n\ntype (\n\t// postalTransmission defines the data to be sent to the sendGrid API.\n\tsgTransmission struct {\n\t\tFrom             *sgEmail             `json:\"from,omitempty\"`\n\t\tSubject          string               `json:\"subject,omitempty\"`\n\t\tPersonalizations []*sgPersonalization `json:\"personalizations,omitempty\"`\n\t\tContent          []*sgContent         `json:\"content,omitempty\"`\n\t\tAttachments      []*sgAttachment      `json:\"attachments,omitempty\"`\n\t\tTemplateID       string               `json:\"template_id,omitempty\"`\n\t\tSections         map[string]string    `json:\"sections,omitempty\"`\n\t\tHeaders          map[string]string    `json:\"headers,omitempty\"`\n\t\tCategories       []string             `json:\"categories,omitempty\"`\n\t\tCustomArgs       map[string]string    `json:\"custom_args,omitempty\"`\n\t\tSendAt           int                  `json:\"send_at,omitempty\"`\n\t\tBatchID          string               `json:\"batch_id,omitempty\"`\n\t\tIPPoolID         string               `json:\"ip_pool_name,omitempty\"`\n\t\tReplyTo          *sgEmail             `json:\"reply_to,omitempty\"`\n\t}\n\t// sgPersonalization holds the mail body struct.\n\tsgPersonalization struct {\n\t\tTo                  []*sgEmail             `json:\"to,omitempty\"`\n\t\tFrom                *sgEmail               `json:\"from,omitempty\"`\n\t\tCC                  []*sgEmail             `json:\"cc,omitempty\"`\n\t\tBCC                 []*sgEmail             `json:\"bcc,omitempty\"`\n\t\tSubject             string                 `json:\"subject,omitempty\"`\n\t\tHeaders             map[string]string      `json:\"headers,omitempty\"`\n\t\tSubstitutions       map[string]string      `json:\"substitutions,omitempty\"`\n\t\tCustomArgs          map[string]string      `json:\"custom_args,omitempty\"`\n\t\tDynamicTemplateData map[string]interface{} `json:\"dynamic_template_data,omitempty\"`\n\t\tCategories          []string               `json:\"categories,omitempty\"`\n\t\tSendAt              int                    `json:\"send_at,omitempty\"`\n\t}\n\t// sgEmail holds email name and address info.\n\tsgEmail struct {\n\t\tName    string `json:\"name,omitempty\"`\n\t\tAddress string `json:\"email,omitempty\"`\n\t}\n\t// sgContent defines content of the mail body.\n\tsgContent struct {\n\t\tType  string `json:\"type,omitempty\"`\n\t\tValue string `json:\"value,omitempty\"`\n\t}\n\t// sgAttachment holds attachment information.\n\tsgAttachment struct {\n\t\tContent     string `json:\"content,omitempty\"`\n\t\tType        string `json:\"type,omitempty\"`\n\t\tName        string `json:\"name,omitempty\"`\n\t\tFilename    string `json:\"filename,omitempty\"`\n\t\tDisposition string `json:\"disposition,omitempty\"`\n\t\tContentID   string `json:\"content_id,omitempty\"`\n\t}\n\t// sgResponse contains the response data from the SendGrid\n\t// API.\n\t// Note: No response data is passed if the response code is 2xx\n\t//\n\t// Example JSON Response:\n\t// {\"errors\":[{\"message\":\"The from object must be provided for every email send. It is an object that requires the email parameter, but may also contain a name parameter.  e.g. {\\\"email\\\" : \\\"example@example.com\\\"}  or {\\\"email\\\" : \\\"example@example.com\\\", \\\"name\\\" : \\\"Example Recipient\\\"}.\",\"field\":\"from.email\",\"help\":\"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.from\"}]}\n\tsgResponse struct {\n\t\tErrors []sgError `json:\"errors\"`\n\t}\n\t// sgError defines a singular validation error from the API.\n\tsgError struct {\n\t\tMessage string `json:\"message\"`\n\t\tField   string `json:\"field\"`\n\t\tHelp    string `json:\"help\"`\n\t}\n)\n\nfunc (r *sgResponse) Unmarshal(buf []byte) error {\n\tif len(buf) == 0 {\n\t\treturn nil\n\t}\n\tresp := &sgResponse{}\n\terr := json.Unmarshal(buf, resp)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*r = *resp\n\treturn nil\n}\n\nfunc (r *sgResponse) CheckError(response *http.Response, buf []byte) error {\n\tif client.Is2XX(response.StatusCode) {\n\t\treturn nil\n\t}\n\tif len(r.Errors) == 0 {\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"%s - message: %s, field: %s, help: %s\", sendgridErrorMessage, r.Errors[0].Message, r.Errors[0].Field, r.Errors[0].Help)\n}\n\nfunc (r *sgResponse) Meta() httputil.Meta {\n\treturn httputil.Meta{\n\t\tMessage: \"Successfully sent SendGrid email\",\n\t\t// No response data from SendGrid\n\t\tID: \"\",\n\t}\n}\n\nfunc (d *sendGrid) Send(t *mail.Transmission) (mail.Response, error) {\n\terr := t.Validate()\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\ttx := sgTransmission{\n\t\tFrom: &sgEmail{\n\t\t\tName:    d.cfg.FromName,\n\t\t\tAddress: d.cfg.FromAddress,\n\t\t},\n\t\tSubject: t.Subject,\n\t\tPersonalizations: []*sgPersonalization{\n\t\t\t{Subject: t.Subject},\n\t\t},\n\t\tContent: []*sgContent{\n\t\t\t{Type: \"text/plain\", Value: t.PlainText},\n\t\t\t{Type: \"text/html\", Value: t.HTML},\n\t\t},\n\t\tAttachments: nil,\n\t}\n\n\tfor _, r := range t.Recipients {\n\t\ttx.Personalizations[0].To = append(tx.Personalizations[0].To, &sgEmail{\n\t\t\tAddress: r,\n\t\t})\n\t}\n\n\tif t.HasCC() {\n\t\tfor _, c := range t.CC {\n\t\t\ttx.Personalizations[0].CC = append(tx.Personalizations[0].CC, &sgEmail{\n\t\t\t\tAddress: c,\n\t\t\t})\n\t\t}\n\t}\n\n\tif t.HasBCC() {\n\t\tfor _, b := range t.BCC {\n\t\t\ttx.Personalizations[0].BCC = append(tx.Personalizations[0].BCC, &sgEmail{\n\t\t\t\tAddress: b,\n\t\t\t})\n\t\t}\n\t}\n\n\tif t.HasAttachments() {\n\t\tfor _, v := range t.Attachments {\n\t\t\ttx.Attachments = append(tx.Attachments, &sgAttachment{\n\t\t\t\tContent:     v.B64(),\n\t\t\t\tType:        v.Mime(),\n\t\t\t\tName:        \"\",\n\t\t\t\tFilename:    v.Filename,\n\t\t\t\tDisposition: \"attachment\",\n\t\t\t})\n\t\t}\n\t}\n\n\ttx.Headers = t.Headers\n\n\tpl, err := newJSONData(tx)\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\treq := httputil.NewHTTPRequest(http.MethodPost, sendGridEndpoint)\n\treq.AddHeader(\"Authorization\", \"Bearer \"+d.cfg.APIKey)\n\n\treturn d.client.Do(context.Background(), req, pl, &sgResponse{})\n}\n"
  },
  {
    "path": "drivers/sendgrid_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"fmt\"\n\tmocks \"github.com/ainsleyclark/go-mail/internal/mocks/client\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc ExampleNewSendGrid() {\n\tcfg := mail.Config{\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t}\n\n\t_, err := NewSendGrid(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n}\n\nfunc (t *DriversTestSuite) TestNewSendGrid() {\n\ttt := map[string]struct {\n\t\tinput mail.Config\n\t\twant  interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\tmail.Config{\n\t\t\t\tURL:         \"https://sendgrid.example.com\",\n\t\t\t\tAPIKey:      \"key\",\n\t\t\t\tFromAddress: \"addr\",\n\t\t\t\tFromName:    \"name\",\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t\"Validation Failed\": {\n\t\t\tmail.Config{},\n\t\t\t\"driver requires from address\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tgot, err := NewSendGrid(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.NotNil(got)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestSendGridResponse_Unmarshal() {\n\tt.UtilTestUnmarshal(&sgResponse{}, []byte(`{\"errors\": []}`))\n\tres := sgResponse{}\n\terr := res.Unmarshal(nil)\n\tt.NoError(err)\n}\n\nfunc (t *DriversTestSuite) TestSendGridResponse_CheckError() {\n\ttt := map[string]struct {\n\t\tinput    sgResponse\n\t\tresponse *http.Response\n\t\tbuf      []byte\n\t\twant     error\n\t}{\n\t\t\"Success\": {\n\t\t\tsgResponse{Errors: nil},\n\t\t\t&http.Response{StatusCode: http.StatusOK},\n\t\t\t[]byte(\"test\"),\n\t\t\tnil,\n\t\t},\n\t\t\"No Errors\": {\n\t\t\tsgResponse{},\n\t\t\t&http.Response{StatusCode: http.StatusInternalServerError},\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\t\"Error\": {\n\t\t\tsgResponse{Errors: []sgError{{Message: \"message\", Field: \"field\", Help: \"help\"}}},\n\t\t\t&http.Response{StatusCode: http.StatusInternalServerError},\n\t\t\t[]byte(\"test\"),\n\t\t\tfmt.Errorf(\"%s - message: message, field: field, help: help\", sendgridErrorMessage),\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\terr := test.input.CheckError(test.response, test.buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.Equal(test.want, err)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestSendGridResponse_Meta() {\n\td := &sgResponse{}\n\tt.UtilTestMeta(d, \"Successfully sent SendGrid email\", \"\")\n}\n\nfunc (t *DriversTestSuite) TestSendGrid_Send() {\n\tt.UtilTestSend(func(m *mocks.Requester) mail.Mailer {\n\t\treturn &sendGrid{cfg: Comfig, client: m}\n\t}, true)\n}\n"
  },
  {
    "path": "drivers/smtp.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/smtp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// smtpClient represents the data for sending mail via\n// plain ol SMTP. Configuration, the client and the\n// main send function are parsed for sending\n// data.\ntype smtpClient struct {\n\tcfg  mail.Config\n\tsend smtpSendFunc\n}\n\n// smtpSendFunc defines the function for ending\n// SMTP mail.Transmissions.\ntype smtpSendFunc func(addr string, a smtp.Auth, from string, to []string, msg []byte) error\n\n// NewSMTP creates a new smtp client. Configuration\n// is validated before initialisation.\nfunc NewSMTP(cfg mail.Config) (mail.Mailer, error) {\n\tif cfg.URL == \"\" {\n\t\treturn nil, errors.New(\"driver requires a url\")\n\t}\n\tif cfg.FromAddress == \"\" {\n\t\treturn nil, errors.New(\"driver requires from address\")\n\t}\n\tif cfg.FromName == \"\" {\n\t\treturn nil, errors.New(\"driver requires from name\")\n\t}\n\tif cfg.Password == \"\" {\n\t\treturn nil, errors.New(\"driver requires a password\")\n\t}\n\treturn &smtpClient{\n\t\tcfg:  cfg,\n\t\tsend: smtp.SendMail,\n\t}, nil\n}\n\n// Send mail via plain SMTP. mail.Transmissions are validated\n// before sending and attachments are added. Returns\n// an error upon failure.\nfunc (m *smtpClient) Send(t *mail.Transmission) (mail.Response, error) {\n\terr := t.Validate()\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\tauth := smtp.PlainAuth(\"\", m.cfg.FromAddress, m.cfg.Password, m.cfg.URL)\n\terr = m.send(m.cfg.URL+\":\"+strconv.Itoa(m.cfg.Port), auth, m.cfg.FromAddress, m.getTo(t), m.bytes(t))\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\treturn mail.Response{\n\t\tStatusCode: http.StatusOK,\n\t\tMessage:    \"Email sent successfully\",\n\t}, nil\n}\n\n// getTo returns the merged mail.Transmission recipients, CC and\n// BCC email addresses.\nfunc (m *smtpClient) getTo(t *mail.Transmission) []string {\n\tvar to []string\n\tto = append(t.Recipients, t.CC...)\n\tto = append(to, t.BCC...)\n\treturn to\n}\n\n// Processes the mail.Transmission and returns the bytes for\n// sending. Mime types are set dependent on the\n// content passed.\n// See: https://gist.github.com/tylermakin/d820f65eb3c9dd98d58721c7fb1939a8?permalink_comment_id=2703291\nfunc (m *smtpClient) bytes(t *mail.Transmission) []byte {\n\tbuf := bytes.NewBuffer(nil)\n\n\tfor k, v := range t.Headers {\n\t\tbuf.WriteString(fmt.Sprintf(\"%s: %s\\n\", k, v))\n\t}\n\n\tbuf.WriteString(\"MIME-Version: 1.0\\n\")\n\twriter := multipart.NewWriter(buf)\n\tboundary := writer.Boundary()\n\n\tif t.HasAttachments() {\n\t\tbuf.WriteString(fmt.Sprintf(\"Content-Type: multipart/alternative; boundary=%s\\r\\n\", boundary))\n\t} else {\n\t\tbuf.WriteString(fmt.Sprintf(\"Content-Type: text/html; charset=UTF-8; boundary=%s\\r\\n\", boundary))\n\t}\n\n\tbuf.WriteString(fmt.Sprintf(\"Subject: %s\\n\", t.Subject))\n\tbuf.WriteString(fmt.Sprintf(\"To: %s\\n\", strings.Join(t.Recipients, \",\")))\n\n\tif t.HasCC() {\n\t\tbuf.WriteString(fmt.Sprintf(\"CC: %s\\n\", strings.Join(t.CC, \",\")))\n\t}\n\n\tbuf.WriteString(\"\\n\")\n\n\tif t.PlainText != \"\" {\n\t\tbuf.WriteString(fmt.Sprintf(\"--%s\\r\\n\", boundary))\n\t\tbuf.WriteString(\"Content-Transfer-Encoding: quoted-printable\\r\\n\")\n\t\tbuf.WriteString(\"Content-Type: text/plain; charset=UTF-8\\r\\n\")\n\t\tbuf.WriteString(fmt.Sprintf(\"\\r\\n%s\\r\\n\\n\", strings.TrimSpace(t.PlainText)))\n\t}\n\n\tif t.HTML != \"\" {\n\t\tbuf.WriteString(fmt.Sprintf(\"--%s\\r\\n\", boundary))\n\t\tbuf.WriteString(\"Content-Transfer-Encoding: quoted-printable\\r\\n\")\n\t\tbuf.WriteString(\"Content-Type: text/html; charset=UTF-8\\r\\n\")\n\t\tbuf.WriteString(fmt.Sprintf(\"\\r\\n%s\\r\\n\\n\", t.HTML))\n\t}\n\n\tif t.HasAttachments() {\n\t\tfor _, v := range t.Attachments {\n\t\t\tbuf.WriteString(fmt.Sprintf(\"--%s\\r\\n\", boundary))\n\t\t\tbuf.WriteString(fmt.Sprintf(\"Content-Type: %s\\n\", v.Mime()))\n\t\t\tbuf.WriteString(\"Content-Transfer-Encoding: base64\\n\")\n\t\t\tbuf.WriteString(fmt.Sprintf(\"Content-Disposition: attachment; filename=%s\\n\", v.Filename))\n\t\t\tbuf.WriteString(fmt.Sprintf(\"\\r\\n--%s\", v.B64()))\n\t\t}\n\t\tbuf.WriteString(\"--\")\n\t}\n\n\tbuf.WriteString(fmt.Sprintf(\"--%s--\\n\", boundary))\n\n\treturn buf.Bytes()\n}\n"
  },
  {
    "path": "drivers/smtp_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"net/smtp\"\n)\n\nfunc (t *DriversTestSuite) TestNewSMTP() {\n\ttt := map[string]struct {\n\t\tinput mail.Config\n\t\twant  interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\tmail.Config{\n\t\t\t\tURL:         \"https://smtp.example.com\",\n\t\t\t\tFromAddress: \"addr\",\n\t\t\t\tFromName:    \"name\",\n\t\t\t\tPassword:    \"password\",\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t\"No url\": {\n\t\t\tmail.Config{},\n\t\t\t\"driver requires a url\",\n\t\t},\n\t\t\"No From Address\": {\n\t\t\tmail.Config{\n\t\t\t\tURL: \"https://smtp.example.com\",\n\t\t\t},\n\t\t\t\"driver requires from address\",\n\t\t},\n\t\t\"No From Name\": {\n\t\t\tmail.Config{\n\t\t\t\tURL:         \"https://smtp.example.com\",\n\t\t\t\tFromAddress: \"hello@gophers.com\",\n\t\t\t},\n\t\t\t\"driver requires from name\",\n\t\t},\n\t\t\"No Password\": {\n\t\t\tmail.Config{\n\t\t\t\tURL:         \"https://smtp.example.com\",\n\t\t\t\tFromAddress: \"hello@gophers.com\",\n\t\t\t\tFromName:    \"name\",\n\t\t\t},\n\t\t\t\"driver requires a password\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tgot, err := NewSMTP(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.NotNil(got)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestSMTP_Send() {\n\ttt := map[string]struct {\n\t\tinput *mail.Transmission\n\t\tsend  smtpSendFunc\n\t\twant  interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\tTrans,\n\t\t\tfunc(addr string, a smtp.Auth, from string, to []string, msg []byte) error {\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tmail.Response{\n\t\t\t\tStatusCode: 200,\n\t\t\t\tMessage:    \"Email sent successfully\",\n\t\t\t},\n\t\t},\n\t\t\"With Attachment\": {\n\t\t\tTransWithAttachment,\n\t\t\tfunc(addr string, a smtp.Auth, from string, to []string, msg []byte) error {\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\tmail.Response{\n\t\t\t\tStatusCode: 200,\n\t\t\t\tMessage:    \"Email sent successfully\",\n\t\t\t},\n\t\t},\n\t\t\"Validation Failed\": {\n\t\t\tnil,\n\t\t\tfunc(addr string, a smtp.Auth, from string, to []string, msg []byte) error {\n\t\t\t\treturn nil\n\t\t\t},\n\t\t\t\"can't validate a nil transmission\",\n\t\t},\n\t\t\"Send Error\": {\n\t\t\tTrans,\n\t\t\tfunc(addr string, a smtp.Auth, from string, to []string, msg []byte) error {\n\t\t\t\treturn errors.New(\"send error\")\n\t\t\t},\n\t\t\t\"send error\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tspark := smtpClient{\n\t\t\t\tcfg: mail.Config{\n\t\t\t\t\tFromAddress: \"from\",\n\t\t\t\t},\n\t\t\t\tsend: test.send,\n\t\t\t}\n\t\t\tresp, err := spark.Send(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.Equal(test.want, resp)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestSMTP_Bytes() {\n\tt.T().Skip()\n\n\tm := smtpClient{}\n\tgot := m.bytes(&mail.Transmission{\n\t\tRecipients: []string{\"hello@gmail.com\"},\n\t\tSubject:    \"Subject\",\n\t\tHTML:       \"<h1>Hey!</h1>\",\n\t\tPlainText:  \"Hey!\",\n\t\t//Attachments: []mail.Attachment{\n\t\t//\t{\n\t\t//\t\tFilename: \"test.jpg\",\n\t\t//\t\tBytes:    []byte(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==\"),\n\t\t//\t},\n\t\t//},\n\t})\n\tfmt.Println(string(got))\n}\n"
  },
  {
    "path": "drivers/sparkpost.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/internal/client\"\n\t\"github.com/ainsleyclark/go-mail/internal/httputil\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// postal represents the entity for sending mail via the\n// Postal API.\n//\n// See:\n// https://developers.sparkpost.com/api/\n// https://developers.sparkpost.com/api/transmissions/#transmissions-create-a-transmission\ntype sparkPost struct {\n\tcfg    mail.Config\n\tclient client.Requester\n}\n\nconst (\n\t// sparkpostEndpoint defines the endpoint to POST to.\n\t// See: https://www.sparkpost.com/api#/reference/transmissions\n\tsparkpostEndpoint = \"%s/api/v1/transmissions\"\n\t// sparkpostErrorMessage defines the message when an error occurred\n\t// when sending mail via the SparkPost API.\n\tsparkpostErrorMessage = \"error sending transmission to SparkPost API\"\n)\n\n// NewSparkPost creates a new SparkPost client. Configuration\n// is validated before initialisation.\nfunc NewSparkPost(cfg mail.Config) (mail.Mailer, error) {\n\terr := cfg.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &sparkPost{\n\t\tcfg:    cfg,\n\t\tclient: client.New(cfg.Client),\n\t}, nil\n}\n\ntype (\n\t// spTransmission is the JSON structure accepted by and returned\n\t// from the SparkPost Transmissions API.\n\tspTransmission struct {\n\t\tID                   string                 `json:\"id,omitempty\"`\n\t\tState                string                 `json:\"state,omitempty\"`\n\t\tOptions              *spTransmissionOptions `json:\"options,omitempty\"`\n\t\tRecipients           []spRecipient          `json:\"recipients\"`\n\t\tCampaignID           string                 `json:\"campaign_id,omitempty\"`\n\t\tDescription          string                 `json:\"description,omitempty\"`\n\t\tMetadata             interface{}            `json:\"metadata,omitempty\"`\n\t\tSubstitutionData     interface{}            `json:\"substitution_data,omitempty\"`\n\t\tReturnPath           string                 `json:\"return_path,omitempty\"`\n\t\tContent              spContent              `json:\"content\"`\n\t\tTotalRecipients      *int                   `json:\"total_recipients,omitempty\"`\n\t\tNumGenerated         *int                   `json:\"num_generated,omitempty\"`\n\t\tNumFailedGeneration  *int                   `json:\"num_failed_generation,omitempty\"`\n\t\tNumInvalidRecipients *int                   `json:\"num_invalid_recipients,omitempty\"`\n\t}\n\t// spTransmissionOptions specifies settings to apply to this Transmission.\n\t// If not specified, and present in TmplOptions, those values will be used.\n\tspTransmissionOptions struct {\n\t\tOpenTracking         *bool      `json:\"open_tracking,omitempty\"`\n\t\tClickTracking        *bool      `json:\"click_tracking,omitempty\"`\n\t\tTransactional        *bool      `json:\"transactional,omitempty\"`\n\t\tStartTime            *time.Time `json:\"start_time,omitempty\"`\n\t\tSandbox              *bool      `json:\"sandbox,omitempty\"`\n\t\tSkipSuppression      *bool      `json:\"skip_suppression,omitempty\"`\n\t\tIPPool               string     `json:\"ip_pool,omitempty\"`\n\t\tInlineCSS            *bool      `json:\"inline_css,omitempty\"`\n\t\tPerformSubstitutions *bool      `json:\"perform_substitutions,omitempty\"`\n\t}\n\t// spContent is what will be sent to recipients.\n\t// Knowledge of SparkPost's substitution/templating capabilities will come in handy here.\n\t// https://www.sparkpost.com/api#/introduction/substitutions-reference\n\tspContent struct {\n\t\tHTML         string            `json:\"html,omitempty\"`\n\t\tText         string            `json:\"text,omitempty\"`\n\t\tSubject      string            `json:\"subject,omitempty\"`\n\t\tFrom         spFrom            `json:\"from,omitempty\"`\n\t\tReplyTo      string            `json:\"reply_to,omitempty\"`\n\t\tHeaders      map[string]string `json:\"headers,omitempty\"`\n\t\tEmailRFC822  string            `json:\"email_rfc822,omitempty\"`\n\t\tAttachments  []spAttachment    `json:\"attachments,omitempty\"`\n\t\tInlineImages []interface{}     `json:\"inline_images,omitempty\"`\n\t}\n\t// spFrom describes the nested object way of specifying the `From` header.\n\t// Content.From can be specified this way, or as a plain string.\n\tspFrom struct {\n\t\tEmail string `json:\"email\"`\n\t\tName  string `json:\"name\"`\n\t}\n\t// spResponse contains information about the last HTTP response from\n\t// the SparkPost API.\n\t//\n\t// Example JSON Response:\n\t// {\"results\":{\"total_rejected_recipients\":0,\"total_accepted_recipients\":1,\"id\":\"7029753512321354395\"}}\n\t// {\"errors\":[{\"message\":\"content.subject is a required field\",\"code\":\"1400\"}]}\n\tspResponse struct {\n\t\tResults map[string]interface{} `json:\"results,omitempty\"`\n\t\tErrors  []spError              `json:\"errors,omitempty\"`\n\t}\n\t// spError mirrors the error format returned by SparkPost APIs.\n\tspError struct {\n\t\tMessage     string `json:\"message\"`\n\t\tCode        string `json:\"code\"`\n\t\tDescription string `json:\"description\"`\n\t\tPart        string `json:\"part,omitempty\"`\n\t\tLine        int    `json:\"line,omitempty\"`\n\t}\n\t// spRecipient represents one email (you guessed it) recipient.\n\tspRecipient struct {\n\t\tAddress          spAddress   `json:\"address\"`\n\t\tReturnPath       string      `json:\"return_path,omitempty\"`\n\t\tTags             []string    `json:\"tags,omitempty\"`\n\t\tMetadata         interface{} `json:\"metadata,omitempty\"`\n\t\tSubstitutionData interface{} `json:\"substitution_data,omitempty\"`\n\t}\n\t// spAddress describes the nested object way of specifying the\n\t// Recipient's email address. Recipient.Address can also be\n\t// a plain string.\n\tspAddress struct {\n\t\tEmail    string `json:\"email\"`\n\t\tName     string `json:\"name,omitempty\"`\n\t\tHeaderTo string `json:\"header_to,omitempty\"`\n\t}\n\t// spAttachment contains metadata and the contents of the\n\t// file to attach.\n\tspAttachment struct {\n\t\tMIMEType string `json:\"type\"`\n\t\tFilename string `json:\"name\"`\n\t\tB64Data  string `json:\"data\"`\n\t}\n)\n\nfunc (r *spResponse) Unmarshal(buf []byte) error {\n\tresp := &spResponse{}\n\terr := json.Unmarshal(buf, resp)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*r = *resp\n\treturn nil\n}\n\nfunc (r *spResponse) CheckError(response *http.Response, buf []byte) error {\n\tif len(r.Errors) == 0 {\n\t\treturn nil\n\t}\n\tif len(buf) == 0 {\n\t\treturn mail.ErrEmptyBody\n\t}\n\treturn fmt.Errorf(\"%s - code: %s, message: %s\", sparkpostErrorMessage, r.Errors[0].Code, r.Errors[0].Message)\n}\n\nfunc (r *spResponse) Meta() httputil.Meta {\n\tm := httputil.Meta{\n\t\tMessage: \"Successfully sent SparkPost email\",\n\t}\n\tif val, ok := r.Results[\"id\"]; ok {\n\t\tm.ID = fmt.Sprintf(\"%v\", val)\n\t}\n\treturn m\n}\n\nfunc (d *sparkPost) Send(t *mail.Transmission) (mail.Response, error) {\n\terr := t.Validate()\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\theaderTo := strings.Join(t.Recipients, \",\")\n\n\ttx := spTransmission{\n\t\tContent: spContent{\n\t\t\tHTML:    t.HTML,\n\t\t\tText:    t.PlainText,\n\t\t\tSubject: t.Subject,\n\t\t\tFrom: spFrom{\n\t\t\t\tEmail: d.cfg.FromAddress,\n\t\t\t\tName:  d.cfg.FromName,\n\t\t\t},\n\t\t\tReplyTo: \"\",\n\t\t\tHeaders: make(map[string]string),\n\t\t},\n\t}\n\n\tfor _, r := range t.Recipients {\n\t\ttx.Recipients = append(tx.Recipients, spRecipient{\n\t\t\tAddress: spAddress{Email: r, HeaderTo: headerTo},\n\t\t})\n\t}\n\n\tif t.HasCC() {\n\t\tfor _, c := range t.CC {\n\t\t\ttx.Recipients = append(tx.Recipients, spRecipient{\n\t\t\t\tAddress: spAddress{Email: c, HeaderTo: headerTo},\n\t\t\t})\n\t\t\ttx.Content.Headers[\"cc\"] = strings.Join(t.CC, \",\")\n\t\t}\n\t}\n\n\tif t.HasBCC() {\n\t\tfor _, b := range t.BCC {\n\t\t\ttx.Recipients = append(tx.Recipients, spRecipient{\n\t\t\t\tAddress: spAddress{Email: b, HeaderTo: headerTo},\n\t\t\t})\n\t\t}\n\t}\n\n\tif t.HasAttachments() {\n\t\tfor _, v := range t.Attachments {\n\t\t\ttx.Content.Attachments = append(tx.Content.Attachments, spAttachment{\n\t\t\t\tMIMEType: v.Mime(),\n\t\t\t\tFilename: v.Filename,\n\t\t\t\tB64Data:  v.B64(),\n\t\t\t})\n\t\t}\n\t}\n\n\ttx.Content.Headers = t.Headers\n\n\tpl, err := newJSONData(tx)\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\treq := httputil.NewHTTPRequest(http.MethodPost, fmt.Sprintf(sparkpostEndpoint, d.cfg.URL))\n\treq.AddHeader(\"Authorization\", d.cfg.APIKey)\n\n\treturn d.client.Do(context.Background(), req, pl, &spResponse{})\n}\n"
  },
  {
    "path": "drivers/sparkpost_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage drivers\n\nimport (\n\t\"fmt\"\n\tmocks \"github.com/ainsleyclark/go-mail/internal/mocks/client\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc ExampleNewSparkPost() {\n\tcfg := mail.Config{\n\t\tURL:         \"https://api.eu.sparkpost.com\", // Or https://api.sparkpost.com/api/v1\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t}\n\n\t_, err := NewSparkPost(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n}\n\nfunc (t *DriversTestSuite) TestNewSparkPost() {\n\ttt := map[string]struct {\n\t\tinput mail.Config\n\t\twant  interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\tmail.Config{\n\t\t\t\tURL:         \"https://api.eu.sparkpost.com\",\n\t\t\t\tAPIKey:      \"key\",\n\t\t\t\tFromAddress: \"addr\",\n\t\t\t\tFromName:    \"name\",\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t\"Validation Failed\": {\n\t\t\tmail.Config{},\n\t\t\t\"driver requires from address\",\n\t\t},\n\t\t\"Error\": {\n\t\t\tmail.Config{\n\t\t\t\tURL:         \"http://\",\n\t\t\t\tAPIKey:      \"key\",\n\t\t\t\tFromAddress: \"addr\",\n\t\t\t\tFromName:    \"name\",\n\t\t\t},\n\t\t\t\"API base url must be https!\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tgot, err := NewSparkPost(test.input)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.NotNil(got)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestSSparkPostResponse_Unmarshal() {\n\tt.UtilTestUnmarshal(&spResponse{}, []byte(`{\"results\": {}}`))\n}\n\nfunc (t *DriversTestSuite) TestSparkPostResponse_CheckError() {\n\ttt := map[string]struct {\n\t\tinput    spResponse\n\t\tresponse *http.Response\n\t\tbuf      []byte\n\t\twant     error\n\t}{\n\t\t\"Success\": {\n\t\t\tspResponse{Errors: nil},\n\t\t\t&http.Response{StatusCode: http.StatusOK},\n\t\t\t[]byte(\"test\"),\n\t\t\tnil,\n\t\t},\n\t\t\"No Errors\": {\n\t\t\tspResponse{Errors: []spError{{Message: \"message\", Code: \"code\"}}},\n\t\t\t&http.Response{StatusCode: http.StatusInternalServerError},\n\t\t\tnil,\n\t\t\tmail.ErrEmptyBody,\n\t\t},\n\t\t\"Error\": {\n\t\t\tspResponse{Errors: []spError{{Message: \"message\", Code: \"code\"}}},\n\t\t\t&http.Response{StatusCode: http.StatusInternalServerError},\n\t\t\t[]byte(\"test\"),\n\t\t\tfmt.Errorf(\"%s - code: code, message: message\", sparkpostErrorMessage),\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\terr := test.input.CheckError(test.response, test.buf)\n\t\t\tif err != nil {\n\t\t\t\tt.Contains(err.Error(), test.want.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tt.Equal(test.want, err)\n\t\t})\n\t}\n}\n\nfunc (t *DriversTestSuite) TestSparkPostResponse_Meta() {\n\td := &spResponse{\n\t\tResults: map[string]interface{}{\"id\": \"10\"},\n\t\tErrors:  nil,\n\t}\n\tt.UtilTestMeta(d, \"Successfully sent SparkPost email\", \"10\")\n}\n\nfunc (t *DriversTestSuite) TestSparkPost_Send() {\n\tt.UtilTestSend(func(m *mocks.Requester) mail.Mailer {\n\t\treturn &sparkPost{cfg: Comfig, client: m}\n\t}, true)\n}\n"
  },
  {
    "path": "examples/attachments.go",
    "content": "// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"io/ioutil\"\n\t\"log\"\n)\n\n// Attachments example for Go Mail\nfunc Attachments() {\n\tcfg := mail.Config{\n\t\tURL:         \"https://api.eu.sparkpost.com\",\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t}\n\n\tmailer, err := drivers.NewSparkPost(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\timage, err := ioutil.ReadFile(\"gopher.jpg\")\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\ttx := &mail.Transmission{\n\t\tRecipients: []string{\"hello@gophers.com\"},\n\t\tSubject:    \"My email\",\n\t\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\t\tPlainText:  \"plain text\",\n\t\tAttachments: []mail.Attachment{\n\t\t\t{\n\t\t\t\tFilename: \"gopher.jpg\",\n\t\t\t\tBytes:    image,\n\t\t\t},\n\t\t},\n\t}\n\n\tresult, err := mailer.Send(tx)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", result)\n}\n"
  },
  {
    "path": "examples/mailgun.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"log\"\n)\n\n// MailGun example for Go Mail\nfunc MailGun() {\n\tcfg := mail.Config{\n\t\tURL:         \"my-url\",\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t\tDomain:      \"my-domain\",\n\t}\n\n\tmailer, err := drivers.NewMailGun(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\ttx := &mail.Transmission{\n\t\tRecipients: []string{\"hello@gophers.com\"},\n\t\tSubject:    \"My email\",\n\t\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\t\tPlainText:  \"plain text\",\n\t}\n\n\tresult, err := mailer.Send(tx)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", result)\n}\n"
  },
  {
    "path": "examples/postal.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"log\"\n)\n\n// Postal example for Go Mail\nfunc Postal() {\n\tcfg := mail.Config{\n\t\tURL:         \"https://postal.example.com\",\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t}\n\n\tmailer, err := drivers.NewPostal(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\ttx := &mail.Transmission{\n\t\tRecipients: []string{\"hello@gophers.com\"},\n\t\tSubject:    \"My email\",\n\t\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\t\tPlainText:  \"plain text\",\n\t}\n\n\tresult, err := mailer.Send(tx)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", result)\n}\n"
  },
  {
    "path": "examples/postmark.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"log\"\n)\n\n// Postmark example for Go Mail\nfunc Postmark() {\n\tcfg := mail.Config{\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t}\n\n\tmailer, err := drivers.NewPostmark(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\ttx := &mail.Transmission{\n\t\tRecipients: []string{\"hello@gophers.com\"},\n\t\tSubject:    \"My email\",\n\t\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\t\tPlainText:  \"plain text\",\n\t}\n\n\tresult, err := mailer.Send(tx)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", result)\n}\n"
  },
  {
    "path": "examples/sendgrid.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"log\"\n)\n\n// SendGrid example for Go Mail\nfunc SendGrid() {\n\tcfg := mail.Config{\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t}\n\n\tmailer, err := drivers.NewSendGrid(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\ttx := &mail.Transmission{\n\t\tRecipients: []string{\"hello@gophers.com\"},\n\t\tSubject:    \"My email\",\n\t\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\t\tPlainText:  \"plain text\",\n\t}\n\n\tresult, err := mailer.Send(tx)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", result)\n}\n"
  },
  {
    "path": "examples/smtp.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"log\"\n)\n\n// SMTP example for Go Mail\nfunc SMTP() {\n\tcfg := mail.Config{\n\t\tURL:         \"smtp.gmail.com\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t\tPassword:    \"my-password\",\n\t\tPort:        587,\n\t}\n\n\tmailer, err := drivers.NewSMTP(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\ttx := &mail.Transmission{\n\t\tRecipients: []string{\"hello@gophers.com\"},\n\t\tSubject:    \"My email\",\n\t\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\t\tPlainText:  \"plain text\",\n\t}\n\n\tresult, err := mailer.Send(tx)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", result)\n}\n"
  },
  {
    "path": "examples/sparkpost.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"log\"\n)\n\n// Sparkpost example for Go Mail\nfunc Sparkpost() {\n\tcfg := mail.Config{\n\t\tURL:         \"https://api.eu.sparkpost.com\",\n\t\tAPIKey:      \"my-key\",\n\t\tFromAddress: \"hello@gophers.com\",\n\t\tFromName:    \"Gopher\",\n\t}\n\n\tmailer, err := drivers.NewSparkPost(cfg)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\ttx := &mail.Transmission{\n\t\tRecipients: []string{\"hello@gophers.com\"},\n\t\tSubject:    \"My email\",\n\t\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\t\tPlainText:  \"plain text\",\n\t}\n\n\tresult, err := mailer.Send(tx)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", result)\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/ainsleyclark/go-mail\n\ngo 1.18\n\nrequire (\n\tgithub.com/joho/godotenv v1.4.0\n\tgithub.com/stretchr/testify v1.9.0\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=\ngithub.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "internal/client/client.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/internal/errors\"\n\t\"github.com/ainsleyclark/go-mail/internal/httputil\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Requester defines the method used for interacting with\n// a Mailable API.\ntype Requester interface {\n\t// Do accepts a message, url endpoint and optional Headers to POST data\n\t// to a drivers API.\n\t// Returns an error if data could not be marshalled/unmarshalled\n\t// or if the request could not be processed.\n\tDo(ctx context.Context, r *httputil.Request, payload httputil.Payload, responder httputil.Responder) (mail.Response, error)\n}\n\n// New creates a new Client with a stdlib http.Client.\nfunc New(client *http.Client) *Client {\n\tif client == nil {\n\t\tclient = &http.Client{\n\t\t\tTimeout: Timeout,\n\t\t}\n\t}\n\treturn &Client{\n\t\tClient:     client,\n\t\tbodyReader: io.ReadAll,\n\t}\n}\n\nconst (\n\t// Timeout is the amount of time to wait before\n\t// a mail request is cancelled.\n\tTimeout = time.Second * 10\n)\n\n// Client defines a http.Client to interact with the mail\n// drivers API's. It acts as a reusable helper to send\n// data to the drivers endpoints.\ntype Client struct {\n\tClient     *http.Client\n\tbodyReader func(r io.Reader) ([]byte, error)\n}\n\n// Do accepts a message, Request and a Payload to POST data\n// to a drivers API.\n// Logs Curl output if mail.debug is set to true.\n//\n// Returns an error if data could not be marshalled/unmarshalled\n// or if the request could not be processed.\nfunc (c *Client) Do(ctx context.Context, r *httputil.Request, payload httputil.Payload, responder httputil.Responder) (mail.Response, error) {\n\tconst op = \"Client.Do\"\n\n\treq, err := c.makeRequest(ctx, r, payload)\n\tif err != nil {\n\t\treturn mail.Response{}, err\n\t}\n\n\tresp, err := c.Client.Do(req)\n\tif err != nil {\n\t\treturn mail.Response{}, &errors.Error{Code: errors.API, Message: \"Error doing request\", Operation: op, Err: err}\n\t}\n\tdefer resp.Body.Close()\n\n\tresponse := mail.Response{\n\t\tStatusCode: resp.StatusCode,\n\t}\n\n\tbuf, err := c.bodyReader(resp.Body)\n\tif err != nil {\n\t\treturn response, &errors.Error{Code: errors.INTERNAL, Message: \"Error reading response body\", Operation: op, Err: err}\n\t}\n\tresponse.Body = buf\n\n\terr = responder.Unmarshal(buf)\n\tif err != nil {\n\t\treturn response, &errors.Error{Code: errors.INVALID, Message: \"Error unmarshalling response error\", Operation: op, Err: err}\n\t}\n\n\terr = responder.CheckError(resp, buf)\n\tif err != nil {\n\t\treturn response, &errors.Error{Code: errors.API, Message: \"Error performing mail request\", Operation: op, Err: err}\n\t}\n\n\tmeta := responder.Meta()\n\treturn mail.Response{\n\t\tStatusCode: resp.StatusCode,\n\t\tBody:       buf,\n\t\tHeaders:    resp.Header,\n\t\tID:         meta.ID,\n\t\tMessage:    meta.Message,\n\t}, nil\n}\n\n// makeRequest creates a stdlib http.Request.\n// Content-Type, BasicAuth and headers are attached to the request.\n// Returns an error if the request could not be created.\nfunc (c *Client) makeRequest(ctx context.Context, r *httputil.Request, payload httputil.Payload) (*http.Request, error) {\n\tconst op = \"Client.MakeRequest\"\n\n\tvar body io.Reader\n\tif payload != nil {\n\t\tb, err := payload.Buffer()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tbody = b\n\t}\n\n\treq, err := http.NewRequest(r.Method, r.URL, body)\n\tif err != nil {\n\t\treturn nil, &errors.Error{Code: errors.INVALID, Message: \"Error creating http request\", Operation: op, Err: err}\n\t}\n\n\tif mail.Debug {\n\t\tfmt.Println(c.curlString(req, payload))\n\t}\n\n\treq = req.WithContext(ctx)\n\n\tif payload != nil && payload.ContentType() != \"\" {\n\t\treq.Header.Add(\"Content-Type\", payload.ContentType())\n\t}\n\n\tif r.BasicAuthUser != \"\" && r.BasicAuthPassword != \"\" {\n\t\treq.SetBasicAuth(r.BasicAuthUser, r.BasicAuthPassword)\n\t}\n\n\tfor header, value := range r.Headers {\n\t\treq.Header.Add(header, value)\n\t}\n\n\treturn req, nil\n}\n\n// curlString constructs a string used for posting the\n// request via Curl.\nfunc (c *Client) curlString(req *http.Request, p httputil.Payload) string {\n\tparts := []string{\"curl\", \"-i\", \"-X\", req.Method, req.URL.String()}\n\tfor key, value := range req.Header {\n\t\tparts = append(parts, fmt.Sprintf(\"-H \\\"%s: %s\\\"\", key, value[0]))\n\t}\n\n\tif p != nil {\n\t\tfor key, value := range p.Values() {\n\t\t\tparts = append(parts, fmt.Sprintf(\" -F %s='%s'\", key, value))\n\t\t}\n\t}\n\n\treturn strings.Join(parts, \" \")\n}\n"
  },
  {
    "path": "internal/client/client_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage client\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"github.com/ainsleyclark/go-mail/internal/errors\"\n\t\"github.com/ainsleyclark/go-mail/internal/httputil\"\n\tmocks \"github.com/ainsleyclark/go-mail/internal/mocks/httputil\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n)\n\nfunc TestNewClient(t *testing.T) {\n\tgot := New(nil)\n\tassert.NotNil(t, got.bodyReader)\n\tc := &http.Client{}\n\twithClient := New(c)\n\tassert.Equal(t, withClient.Client, c)\n}\n\nfunc TestClient_Do(t *testing.T) {\n\tsuccessHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\t_, err := w.Write([]byte(\"buf\"))\n\t\tassert.NoError(t, err)\n\t}\n\n\ttt := map[string]struct {\n\t\tinput      *httputil.Request\n\t\thandler    http.HandlerFunc\n\t\tresponder  func(m *mocks.Responder)\n\t\tbodyReader func(r io.Reader) ([]byte, error)\n\t\twant       interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\thandler: successHandler,\n\t\t\tresponder: func(m *mocks.Responder) {\n\t\t\t\tm.On(\"Unmarshal\", mock.Anything).\n\t\t\t\t\tReturn(nil)\n\t\t\t\tm.On(\"CheckError\", mock.Anything, []byte(\"buf\")).\n\t\t\t\t\tReturn(nil)\n\t\t\t\tm.On(\"Meta\", mock.Anything).\n\t\t\t\t\tReturn(httputil.Meta{Message: \"message\", ID: \"10\"})\n\t\t\t},\n\t\t\tbodyReader: io.ReadAll,\n\t\t\twant: mail.Response{\n\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\tBody:       []byte(\"buf\"),\n\t\t\t\tHeaders:    nil,\n\t\t\t\tID:         \"10\",\n\t\t\t\tMessage:    \"message\",\n\t\t\t},\n\t\t},\n\t\t\"Bad Request\": {\n\t\t\tinput: &httputil.Request{URL: \"@#@#$$%$\"},\n\t\t\twant:  \"Error creating http request\",\n\t\t},\n\t\t\"Do Error\": {\n\t\t\tinput: &httputil.Request{URL: \"wrong\"},\n\t\t\twant:  \"Error doing request\",\n\t\t},\n\t\t\"Body Read Error\": {\n\t\t\thandler: successHandler,\n\t\t\tbodyReader: func(r io.Reader) ([]byte, error) {\n\t\t\t\treturn nil, errors.New(\"body read error\")\n\t\t\t},\n\t\t\twant: \"Error reading response body\",\n\t\t},\n\t\t\"Unmarshal Error\": {\n\t\t\thandler: successHandler,\n\t\t\tresponder: func(m *mocks.Responder) {\n\t\t\t\tm.On(\"Unmarshal\", mock.Anything).\n\t\t\t\t\tReturn(errors.New(\"unmarshal error\"))\n\t\t\t},\n\t\t\tbodyReader: io.ReadAll,\n\t\t\twant:       \"Error unmarshalling response error\",\n\t\t},\n\t\t\"Responder Error\": {\n\t\t\thandler: successHandler,\n\t\t\tresponder: func(m *mocks.Responder) {\n\t\t\t\tm.On(\"Unmarshal\", mock.Anything).\n\t\t\t\t\tReturn(nil)\n\t\t\t\tm.On(\"CheckError\", mock.Anything, []byte(\"buf\")).\n\t\t\t\t\tReturn(errors.New(\"response error\"))\n\t\t\t},\n\t\t\tbodyReader: io.ReadAll,\n\t\t\twant:       \"Error performing mail request\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tserver := httptest.NewServer(test.handler)\n\t\t\tdefer server.Close()\n\n\t\t\tif test.input == nil {\n\t\t\t\ttest.input = &httputil.Request{}\n\t\t\t}\n\n\t\t\tif test.input.URL == \"\" {\n\t\t\t\ttest.input.URL = server.URL\n\t\t\t}\n\n\t\t\tresponder := &mocks.Responder{}\n\t\t\tif test.responder != nil {\n\t\t\t\ttest.responder(responder)\n\t\t\t}\n\n\t\t\tc := Client{\n\t\t\t\tClient:     server.Client(),\n\t\t\t\tbodyReader: test.bodyReader,\n\t\t\t}\n\n\t\t\tgot, err := c.Do(context.Background(), test.input, nil, responder)\n\t\t\tif err != nil {\n\t\t\t\tassert.Contains(t, errors.Message(err), test.want)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\twant := test.want.(mail.Response)\n\n\t\t\tassert.Equal(t, want.StatusCode, got.StatusCode)\n\t\t\tassert.Equal(t, want.Body, got.Body)\n\t\t\tassert.NotEmpty(t, got.Headers)\n\t\t\tassert.Equal(t, want.ID, got.ID)\n\t\t\tassert.Equal(t, want.Message, got.Message)\n\t\t})\n\t}\n}\n\nfunc TestClient_MakeRequest(t *testing.T) {\n\turi, err := url.Parse(\"https://gomail.example.com\")\n\tassert.NoError(t, err)\n\n\ttt := map[string]struct {\n\t\trequest *httputil.Request\n\t\tpayload func(m *mocks.Payload)\n\t\twant    interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\t&httputil.Request{\n\t\t\t\tMethod:            http.MethodPost,\n\t\t\t\tURL:               \"https://gomail.example.com\",\n\t\t\t\tBasicAuthUser:     \"user\",\n\t\t\t\tBasicAuthPassword: \"password\",\n\t\t\t\tHeaders:           map[string]string{\"header\": \"Value\"},\n\t\t\t},\n\t\t\tfunc(m *mocks.Payload) {\n\t\t\t\tm.On(\"Buffer\").\n\t\t\t\t\tReturn(&bytes.Buffer{}, nil)\n\t\t\t\tm.On(\"ContentType\").\n\t\t\t\t\tReturn(httputil.JSONContentType)\n\t\t\t\tm.On(\"Values\").\n\t\t\t\t\tReturn(map[string]string{\"key\": \"value\"})\n\t\t\t},\n\t\t\t&http.Request{\n\t\t\t\tMethod: http.MethodPost,\n\t\t\t\tURL:    uri,\n\t\t\t\tHeader: map[string][]string{\n\t\t\t\t\t\"Authorization\": {\"Basic dXNlcjpwYXNzd29yZA==\"},\n\t\t\t\t\t\"Content-Type\":  {httputil.JSONContentType},\n\t\t\t\t\t\"Header\":        {\"Value\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"Buffer Error\": {\n\t\t\t&httputil.Request{},\n\t\t\tfunc(m *mocks.Payload) {\n\t\t\t\tm.On(\"Buffer\").\n\t\t\t\t\tReturn(nil, &errors.Error{Message: \"buffer error\"})\n\t\t\t},\n\t\t\t\"buffer error\",\n\t\t},\n\t\t\"Request Error\": {\n\t\t\t&httputil.Request{\n\t\t\t\tURL: \"@#@#$$%$\",\n\t\t\t},\n\t\t\tfunc(m *mocks.Payload) {\n\t\t\t\tm.On(\"Buffer\").\n\t\t\t\t\tReturn(&bytes.Buffer{}, nil)\n\t\t\t},\n\t\t\t\"Error creating http request\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tdefer func() { mail.Debug = false }()\n\t\t\tmail.Debug = true\n\n\t\t\tc := New(nil)\n\n\t\t\tmock := &mocks.Payload{}\n\t\t\tif test.payload != nil {\n\t\t\t\ttest.payload(mock)\n\t\t\t}\n\n\t\t\trequest, err := c.makeRequest(context.Background(), test.request, mock)\n\t\t\tif err != nil {\n\t\t\t\tassert.Contains(t, errors.Message(err), test.want)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\twant := test.want.(*http.Request)\n\t\t\tassert.Equal(t, want.Method, request.Method)\n\t\t\tassert.Equal(t, want.URL, request.URL)\n\t\t\tassert.Equal(t, want.Header, request.Header)\n\t\t})\n\t}\n}\n\nfunc TestClient_CurlString(t *testing.T) {\n\turi, err := url.Parse(\"https://gomail.example.com\")\n\tassert.NoError(t, err)\n\n\treq := &http.Request{\n\t\tMethod: http.MethodGet,\n\t\tURL:    uri,\n\t\tHeader: map[string][]string{\"header\": {\"value\"}},\n\t}\n\n\tpayload := mocks.Payload{}\n\tpayload.On(\"Values\").\n\t\tReturn(map[string]string{\"key\": \"value\"})\n\n\tc := Client{}\n\tgot := c.curlString(req, &payload)\n\twant := \"curl -i -X GET https://gomail.example.com -H \\\"header: value\\\"  -F key='value'\"\n\tassert.Equal(t, want, got)\n}\n"
  },
  {
    "path": "internal/client/util.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage client\n\n// Is2XX returns true if the provided HTTP response code is\n// in the range 200-299.\nfunc Is2XX(code int) bool {\n\tif code < 300 && code >= 200 {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "internal/client/util_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage client\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"net/http\"\n\t\"testing\"\n)\n\nfunc TestIs2XX(t *testing.T) {\n\ttt := map[string]struct {\n\t\tinput int\n\t\twant  bool\n\t}{\n\t\t\"< 200\": {\n\t\t\thttp.StatusContinue,\n\t\t\tfalse,\n\t\t},\n\t\t\"200\": {\n\t\t\thttp.StatusOK,\n\t\t\ttrue,\n\t\t},\n\t\t\"300 >\": {\n\t\t\thttp.StatusMultipleChoices,\n\t\t\tfalse,\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot := Is2XX(test.input)\n\t\t\tassert.Equal(t, test.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/errors/errors.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage errors\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n)\n\n// Application error codes.\nconst (\n\t// CONFLICT - An action cannot be performed.\n\tCONFLICT = \"conflict\"\n\t// INTERNAL - Error within Go Mail\n\tINTERNAL = \"internal\" // Internal error\n\t// INVALID - Validation failed\n\tINVALID = \"invalid\" // Validation failed\n\t// API - Error in the http request.\n\tAPI = \"api\"\n\t// Prefix is the string prefixed to an error message.\n\tPrefix = \"go-mail\"\n\t// GlobalError is a general message when no error message\n\t// has been found.\n\tGlobalError = \"An error has occurred.\"\n)\n\n// Error defines a standard application error.\ntype Error struct {\n\tCode      string `json:\"code\"`\n\tMessage   string `json:\"message\"`\n\tOperation string `json:\"operation\"`\n\tErr       error  `json:\"error\"`\n}\n\n// Error returns the string representation of the error\n// message.\nfunc (e *Error) Error() string {\n\tvar buf bytes.Buffer\n\n\t// Print the prefix\n\tfmt.Fprintf(&buf, \"%s: \", Prefix)\n\n\t// Print the current operation in our stack, if any.\n\tif e.Operation != \"\" && mail.Debug {\n\t\tfmt.Fprintf(&buf, \"%s: \", e.Operation)\n\t}\n\n\t// If wrapping an error, print its Error() message.\n\t// Otherwise, print the error code & message.\n\tif e.Err != nil {\n\t\tbuf.WriteString(e.Err.Error())\n\t} else {\n\t\tif e.Code != \"\" {\n\t\t\t_, _ = fmt.Fprintf(&buf, \"<%s> \", e.Code)\n\t\t}\n\t\tbuf.WriteString(e.Message)\n\t}\n\n\treturn buf.String()\n}\n\n// Code returns the code of the root error, if available.\n// Otherwise, returns INTERNAL.\nfunc Code(err error) string {\n\tif err == nil {\n\t\treturn \"\"\n\t} else if e, ok := err.(*Error); ok && e.Code != \"\" {\n\t\treturn e.Code\n\t} else if ok && e.Err != nil {\n\t\treturn Code(e.Err)\n\t}\n\treturn INTERNAL\n}\n\n// Message returns the human-readable message of the error,\n// if available. Otherwise, returns a generic error\n// message.\nfunc Message(err error) string {\n\tif err == nil {\n\t\treturn \"\"\n\t} else if e, ok := err.(*Error); ok && e.Message != \"\" {\n\t\treturn e.Message\n\t} else if ok && e.Err != nil {\n\t\treturn Message(e.Err)\n\t}\n\treturn GlobalError\n}\n\n// ToError Returns a Go Mail error from input. If The type\n// is not of type Error, nil will be returned.\nfunc ToError(err interface{}) *Error {\n\tswitch v := err.(type) {\n\tcase *Error:\n\t\treturn v\n\tcase Error:\n\t\treturn &v\n\tcase error:\n\t\treturn &Error{Err: fmt.Errorf(v.Error())}\n\tcase string:\n\t\treturn &Error{Err: fmt.Errorf(v)}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// New is a wrapper for the stdlib new function.\nfunc New(text string) error {\n\treturn errors.New(text)\n}\n"
  },
  {
    "path": "internal/errors/errors_test.go",
    "content": "package errors\n\nimport (\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"testing\"\n)\n\nfunc TestError_Error(t *testing.T) {\n\ttt := map[string]struct {\n\t\tinput *Error\n\t\tdebug bool\n\t\twant  string\n\t}{\n\t\t\"Normal\": {\n\t\t\t&Error{Code: INTERNAL, Message: \"test\", Operation: \"op\", Err: fmt.Errorf(\"err\")},\n\t\t\tfalse,\n\t\t\tfmt.Sprintf(\"%s: err\", Prefix),\n\t\t},\n\t\t\"Debug\": {\n\t\t\t&Error{Code: INTERNAL, Message: \"test\", Operation: \"op\", Err: fmt.Errorf(\"err\")},\n\t\t\ttrue,\n\t\t\tfmt.Sprintf(\"%s: op: err\", Prefix),\n\t\t},\n\t\t\"Nil Operation\": {\n\t\t\t&Error{Code: INTERNAL, Message: \"test\", Operation: \"\", Err: fmt.Errorf(\"err\")},\n\t\t\tfalse,\n\t\t\tfmt.Sprintf(\"%s: err\", Prefix),\n\t\t},\n\t\t\"Nil Err\": {\n\t\t\t&Error{Code: INTERNAL, Message: \"test\", Operation: \"\", Err: nil},\n\t\t\tfalse,\n\t\t\tfmt.Sprintf(\"%s: <internal> test\", Prefix),\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tdefer func() { mail.Debug = false }()\n\t\t\tmail.Debug = test.debug\n\t\t\tassert.Equal(t, test.want, test.input.Error())\n\t\t})\n\t}\n}\n\nfunc TestError_Code(t *testing.T) {\n\ttt := map[string]struct {\n\t\tinput error\n\t\twant  string\n\t}{\n\t\t\"Normal\": {\n\t\t\t&Error{Code: INTERNAL, Message: \"test\", Operation: \"op\", Err: fmt.Errorf(\"err\")},\n\t\t\t\"internal\",\n\t\t},\n\t\t\"Nil Input\": {\n\t\t\tnil,\n\t\t\t\"\",\n\t\t},\n\t\t\"Nil Code\": {\n\t\t\t&Error{Code: \"\", Message: \"test\", Operation: \"op\", Err: fmt.Errorf(\"err\")},\n\t\t\t\"internal\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tassert.Equal(t, test.want, Code(test.input))\n\t\t})\n\t}\n}\n\nfunc Test_Message(t *testing.T) {\n\ttt := map[string]struct {\n\t\tinput error\n\t\twant  string\n\t}{\n\t\t\"Normal\": {\n\t\t\t&Error{Code: INTERNAL, Message: \"test\", Operation: \"op\", Err: fmt.Errorf(\"err\")},\n\t\t\t\"test\",\n\t\t},\n\t\t\"Nil Input\": {\n\t\t\tnil,\n\t\t\t\"\",\n\t\t},\n\t\t\"Nil Message\": {\n\t\t\t&Error{Code: \"\", Message: \"\", Operation: \"op\", Err: fmt.Errorf(\"err\")},\n\t\t\tGlobalError,\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tassert.Equal(t, test.want, Message(test.input))\n\t\t})\n\t}\n}\n\nfunc TestError_ToError(t *testing.T) {\n\ttt := map[string]struct {\n\t\tinput interface{}\n\t\twant  *Error\n\t}{\n\t\t\"Pointer\": {\n\t\t\t&Error{Code: INTERNAL, Message: \"test\", Operation: \"op\", Err: fmt.Errorf(\"err\")},\n\t\t\t&Error{Code: INTERNAL, Message: \"test\", Operation: \"op\", Err: fmt.Errorf(\"err\")},\n\t\t},\n\t\t\"Non Pointer\": {\n\t\t\tError{Code: INTERNAL, Message: \"test\", Operation: \"op\", Err: fmt.Errorf(\"err\")},\n\t\t\t&Error{Code: INTERNAL, Message: \"test\", Operation: \"op\", Err: fmt.Errorf(\"err\")},\n\t\t},\n\t\t\"Error\": {\n\t\t\tfmt.Errorf(\"err\"),\n\t\t\t&Error{Err: fmt.Errorf(\"err\")},\n\t\t},\n\t\t\"String\": {\n\t\t\t\"err\",\n\t\t\t&Error{Err: fmt.Errorf(\"err\")},\n\t\t},\n\t\t\"Default\": {\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tassert.Equal(t, test.want, ToError(test.input))\n\t\t})\n\t}\n}\n\nfunc TestNew(t *testing.T) {\n\twant := fmt.Errorf(\"error\")\n\tgot := New(\"error\")\n\tassert.Errorf(t, want, got.Error())\n}\n"
  },
  {
    "path": "internal/httputil/payload.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage httputil\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/internal/errors\"\n\t\"io\"\n\t\"mime/multipart\"\n)\n\n// Payload defines the methods used for creating  HTTP payload\n// helper.\ntype Payload interface {\n\t// Buffer returns the byte buffer used for making the\n\t// HTTP Request\n\tBuffer() (*bytes.Buffer, error)\n\t// ContentType returns the `Content-Type` header used for\n\t// making the HTTP Request\n\tContentType() string\n\t// Values returns a map of key - value pairs used for testing\n\t// and debugging.\n\tValues() map[string]string\n}\n\nconst (\n\t// JSONContentType is the Content-Type header for\n\t// JSON payloads.\n\tJSONContentType = \"application/json\"\n)\n\n// JSONData defines the payload for JSON types.\ntype JSONData struct {\n\toriginal interface{}\n\tvalues   map[string]interface{}\n}\n\n// NewJSONData creates a new JSON Data Payload type.\n// It adds a struct type to the JSON Data payload.\n// Returns an error if the struct could not be marshalled or unmarshalled.\nfunc NewJSONData(obj interface{}) (*JSONData, error) {\n\tconst op = \"HTTPUtil.NewJSONData\"\n\n\tbuf, err := json.Marshal(obj)\n\tif err != nil {\n\t\treturn nil, &errors.Error{Code: errors.INTERNAL, Message: \"Error marshalling payload\", Operation: op, Err: err}\n\t}\n\n\tm := make(map[string]interface{})\n\terr = json.Unmarshal(buf, &m)\n\tif err != nil {\n\t\treturn nil, &errors.Error{Code: errors.INTERNAL, Message: \"Error unmarshalling payload\", Operation: op, Err: err}\n\t}\n\n\treturn &JSONData{\n\t\toriginal: obj,\n\t\tvalues:   m,\n\t}, nil\n}\n\n// Buffer returns the byte buffer for making the request.\nfunc (j *JSONData) Buffer() (*bytes.Buffer, error) {\n\tconst op = \"JSONData.Buffer\"\n\tbuf, err := json.Marshal(j.values)\n\tif err != nil {\n\t\treturn nil, &errors.Error{Code: errors.INTERNAL, Message: \"Error marshalling values\", Operation: op, Err: err}\n\t}\n\treturn bytes.NewBuffer(buf), nil\n}\n\n// ContentType returns the `Content-Type` header.\nfunc (j *JSONData) ContentType() string {\n\treturn JSONContentType\n}\n\n// Values returns a map of key - value pairs used for testing\n// and debugging.\nfunc (j *JSONData) Values() map[string]string {\n\tm := make(map[string]string)\n\tfor key, value := range j.values {\n\t\tm[key] = fmt.Sprintf(\"%v\", value)\n\t}\n\treturn m\n}\n\n// FormData defines the payload for URL encoded types.\ntype FormData struct {\n\tcontentType string\n\tvalues      map[string]string\n\tbuffers     []keyNameBuff\n}\n\n// keyNameBuff defines the buffer for multipart attachments.\ntype keyNameBuff struct {\n\tkey   string\n\tname  string\n\tvalue []byte\n}\n\n// NewFormData creates a new Form Data Payload type.\nfunc NewFormData() *FormData {\n\treturn &FormData{}\n}\n\n// AddValue adds a key - value string pair to the Payload.\nfunc (f *FormData) AddValue(key, value string) {\n\tif len(f.values) == 0 {\n\t\tf.values = make(map[string]string)\n\t}\n\tf.values[key] = value\n}\n\n// AddBuffer adds a file buffer to the Payload with a filename.\nfunc (f *FormData) AddBuffer(key, fileName string, buff []byte) {\n\tf.buffers = append(f.buffers, keyNameBuff{\n\t\tkey:   key,\n\t\tname:  fileName,\n\t\tvalue: buff,\n\t})\n}\n\nvar newWriter = multipart.NewWriter\n\n// Buffer returns the byte buffer for making the request.\nfunc (f *FormData) Buffer() (*bytes.Buffer, error) {\n\tconst op = \"FormData.Buffer\"\n\n\tdata := &bytes.Buffer{}\n\twriter := newWriter(data)\n\tdefer writer.Close()\n\n\tfor key, val := range f.values {\n\t\tif tmp, err := writer.CreateFormField(key); err == nil {\n\t\t\ttmp.Write([]byte(val)) // nolint\n\t\t} else {\n\t\t\treturn nil, &errors.Error{Code: errors.INTERNAL, Message: \"Error creating form field\", Operation: op, Err: err}\n\t\t}\n\t}\n\n\tfor _, buff := range f.buffers {\n\t\tif tmp, err := writer.CreateFormFile(buff.key, buff.name); err == nil {\n\t\t\tr := bytes.NewReader(buff.value)\n\t\t\tio.Copy(tmp, r) // nolint\n\t\t} else {\n\t\t\treturn nil, &errors.Error{Code: errors.INTERNAL, Message: \"Error creating form file\", Operation: op, Err: err}\n\t\t}\n\t}\n\n\tf.contentType = writer.FormDataContentType()\n\n\treturn data, nil\n}\n\n// ContentType returns the `Content-Type` header.\nfunc (f *FormData) ContentType() string {\n\tif f.contentType == \"\" {\n\t\tf.Buffer() // nolint\n\t}\n\treturn f.contentType\n}\n\n// Values returns a map of key - value pairs used for testing\n// and debugging.\nfunc (f *FormData) Values() map[string]string {\n\treturn f.values\n}\n"
  },
  {
    "path": "internal/httputil/payload_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage httputil\n\nimport (\n\t\"github.com/ainsleyclark/go-mail/internal/errors\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"testing\"\n)\n\nfunc TestNewJSONData(t *testing.T) {\n\ttt := map[string]struct {\n\t\tinput interface{}\n\t\twant  interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\tmap[string]interface{}{\"test\": 1},\n\t\t\tmap[string]interface{}{\"test\": float64(1)},\n\t\t},\n\t\t\"Marshal Error\": {\n\t\t\tmap[string]interface{}{\"test\": make(chan struct{})},\n\t\t\t\"Error marshalling payload\",\n\t\t},\n\t\t\"Unmarshal Error\": {\n\t\t\t1,\n\t\t\t\"Error unmarshalling payload\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tpl, err := NewJSONData(test.input)\n\t\t\tif err != nil {\n\t\t\t\tassert.Contains(t, errors.Message(err), test.want)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassert.Equal(t, test.want, pl.values)\n\t\t\tassert.NotNil(t, pl.original)\n\t\t})\n\t}\n}\n\nfunc TestJSONData_Buffer(t *testing.T) {\n\ttt := map[string]struct {\n\t\tinput JSONData\n\t\twant  interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\tJSONData{values: map[string]interface{}{\"test\": 1}},\n\t\t\t`{\"test\":1}`,\n\t\t},\n\t\t\"Marshal Error\": {\n\t\t\tJSONData{values: map[string]interface{}{\"test\": make(chan struct{})}},\n\t\t\t\"unsupported type\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot, err := test.input.Buffer()\n\t\t\tif err != nil {\n\t\t\t\tassert.Contains(t, err.Error(), test.want)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassert.Equal(t, test.want, got.String())\n\t\t})\n\t}\n}\n\nfunc TestJsonData_ContentType(t *testing.T) {\n\tpl := JSONData{}\n\tgot := pl.ContentType()\n\tassert.Equal(t, JSONContentType, got)\n}\n\nfunc TestJSONData_Values(t *testing.T) {\n\tpl := JSONData{values: map[string]interface{}{\"test\": 1}}\n\tgot := pl.Values()\n\twant := map[string]string{\"test\": \"1\"}\n\tassert.Equal(t, want, got)\n}\n\nfunc TestFormData_AddValue(t *testing.T) {\n\tpl := NewFormData()\n\tpl.AddValue(\"key\", \"value\")\n\twant := map[string]string{\"key\": \"value\"}\n\tassert.Equal(t, want, pl.values)\n}\n\nfunc TestFormData_AddBuffer(t *testing.T) {\n\tpl := NewFormData()\n\tpl.AddBuffer(\"key\", \"file\", []byte(\"value\"))\n\twant := []keyNameBuff{\n\t\t{key: \"key\", name: \"file\", value: []byte(\"value\")},\n\t}\n\tassert.Equal(t, want, pl.buffers)\n}\n\ntype mockWriterError struct{}\n\nfunc (m *mockWriterError) Write(p []byte) (n int, err error) {\n\treturn 0, errors.New(\"write error\")\n}\n\nfunc TestFormData_Buffer(t *testing.T) {\n\ttt := map[string]struct {\n\t\tinput  FormData\n\t\twriter func(w io.Writer) *multipart.Writer\n\t\twant   interface{}\n\t}{\n\t\t\"Success\": {\n\t\t\tFormData{\n\t\t\t\tvalues: map[string]string{\n\t\t\t\t\t\"key\": \"value\",\n\t\t\t\t},\n\t\t\t\tbuffers: []keyNameBuff{\n\t\t\t\t\t{key: \"key\", name: \"file\", value: []byte(\"value\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\tmultipart.NewWriter,\n\t\t\t\"Content-Disposition\",\n\t\t},\n\t\t\"Value Error\": {\n\t\t\tFormData{\n\t\t\t\tvalues: map[string]string{\"key\": \"value\"},\n\t\t\t},\n\t\t\tfunc(w io.Writer) *multipart.Writer {\n\t\t\t\treturn multipart.NewWriter(&mockWriterError{})\n\t\t\t},\n\t\t\t\"write error\",\n\t\t},\n\t\t\"Buffer Error\": {\n\t\t\tFormData{\n\t\t\t\tbuffers: []keyNameBuff{\n\t\t\t\t\t{key: \"key\", name: \"file\", value: []byte(\"value\")},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunc(w io.Writer) *multipart.Writer {\n\t\t\t\treturn multipart.NewWriter(&mockWriterError{})\n\t\t\t},\n\t\t\t\"write error\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\torig := newWriter\n\t\t\tdefer func() { newWriter = orig }()\n\t\t\tnewWriter = test.writer\n\n\t\t\tgot, err := test.input.Buffer()\n\t\t\tif err != nil {\n\t\t\t\tassert.Contains(t, err.Error(), test.want)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Contains(t, got.String(), test.want)\n\t\t})\n\t}\n}\n\nfunc TestFormData_ContentType(t *testing.T) {\n\tpl := FormData{values: map[string]string{\"test\": \"1\"}}\n\tgot := pl.ContentType()\n\twant := \"multipart/form-data\"\n\tassert.Contains(t, got, want)\n}\n\nfunc TestFormData_Values(t *testing.T) {\n\tpl := FormData{values: map[string]string{\"test\": \"1\"}}\n\tgot := pl.Values()\n\twant := map[string]string{\"test\": \"1\"}\n\tassert.Equal(t, want, got)\n}\n"
  },
  {
    "path": "internal/httputil/request.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage httputil\n\n// A Request represents an HTTP request received by a server\n// or to be sent by a client. It is an extension of the\n// std http.Request for Go Mail.\ntype Request struct {\n\tMethod            string\n\tURL               string\n\tHeaders           map[string]string\n\tBasicAuthUser     string\n\tBasicAuthPassword string\n}\n\n// NewHTTPRequest returns a new Request given a method and URL.\nfunc NewHTTPRequest(method, url string) *Request {\n\treturn &Request{\n\t\tMethod: method,\n\t\tURL:    url,\n\t}\n}\n\n// AddHeader adds the key, value pair to the request headers.\nfunc (r *Request) AddHeader(name, value string) {\n\tif r.Headers == nil {\n\t\tr.Headers = make(map[string]string)\n\t}\n\tr.Headers[name] = value\n}\n\n// SetBasicAuth sets the request's Authorization header to use HTTP\n// Basic Authentication with the provided username and password.\n//\n// With HTTP Basic Authentication the provided username and password\n// are not encrypted.\nfunc (r *Request) SetBasicAuth(user, password string) {\n\tr.BasicAuthUser = user\n\tr.BasicAuthPassword = password\n}\n"
  },
  {
    "path": "internal/httputil/request_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage httputil\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"net/http\"\n\t\"testing\"\n)\n\nfunc TestNewHTTPRequest(t *testing.T) {\n\treq := NewHTTPRequest(http.MethodGet, \"https://gomail.example.com\")\n\tassert.Equal(t, http.MethodGet, req.Method)\n\tassert.Equal(t, \"https://gomail.example.com\", req.URL)\n}\n\nfunc TestRequest_AddHeader(t *testing.T) {\n\treq := NewHTTPRequest(http.MethodGet, \"https://gomail.example.com\")\n\treq.AddHeader(\"header\", \"value\")\n\twant := map[string]string{\"header\": \"value\"}\n\tassert.Equal(t, want, req.Headers)\n}\n\nfunc TestRequest_SetBasicAuth(t *testing.T) {\n\treq := NewHTTPRequest(http.MethodGet, \"https://gomail.example.com\")\n\treq.SetBasicAuth(\"user\", \"pass\")\n\tassert.Equal(t, \"user\", req.BasicAuthUser)\n\tassert.Equal(t, \"pass\", req.BasicAuthPassword)\n}\n"
  },
  {
    "path": "internal/httputil/response.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage httputil\n\nimport \"net/http\"\n\n// Responder defines the methods used for a response back\n// from a mailer's API.\ntype Responder interface {\n\tUnmarshal(buf []byte) error\n\tCheckError(response *http.Response, buf []byte) error\n\tMeta() Meta\n}\n\n// Meta defines the data used for creating a mail.Response.\ntype Meta struct {\n\tMessage string\n\tID      string\n}\n"
  },
  {
    "path": "internal/mime/mime.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mime\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n)\n\nconst (\n\t// sniffLength is the amount of bytes to read to\n\t// detect the MIME type.\n\tsniffLength uint32 = 512\n)\n\n// DetectBuffer returns the MIME type found from the provided byte slice.\n//\n// The result is always a valid MIME type, with application/octet-stream\n// returned when identification failed.\n// Uses http.DetectContentType with a layer for SVG detection.\nfunc DetectBuffer(buf []byte) string {\n\theader := make([]byte, sniffLength)\n\tcopy(header, buf)\n\n\t// Detect for SVGs\n\t// See https://github.com/golang/go/issues/15888\n\tif bytes.Contains(header, []byte(\"<svg\")) {\n\t\treturn \"image/svg+xml\"\n\t}\n\n\treturn http.DetectContentType(header)\n}\n"
  },
  {
    "path": "internal/mime/mime_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mime\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestDetectBuffer(t *testing.T) {\n\ttt := map[string]struct {\n\t\tinput string\n\t\twant  string\n\t}{\n\t\t\"PNG\": {\n\t\t\t\"gopher.png\",\n\t\t\t\"image/png\",\n\t\t},\n\t\t\"JPG\": {\n\t\t\t\"gopher.jpg\",\n\t\t\t\"image/jpeg\",\n\t\t},\n\t\t\"SVG\": {\n\t\t\t\"gopher.svg\",\n\t\t\t\"image/svg+xml\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\twd, err := os.Getwd()\n\t\t\tassert.NoError(t, err)\n\n\t\t\tpath := filepath.Join(filepath.Join(wd, \"../../testdata\"), test.input)\n\t\t\tfile, err := os.ReadFile(path)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tgot := DetectBuffer(file)\n\t\t\tassert.Equal(t, test.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/mocks/client/Requester.go",
    "content": "// Code generated by mockery v2.9.4. DO NOT EDIT.\n\npackage mocks\n\nimport (\n\tcontext \"context\"\n\n\thttputil \"github.com/ainsleyclark/go-mail/internal/httputil\"\n\tmail \"github.com/ainsleyclark/go-mail/mail\"\n\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// Requester is an autogenerated mock type for the Requester type\ntype Requester struct {\n\tmock.Mock\n}\n\n// Do provides a mock function with given fields: ctx, r, payload, responder\nfunc (_m *Requester) Do(ctx context.Context, r *httputil.Request, payload httputil.Payload, responder httputil.Responder) (mail.Response, error) {\n\tret := _m.Called(ctx, r, payload, responder)\n\n\tvar r0 mail.Response\n\tif rf, ok := ret.Get(0).(func(context.Context, *httputil.Request, httputil.Payload, httputil.Responder) mail.Response); ok {\n\t\tr0 = rf(ctx, r, payload, responder)\n\t} else {\n\t\tr0 = ret.Get(0).(mail.Response)\n\t}\n\n\tvar r1 error\n\tif rf, ok := ret.Get(1).(func(context.Context, *httputil.Request, httputil.Payload, httputil.Responder) error); ok {\n\t\tr1 = rf(ctx, r, payload, responder)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n"
  },
  {
    "path": "internal/mocks/drivers/smtpSendFunc.go",
    "content": "// Code generated by mockery v2.9.4. DO NOT EDIT.\n\npackage mocks\n\nimport (\n\tsmtp \"net/smtp\"\n\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// smtpSendFunc is an autogenerated mock type for the smtpSendFunc type\ntype smtpSendFunc struct {\n\tmock.Mock\n}\n\n// Execute provides a mock function with given fields: addr, a, from, to, msg\nfunc (_m *smtpSendFunc) Execute(addr string, a smtp.Auth, from string, to []string, msg []byte) error {\n\tret := _m.Called(addr, a, from, to, msg)\n\n\tvar r0 error\n\tif rf, ok := ret.Get(0).(func(string, smtp.Auth, string, []string, []byte) error); ok {\n\t\tr0 = rf(addr, a, from, to, msg)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\n\treturn r0\n}\n"
  },
  {
    "path": "internal/mocks/httputil/Payload.go",
    "content": "// Code generated by mockery v2.9.4. DO NOT EDIT.\n\npackage mocks\n\nimport (\n\tbytes \"bytes\"\n\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// Payload is an autogenerated mock type for the Payload type\ntype Payload struct {\n\tmock.Mock\n}\n\n// Buffer provides a mock function with given fields:\nfunc (_m *Payload) Buffer() (*bytes.Buffer, error) {\n\tret := _m.Called()\n\n\tvar r0 *bytes.Buffer\n\tif rf, ok := ret.Get(0).(func() *bytes.Buffer); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*bytes.Buffer)\n\t\t}\n\t}\n\n\tvar r1 error\n\tif rf, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = rf()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n\n// ContentType provides a mock function with given fields:\nfunc (_m *Payload) ContentType() string {\n\tret := _m.Called()\n\n\tvar r0 string\n\tif rf, ok := ret.Get(0).(func() string); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tr0 = ret.Get(0).(string)\n\t}\n\n\treturn r0\n}\n\n// Values provides a mock function with given fields:\nfunc (_m *Payload) Values() map[string]string {\n\tret := _m.Called()\n\n\tvar r0 map[string]string\n\tif rf, ok := ret.Get(0).(func() map[string]string); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(map[string]string)\n\t\t}\n\t}\n\n\treturn r0\n}\n"
  },
  {
    "path": "internal/mocks/httputil/Responder.go",
    "content": "// Code generated by mockery v2.9.4. DO NOT EDIT.\n\npackage mocks\n\nimport (\n\thttp \"net/http\"\n\n\thttputil \"github.com/ainsleyclark/go-mail/internal/httputil\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// Responder is an autogenerated mock type for the Responder type\ntype Responder struct {\n\tmock.Mock\n}\n\n// CheckError provides a mock function with given fields: response, buf\nfunc (_m *Responder) CheckError(response *http.Response, buf []byte) error {\n\tret := _m.Called(response, buf)\n\n\tvar r0 error\n\tif rf, ok := ret.Get(0).(func(*http.Response, []byte) error); ok {\n\t\tr0 = rf(response, buf)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\n\treturn r0\n}\n\n// Meta provides a mock function with given fields:\nfunc (_m *Responder) Meta() httputil.Meta {\n\tret := _m.Called()\n\n\tvar r0 httputil.Meta\n\tif rf, ok := ret.Get(0).(func() httputil.Meta); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tr0 = ret.Get(0).(httputil.Meta)\n\t}\n\n\treturn r0\n}\n\n// Unmarshal provides a mock function with given fields: buf\nfunc (_m *Responder) Unmarshal(buf []byte) error {\n\tret := _m.Called(buf)\n\n\tvar r0 error\n\tif rf, ok := ret.Get(0).(func([]byte) error); ok {\n\t\tr0 = rf(buf)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\n\treturn r0\n}\n"
  },
  {
    "path": "internal/mocks/mail/Mailer.go",
    "content": "// Code generated by mockery v2.9.4. DO NOT EDIT.\n\npackage mocks\n\nimport (\n\tmail \"github.com/ainsleyclark/go-mail/mail\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// Mailer is an autogenerated mock type for the Mailer type\ntype Mailer struct {\n\tmock.Mock\n}\n\n// Send provides a mock function with given fields: t\nfunc (_m *Mailer) Send(t *mail.Transmission) (mail.Response, error) {\n\tret := _m.Called(t)\n\n\tvar r0 mail.Response\n\tif rf, ok := ret.Get(0).(func(*mail.Transmission) mail.Response); ok {\n\t\tr0 = rf(t)\n\t} else {\n\t\tr0 = ret.Get(0).(mail.Response)\n\t}\n\n\tvar r1 error\n\tif rf, ok := ret.Get(1).(func(*mail.Transmission) error); ok {\n\t\tr1 = rf(t)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n"
  },
  {
    "path": "mail/attachments.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"encoding/base64\"\n\t\"github.com/ainsleyclark/go-mail/internal/mime\"\n)\n\n// Attachment defines an email attachment for Go Mail.\n// It contains useful information for sending files via\n// the mail driver.\ntype Attachment struct {\n\tFilename string\n\tBytes    []byte\n}\n\n// Mime returns the mime type of the byte data.\nfunc (a Attachment) Mime() string {\n\treturn mime.DetectBuffer(a.Bytes)\n}\n\n// B64 returns the base 64 encoding of the attachment.\nfunc (a Attachment) B64() string {\n\treturn base64.StdEncoding.EncodeToString(a.Bytes)\n}\n"
  },
  {
    "path": "mail/attachments_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport \"fmt\"\n\nfunc ExampleAttachment_Mime() {\n\tsvg := `\n<svg width=\"100\" height=\"100\">\n  <circle cx=\"50\" cy=\"50\" r=\"40\" stroke=\"green\" stroke-width=\"4\" fill=\"yellow\" />\n</svg>`\n\n\ta := Attachment{\n\t\tFilename: \"circle.svg\",\n\t\tBytes:    []byte(svg),\n\t}\n\n\tfmt.Println(a.Mime())\n\t// Output: image/svg+xml\n}\n\nfunc (t *MailTestSuite) TestAttachment_Mime() {\n\ttt := map[string]struct {\n\t\tinput string\n\t\twant  string\n\t}{\n\t\t\"PNG\": {\n\t\t\tPNGName,\n\t\t\t\"image/png\",\n\t\t},\n\t\t\"JPG\": {\n\t\t\tJPGName,\n\t\t\t\"image/jpeg\",\n\t\t},\n\t\t\"SVG\": {\n\t\t\tSVGName,\n\t\t\t\"image/svg+xml\",\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\ta := t.Attachment(test.input)\n\t\t\tgot := a.Mime()\n\t\t\tt.Equal(test.want, got)\n\t\t})\n\t}\n}\n\nfunc ExampleAttachment_B64() {\n\tsvg := `\n<svg width=\"100\" height=\"100\">\n  <circle cx=\"50\" cy=\"50\" r=\"40\" stroke=\"green\" stroke-width=\"4\" fill=\"yellow\" />\n</svg>`\n\n\ta := Attachment{\n\t\tFilename: \"circle.svg\",\n\t\tBytes:    []byte(svg),\n\t}\n\n\tfmt.Println(a.B64())\n\t// Output: Cjxzdmcgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiPgogIDxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIHI9IjQwIiBzdHJva2U9ImdyZWVuIiBzdHJva2Utd2lkdGg9IjQiIGZpbGw9InllbGxvdyIgLz4KPC9zdmc+\n}\n\nfunc (t *MailTestSuite) TestAttachment_B64() {\n\ta := Attachment{\n\t\tBytes: []byte(\"hello\"),\n\t}\n\tgot := a.B64()\n\tt.Equal(\"aGVsbG8=\", got)\n}\n"
  },
  {
    "path": "mail/config.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n)\n\n// Config represents the configuration passed when a new\n// client is constructed. Dependant on what driver is used,\n// different options are required to be present.\ntype Config struct {\n\tURL         string\n\tAPIKey      string\n\tDomain      string\n\tFromAddress string\n\tFromName    string\n\tPassword    string\n\tPort        int\n\tClient      *http.Client\n}\n\n// Validate runs sanity checks of a Config struct.\n// This is run before a new client is created\n// to ensure there are no invalid API\n// calls.\nfunc (c *Config) Validate() error {\n\tif c.FromAddress == \"\" {\n\t\treturn errors.New(\"driver requires from address\")\n\t}\n\tif c.FromName == \"\" {\n\t\treturn errors.New(\"driver requires from name\")\n\t}\n\tif c.APIKey == \"\" {\n\t\treturn errors.New(\"driver requires api key\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "mail/config_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc ExampleConfig_Validate() {\n\tcfg := Config{}\n\tfmt.Println(cfg.Validate())\n\t// Output: driver requires from address\n}\n\nfunc (t *MailTestSuite) TestConfig_Validate() {\n\ttt := map[string]struct {\n\t\tinput Config\n\t\twant  error\n\t}{\n\t\t\"Success\": {\n\t\t\tConfig{\n\t\t\t\tAPIKey:      \"key\",\n\t\t\t\tFromAddress: \"hello@test.com\",\n\t\t\t\tFromName:    \"Test\",\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t\"No From Address\": {\n\t\t\tConfig{\n\t\t\t\tAPIKey:   \"key\",\n\t\t\t\tFromName: \"Test\",\n\t\t\t},\n\t\t\terrors.New(\"driver requires from address\"),\n\t\t},\n\t\t\"No From Name\": {\n\t\t\tConfig{\n\t\t\t\tAPIKey:      \"key\",\n\t\t\t\tFromAddress: \"hello@test.com\",\n\t\t\t},\n\t\t\terrors.New(\"driver requires from name\"),\n\t\t},\n\t\t\"No Key\": {\n\t\t\tConfig{\n\t\t\t\tFromAddress: \"hello@test.com\",\n\t\t\t\tFromName:    \"Test\",\n\t\t\t},\n\t\t\terrors.New(\"driver requires api key\"),\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tgot := test.input.Validate()\n\t\t\tt.Equal(test.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "mail/mail.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport \"errors\"\n\nvar (\n\t// Debug - Set true to write the HTTP requests in curl to stdout.\n\t// Additional information will also be displayed in the errors such as\n\t// method operations.\n\tDebug = false\n\t// ErrEmptyBody is returned by Send when there is nobody attached to the\n\t// request.\n\tErrEmptyBody = errors.New(\"error, empty body\")\n)\n\n// Mailer defines the sender for go-mail returning a\n// Response or error when an email is sent.\n//\n// Below is an example of creating and sending a transmission:\n//\n//\t\tcfg := mail.Config{\n//\t   \t\tURL:         \"https://api.eu.sparkpost.com\",\n//\t   \t\tAPIKey:      \"my-key\",\n//\t   \t\tFromAddress: \"hello@gophers.com\",\n//\t   \t\tFromName:    \"Gopher\",\n//\t\t}\n//\n//\t\tmailer, err := drivers.NewSparkPost(cfg)\n//\t\tif err != nil {\n//\t\t\tlog.Fatalln(err)\n//\t\t}\n//\n//\t\ttx := &mail.Transmission{\n//\t \t\tRecipients:  []string{\"hello@gophers.com\"},\n//\t   \t\tSubject:     \"My email\",\n//\t   \t\tHTML:        \"<h1>Hello from Go Mail!</h1>\",\n//\t\t}\n//\n//\t\tresult, err := mailer.Send(tx)\n//\t\tif err != nil {\n//\t\t\tlog.Fatalln(err)\n//\t\t}\n//\n//\t\tfmt.Printf(\"%+v\\n\", result)\ntype Mailer interface {\n\t// Send accepts a mail.Transmission to send an email through a particular\n\t// driver/provider. Transmissions will be validated before sending.\n\t//\n\t// A mail.Response or an error will be returned. In some circumstances\n\t// the body and status code will be attached to the response for debugging.\n\tSend(t *Transmission) (Response, error)\n}\n"
  },
  {
    "path": "mail/mail_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"github.com/stretchr/testify/suite\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\n// MailTestSuite defines the helper used for mail\n// testing.\ntype MailTestSuite struct {\n\tsuite.Suite\n\tbase string\n}\n\n// Assert testing has begun.\nfunc TestMail(t *testing.T) {\n\tsuite.Run(t, new(MailTestSuite))\n}\n\n// Assigns test base.\nfunc (t *MailTestSuite) SetupSuite() {\n\twd, err := os.Getwd()\n\tt.NoError(err)\n\tt.base = wd\n}\n\nconst (\n\t// DataPath defines where the test data resides.\n\tDataPath = \"testdata\"\n\t// PNGName defines the PNG name for testing.\n\tPNGName = \"gopher.png\"\n\t// JPGName defines the JPG name for testing.\n\tJPGName = \"gopher.jpg\"\n\t// SVGName defines the SVG name testing.\n\tSVGName = \"gopher.svg\"\n)\n\n// Returns a PNG attachment for testing.\nfunc (t *MailTestSuite) Attachment(name string) Attachment {\n\tpath := filepath.Join(filepath.Dir(t.base), DataPath, name)\n\tfile, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fail(\"error getting attachment with the path: \"+path, err)\n\t}\n\treturn Attachment{\n\t\tFilename: name,\n\t\tBytes:    file,\n\t}\n}\n"
  },
  {
    "path": "mail/response.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport \"net/http\"\n\n// Response represents the data passed back from a successful transmission.\ntype Response struct {\n\tStatusCode int         // e.g. 200\n\tBody       []byte      // e.g. {\"result: success\"}\n\tHeaders    http.Header // e.g. map[X-Ratelimit-Limit:[600]]\n\tID         string      // e.g \"100\"\n\tMessage    string      // e.g \"Email sent successfully\"\n}\n"
  },
  {
    "path": "mail/transmissions.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"errors\"\n)\n\n// Transmission represents the JSON structure accepted by\n// and returned from the driver's API. Recipients,\n// HTML and a subject is required to send the\n// email.\ntype Transmission struct {\n\tRecipients  []string\n\tCC          []string\n\tBCC         []string\n\tSubject     string\n\tHTML        string\n\tPlainText   string\n\tAttachments []Attachment\n\tHeaders     map[string]string\n}\n\n// Validate runs sanity checks of a Transmission struct.\n// This is run before any email sending to ensure\n// there are no invalid API calls.\nfunc (t *Transmission) Validate() error {\n\tif t == nil {\n\t\treturn errors.New(\"can't validate a nil transmission\")\n\t}\n\n\tif len(t.Recipients) == 0 {\n\t\treturn errors.New(\"transmission requires recipients\")\n\t}\n\n\tif t.Subject == \"\" {\n\t\treturn errors.New(\"transmission requires a subject\")\n\t}\n\n\tif t.HTML == \"\" {\n\t\treturn errors.New(\"transmission requires html content\")\n\t}\n\n\treturn nil\n}\n\n// HasCC determines if there are any CC recipients\n// attached to the transmission.\nfunc (t *Transmission) HasCC() bool {\n\treturn len(t.CC) != 0\n}\n\n// HasBCC determines if there are any BCC recipients\n// attached to the transmission.\nfunc (t *Transmission) HasBCC() bool {\n\treturn len(t.BCC) != 0\n}\n\n// HasAttachments determines if there are any attachments in\n// the transmission.\nfunc (t Transmission) HasAttachments() bool {\n\treturn len(t.Attachments) != 0\n}\n"
  },
  {
    "path": "mail/transmissions_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc ExampleTransmission_Validate() {\n\tt := Transmission{}\n\tfmt.Println(t.Validate())\n\t// Output: transmission requires recipients\n}\n\nfunc (t *MailTestSuite) TestTransmission_Validate() {\n\ttt := map[string]struct {\n\t\tinput *Transmission\n\t\twant  error\n\t}{\n\t\t\"Success\": {\n\t\t\t&Transmission{\n\t\t\t\tRecipients: []string{\"hello@test.com\"},\n\t\t\t\tSubject:    \"subject\",\n\t\t\t\tHTML:       \"<h1>Hello</h1>\",\n\t\t\t},\n\t\t\tnil,\n\t\t},\n\t\t\"Nil\": {\n\t\t\tnil,\n\t\t\terrors.New(\"can't validate a nil transmission\"),\n\t\t},\n\t\t\"No Recipients\": {\n\t\t\t&Transmission{\n\t\t\t\tHTML:    \"html\",\n\t\t\t\tSubject: \"subject\",\n\t\t\t},\n\t\t\terrors.New(\"transmission requires recipients\"),\n\t\t},\n\t\t\"No Subject\": {\n\t\t\t&Transmission{\n\t\t\t\tRecipients: []string{\"hello@test.com\"},\n\t\t\t\tHTML:       \"html\",\n\t\t\t},\n\t\t\terrors.New(\"transmission requires a subject\"),\n\t\t},\n\t\t\"No HTML\": {\n\t\t\t&Transmission{\n\t\t\t\tRecipients: []string{\"hello@test.com\"},\n\t\t\t\tSubject:    \"subject\",\n\t\t\t},\n\t\t\terrors.New(\"transmission requires html content\"),\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tgot := test.input.Validate()\n\t\t\tt.Equal(test.want, got)\n\t\t})\n\t}\n}\n\nfunc ExampleTransmission_HasCC() {\n\tt := Transmission{\n\t\tCC: []string{\"cc@gophers.com\"},\n\t}\n\tfmt.Println(t.HasCC())\n\t// Output: true\n}\n\nfunc (t *MailTestSuite) TestConfig_HasCC() {\n\ttt := map[string]struct {\n\t\tinput Transmission\n\t\twant  bool\n\t}{\n\t\t\"With\": {\n\t\t\tTransmission{CC: []string{\"hello@test.com\"}},\n\t\t\ttrue,\n\t\t},\n\t\t\"Without\": {\n\t\t\tTransmission{},\n\t\t\tfalse,\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tgot := test.input.HasCC()\n\t\t\tt.Equal(test.want, got)\n\t\t})\n\t}\n}\n\nfunc ExampleTransmission_HasBCC() {\n\tt := Transmission{\n\t\tBCC: []string{\"bcc@gophers.com\"},\n\t}\n\tfmt.Println(t.HasBCC())\n\t// Output: true\n}\n\nfunc (t *MailTestSuite) TestConfig_HasBCC() {\n\ttt := map[string]struct {\n\t\tinput Transmission\n\t\twant  bool\n\t}{\n\t\t\"With\": {\n\t\t\tTransmission{BCC: []string{\"hello@test.com\"}},\n\t\t\ttrue,\n\t\t},\n\t\t\"Without\": {\n\t\t\tTransmission{},\n\t\t\tfalse,\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tgot := test.input.HasBCC()\n\t\t\tt.Equal(test.want, got)\n\t\t})\n\t}\n}\n\nfunc ExampleTransmission_HasAttachments() {\n\tt := Transmission{\n\t\tAttachments: []Attachment{\n\t\t\t{\n\t\t\t\tFilename: \"gopher.svg\",\n\t\t\t\tBytes:    []byte(\"svg\"),\n\t\t\t},\n\t\t},\n\t}\n\tfmt.Println(t.HasAttachments())\n\t// Output: true\n}\n\nfunc (t *MailTestSuite) TestTransmission_HasAttachments() {\n\ttt := map[string]struct {\n\t\tinput Transmission\n\t\twant  bool\n\t}{\n\t\t\"Exists\": {\n\t\t\tTransmission{\n\t\t\t\tAttachments: []Attachment{{Filename: PNGName}},\n\t\t\t},\n\t\t\ttrue,\n\t\t},\n\t\t\"Nil\": {\n\t\t\tTransmission{},\n\t\t\tfalse,\n\t\t},\n\t}\n\n\tfor name, test := range tt {\n\t\tt.Run(name, func() {\n\t\t\tgot := test.input.HasAttachments()\n\t\t\tt.Equal(test.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "mocks/client/Requester.go",
    "content": "// Code generated by mockery v0.0.0-dev. DO NOT EDIT.\n\npackage mocks\n\nimport (\n\tcontext \"context\"\n\n\thttputil \"github.com/ainsleyclark/go-mail/internal/httputil\"\n\tmail \"github.com/ainsleyclark/go-mail/mail\"\n\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// Requester is an autogenerated mock type for the Requester type\ntype Requester struct {\n\tmock.Mock\n}\n\n// Do provides a mock function with given fields: ctx, r, payload, responder\nfunc (_m *Requester) Do(ctx context.Context, r *httputil.Request, payload httputil.Payload, responder httputil.Responder) (mail.Response, error) {\n\tret := _m.Called(ctx, r, payload, responder)\n\n\tvar r0 mail.Response\n\tif rf, ok := ret.Get(0).(func(context.Context, *httputil.Request, httputil.Payload, httputil.Responder) mail.Response); ok {\n\t\tr0 = rf(ctx, r, payload, responder)\n\t} else {\n\t\tr0 = ret.Get(0).(mail.Response)\n\t}\n\n\tvar r1 error\n\tif rf, ok := ret.Get(1).(func(context.Context, *httputil.Request, httputil.Payload, httputil.Responder) error); ok {\n\t\tr1 = rf(ctx, r, payload, responder)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n"
  },
  {
    "path": "mocks/clientold/Requester.go",
    "content": "// Code generated by mockery v0.0.0-dev. DO NOT EDIT.\n\npackage mocks\n\nimport (\n\thttp \"net/http\"\n\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// Requester is an autogenerated mock type for the Requester type\ntype Requester struct {\n\tmock.Mock\n}\n\n// Do provides a mock function with given fields: message, url, headers\nfunc (_m *Requester) Do(message interface{}, url string, headers http.Header) ([]byte, *http.Response, error) {\n\tret := _m.Called(message, url, headers)\n\n\tvar r0 []byte\n\tif rf, ok := ret.Get(0).(func(interface{}, string, http.Header) []byte); ok {\n\t\tr0 = rf(message, url, headers)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]byte)\n\t\t}\n\t}\n\n\tvar r1 *http.Response\n\tif rf, ok := ret.Get(1).(func(interface{}, string, http.Header) *http.Response); ok {\n\t\tr1 = rf(message, url, headers)\n\t} else {\n\t\tif ret.Get(1) != nil {\n\t\t\tr1 = ret.Get(1).(*http.Response)\n\t\t}\n\t}\n\n\tvar r2 error\n\tif rf, ok := ret.Get(2).(func(interface{}, string, http.Header) error); ok {\n\t\tr2 = rf(message, url, headers)\n\t} else {\n\t\tr2 = ret.Error(2)\n\t}\n\n\treturn r0, r1, r2\n}\n"
  },
  {
    "path": "mocks/drivers/smtpSendFunc.go",
    "content": "// Code generated by mockery v0.0.0-dev. DO NOT EDIT.\n\npackage mocks\n\nimport (\n\tsmtp \"net/smtp\"\n\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// smtpSendFunc is an autogenerated mock type for the smtpSendFunc type\ntype smtpSendFunc struct {\n\tmock.Mock\n}\n\n// Execute provides a mock function with given fields: addr, a, from, to, msg\nfunc (_m *smtpSendFunc) Execute(addr string, a smtp.Auth, from string, to []string, msg []byte) error {\n\tret := _m.Called(addr, a, from, to, msg)\n\n\tvar r0 error\n\tif rf, ok := ret.Get(0).(func(string, smtp.Auth, string, []string, []byte) error); ok {\n\t\tr0 = rf(addr, a, from, to, msg)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\n\treturn r0\n}\n"
  },
  {
    "path": "mocks/httputil/Payload.go",
    "content": "// Code generated by mockery v0.0.0-dev. DO NOT EDIT.\n\npackage mocks\n\nimport (\n\tbytes \"bytes\"\n\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// Payload is an autogenerated mock type for the Payload type\ntype Payload struct {\n\tmock.Mock\n}\n\n// Buffer provides a mock function with given fields:\nfunc (_m *Payload) Buffer() (*bytes.Buffer, error) {\n\tret := _m.Called()\n\n\tvar r0 *bytes.Buffer\n\tif rf, ok := ret.Get(0).(func() *bytes.Buffer); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*bytes.Buffer)\n\t\t}\n\t}\n\n\tvar r1 error\n\tif rf, ok := ret.Get(1).(func() error); ok {\n\t\tr1 = rf()\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n\n// ContentType provides a mock function with given fields:\nfunc (_m *Payload) ContentType() string {\n\tret := _m.Called()\n\n\tvar r0 string\n\tif rf, ok := ret.Get(0).(func() string); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tr0 = ret.Get(0).(string)\n\t}\n\n\treturn r0\n}\n\n// Values provides a mock function with given fields:\nfunc (_m *Payload) Values() map[string]string {\n\tret := _m.Called()\n\n\tvar r0 map[string]string\n\tif rf, ok := ret.Get(0).(func() map[string]string); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(map[string]string)\n\t\t}\n\t}\n\n\treturn r0\n}\n"
  },
  {
    "path": "mocks/httputil/Responder.go",
    "content": "// Code generated by mockery v0.0.0-dev. DO NOT EDIT.\n\npackage mocks\n\nimport (\n\thttp \"net/http\"\n\n\thttputil \"github.com/ainsleyclark/go-mail/internal/httputil\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// Responder is an autogenerated mock type for the Responder type\ntype Responder struct {\n\tmock.Mock\n}\n\n// CheckError provides a mock function with given fields: response, buf\nfunc (_m *Responder) CheckError(response *http.Response, buf []byte) error {\n\tret := _m.Called(response, buf)\n\n\tvar r0 error\n\tif rf, ok := ret.Get(0).(func(*http.Response, []byte) error); ok {\n\t\tr0 = rf(response, buf)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\n\treturn r0\n}\n\n// Meta provides a mock function with given fields:\nfunc (_m *Responder) Meta() httputil.Meta {\n\tret := _m.Called()\n\n\tvar r0 httputil.Meta\n\tif rf, ok := ret.Get(0).(func() httputil.Meta); ok {\n\t\tr0 = rf()\n\t} else {\n\t\tr0 = ret.Get(0).(httputil.Meta)\n\t}\n\n\treturn r0\n}\n\n// Unmarshal provides a mock function with given fields: buf\nfunc (_m *Responder) Unmarshal(buf []byte) error {\n\tret := _m.Called(buf)\n\n\tvar r0 error\n\tif rf, ok := ret.Get(0).(func([]byte) error); ok {\n\t\tr0 = rf(buf)\n\t} else {\n\t\tr0 = ret.Error(0)\n\t}\n\n\treturn r0\n}\n"
  },
  {
    "path": "mocks/mail/Mailer.go",
    "content": "// Code generated by mockery v0.0.0-dev. DO NOT EDIT.\n\npackage mocks\n\nimport (\n\tmail \"github.com/ainsleyclark/go-mail/mail\"\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// Mailer is an autogenerated mock type for the Mailer type\ntype Mailer struct {\n\tmock.Mock\n}\n\n// Send provides a mock function with given fields: t\nfunc (_m *Mailer) Send(t *mail.Transmission) (mail.Response, error) {\n\tret := _m.Called(t)\n\n\tvar r0 mail.Response\n\tif rf, ok := ret.Get(0).(func(*mail.Transmission) mail.Response); ok {\n\t\tr0 = rf(t)\n\t} else {\n\t\tr0 = ret.Get(0).(mail.Response)\n\t}\n\n\tvar r1 error\n\tif rf, ok := ret.Get(1).(func(*mail.Transmission) error); ok {\n\t\tr1 = rf(t)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n"
  },
  {
    "path": "tests/mail_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"fmt\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"github.com/joho/godotenv\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\nconst (\n\t// DataPath defines where the test data resides.\n\tDataPath = \"testdata\"\n\t// PNGName defines the PNG name for testing.\n\tPNGName = \"gopher.png\"\n)\n\n// Load the Env variables for testing.\nfunc LoadEnv(t *testing.T) {\n\tt.Helper()\n\n\twd, err := os.Getwd()\n\tassert.NoError(t, err)\n\n\tpath := filepath.Join(filepath.Dir(wd), \"/.env\")\n\terr = godotenv.Load(path)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tfmt.Printf(\"Error loading .env file with path: %s, using system defaults.\\n\", path)\n\t}\n}\n\n// Returns a dummy transition for testing with an\n// attachment.\nfunc GetTransmission(t *testing.T) *mail.Transmission {\n\tt.Helper()\n\n\twd, err := os.Getwd()\n\tassert.NoError(t, err)\n\n\tpath := filepath.Join(filepath.Dir(wd), DataPath, PNGName)\n\tfile, err := os.ReadFile(path)\n\tif err != nil {\n\n\t\tt.Fatal(\"Error getting attachment with the path: \"+path, err)\n\t}\n\n\treturn &mail.Transmission{\n\t\tRecipients: strings.Split(os.Getenv(\"EMAIL_TO\"), \",\"),\n\t\tCC:         strings.Split(os.Getenv(\"EMAIL_CC\"), \",\"),\n\t\tBCC:        strings.Split(os.Getenv(\"EMAIL_BCC\"), \",\"),\n\t\tSubject:    \"Test - Go Mail\",\n\t\tHTML:       \"<h1>Hello from Go Mail!</h1>\",\n\t\tPlainText:  \"Hello from Go Mail!\",\n\t\tHeaders: map[string]string{\n\t\t\t\"X-Go-Mail\": \"Test\",\n\t\t},\n\t\tAttachments: []mail.Attachment{\n\t\t\t{\n\t\t\t\tFilename: \"gopher.png\",\n\t\t\t\tBytes:    file,\n\t\t\t},\n\t\t},\n\t}\n}\n\n// UtilTestSend is a helper function for performing live mailing\n// tests for the drivers.\nfunc UtilTestSend(t *testing.T, fn func(cfg mail.Config) (mail.Mailer, error), cfg mail.Config, driver string) {\n\tt.Helper()\n\n\ttx := GetTransmission(t)\n\n\tmailer, err := fn(cfg)\n\tif err != nil {\n\t\tt.Fatal(\"Error creating client: \" + err.Error())\n\t}\n\n\tresult, err := mailer.Send(tx)\n\tif err != nil {\n\t\tt.Fatalf(\"Error sending %s email: %s\", strings.Title(driver), err.Error()) //nolint\n\t}\n\n\t// Print for sanity\n\tfmt.Println(string(result.Body))\n\n\tassert.InDelta(t, result.StatusCode, http.StatusOK, 299)\n\tassert.NotEmpty(t, result.Message)\n}\n"
  },
  {
    "path": "tests/mailgun_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc Test_MailGun(t *testing.T) {\n\tLoadEnv(t)\n\tcfg := mail.Config{\n\t\tURL:         os.Getenv(\"MAILGUN_URL\"),\n\t\tAPIKey:      os.Getenv(\"MAILGUN_API_KEY\"),\n\t\tFromAddress: os.Getenv(\"MAILGUN_FROM_ADDRESS\"),\n\t\tFromName:    os.Getenv(\"MAILGUN_FROM_NAME\"),\n\t\tDomain:      os.Getenv(\"MAILGUN_DOMAIN\"),\n\t}\n\tUtilTestSend(t, drivers.NewMailgun, cfg, \"MailGun\")\n}\n"
  },
  {
    "path": "tests/postal_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc Test_Postal(t *testing.T) {\n\tLoadEnv(t)\n\tcfg := mail.Config{\n\t\tURL:         os.Getenv(\"POSTAL_URL\"),\n\t\tAPIKey:      os.Getenv(\"POSTAL_API_KEY\"),\n\t\tFromAddress: os.Getenv(\"POSTAL_FROM_ADDRESS\"),\n\t\tFromName:    os.Getenv(\"POSTAL_FROM_NAME\"),\n\t}\n\tUtilTestSend(t, drivers.NewPostal, cfg, \"Postal\")\n}\n"
  },
  {
    "path": "tests/postmark_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc Test_Postmark(t *testing.T) {\n\tLoadEnv(t)\n\tcfg := mail.Config{\n\t\tAPIKey:      os.Getenv(\"POSTMARK_API_KEY\"),\n\t\tFromAddress: os.Getenv(\"POSTMARK_FROM_ADDRESS\"),\n\t\tFromName:    os.Getenv(\"POSTMARK_FROM_NAME\"),\n\t}\n\tUtilTestSend(t, drivers.NewPostmark, cfg, \"Postmark\")\n}\n"
  },
  {
    "path": "tests/sendgrid_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc Test_SendGrid(t *testing.T) {\n\tLoadEnv(t)\n\tcfg := mail.Config{\n\t\tAPIKey:      os.Getenv(\"SENDGRID_API_KEY\"),\n\t\tFromAddress: os.Getenv(\"SENDGRID_FROM_ADDRESS\"),\n\t\tFromName:    os.Getenv(\"SENDGRID_FROM_NAME\"),\n\t}\n\tUtilTestSend(t, drivers.NewSendGrid, cfg, \"SendGrid\")\n}\n"
  },
  {
    "path": "tests/smtp_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc Test_SMTP(t *testing.T) {\n\tLoadEnv(t)\n\tport, err := strconv.Atoi(os.Getenv(\"SMTP_PORT\"))\n\tif err != nil {\n\t\tt.Fatal(\"Error parsing SMTP port\")\n\t}\n\tcfg := mail.Config{\n\t\tURL:         os.Getenv(\"SMTP_URL\"),\n\t\tFromAddress: os.Getenv(\"SMTP_FROM_ADDRESS\"),\n\t\tFromName:    os.Getenv(\"SMTP_FROM_NAME\"),\n\t\tPassword:    os.Getenv(\"SMTP_PASSWORD\"),\n\t\tPort:        port,\n\t}\n\tUtilTestSend(t, drivers.NewSMTP, cfg, \"SMTP\")\n}\n"
  },
  {
    "path": "tests/sparkpost_test.go",
    "content": "// Copyright 2022 Ainsley Clark. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage mail\n\nimport (\n\t\"github.com/ainsleyclark/go-mail/drivers\"\n\t\"github.com/ainsleyclark/go-mail/mail\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc Test_SparkPost(t *testing.T) {\n\tLoadEnv(t)\n\tcfg := mail.Config{\n\t\tURL:         os.Getenv(\"SPARKPOST_URL\"),\n\t\tAPIKey:      os.Getenv(\"SPARKPOST_API_KEY\"),\n\t\tFromAddress: os.Getenv(\"SPARKPOST_FROM_ADDRESS\"),\n\t\tFromName:    os.Getenv(\"SPARKPOST_FROM_NAME\"),\n\t}\n\tUtilTestSend(t, drivers.NewSparkPost, cfg, \"SparkPost\")\n}\n"
  }
]