├── .env_sample ├── .eslintignore ├── .eslintrc.yaml ├── .github └── workflows │ ├── pr-lint.yml │ └── test-and-deploy.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── FIRST_TIMERS.md ├── LICENSE ├── Makefile ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── TROUBLESHOOTING.md ├── UPGRADE.md ├── USAGE.md ├── docs ├── examples │ └── webhooks-docker │ │ ├── .dockerignore │ │ ├── .gitignore │ │ ├── CONTRIBUTING.md │ │ ├── Dockerfile │ │ ├── LICENSE │ │ ├── README.md │ │ ├── app.js │ │ ├── docker-compose.debug.yml │ │ ├── docker-compose.yml │ │ ├── example-email-parse-payload.txt │ │ ├── example-event-notification.txt │ │ ├── k8s │ │ └── inbound-parse.yml │ │ ├── package.json │ │ └── routes │ │ ├── events.js │ │ └── inbound-parse.js ├── migration-guides │ └── migrating-from-version-6-to-7.md └── use-cases │ ├── README.md │ ├── advanced.md │ ├── attachments.md │ ├── categories.md │ ├── cc-bcc-reply-to.md │ ├── custom-headers.md │ ├── customization.md │ ├── data-residency-set-hostname.md │ ├── domain-authentication.md │ ├── email-activity.md │ ├── email-stats.md │ ├── event-webhook.md │ ├── flexible-address-fields.md │ ├── hide-warnings.md │ ├── kitchen-sink.md │ ├── manual-content.md │ ├── multiple-emails-multiple-recipients.md │ ├── multiple-emails-personalizations-with-substitutions.md │ ├── multiple-emails-personalizations.md │ ├── multiple-reply-to-email.md │ ├── scheduled-send.md │ ├── single-email-multiple-recipients.md │ ├── single-email-single-recipient.md │ ├── sms.md │ ├── success-failure-errors.md │ ├── timeout.md │ ├── transactional-legacy-templates.md │ ├── transactional-templates.md │ ├── twilio-email.md │ └── twilio-setup.md ├── lerna.json ├── license.spec.js ├── package.json ├── packages ├── client │ ├── README.md │ ├── USAGE.md │ ├── USE_CASES.md │ ├── index.d.ts │ ├── index.js │ ├── package.json │ └── src │ │ ├── classes │ │ └── client.js │ │ ├── client.d.ts │ │ ├── client.js │ │ ├── client.spec.js │ │ ├── request.d.ts │ │ └── response.d.ts ├── contact-importer │ ├── README.md │ ├── index.d.ts │ ├── package.json │ └── src │ │ ├── importer.d.ts │ │ ├── importer.js │ │ └── importer.spec.js ├── eventwebhook │ ├── index.d.ts │ ├── index.js │ ├── package.json │ └── src │ │ ├── eventwebhook.d.ts │ │ ├── eventwebhook.js │ │ └── eventwebhook.spec.js ├── helpers │ ├── README.md │ ├── attachment.txt │ ├── classes │ │ ├── attachment.d.ts │ │ ├── attachment.js │ │ ├── attachment.spec.js │ │ ├── email-address.d.ts │ │ ├── email-address.js │ │ ├── email-address.spec.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── mail.d.ts │ │ ├── mail.js │ │ ├── mail.spec.js │ │ ├── personalization.d.ts │ │ ├── personalization.js │ │ ├── personalization_specs │ │ │ ├── 527-camel-case-headers.spec.js │ │ │ ├── PERSONALIZATION-SPECS-README.md │ │ │ ├── add-bcc.spec.js │ │ │ ├── add-cc.spec.js │ │ │ ├── add-custom-args.spec.js │ │ │ ├── add-headers.spec.js │ │ │ ├── add-substitutions.spec.js │ │ │ ├── from-data.spec.js │ │ │ ├── reverse-merge-dynamic_teplate_data.spec.js │ │ │ ├── reverse-merge-substitutions.spec.js │ │ │ ├── set-add-to.spec.js │ │ │ ├── set-bcc.spec.js │ │ │ ├── set-cc.spec.js │ │ │ ├── set-custom-args.spec.js │ │ │ ├── set-from.spec.js │ │ │ ├── set-headers.spec.js │ │ │ ├── set-send-at.spec.js │ │ │ ├── set-subject.spec.js │ │ │ ├── set-substitutions.spec.js │ │ │ ├── set-to.spec.js │ │ │ ├── set_dynamic_template_data.spec.js │ │ │ ├── substitutions-wrappers.spec.js │ │ │ └── to-json.spec.js │ │ ├── request.d.ts │ │ ├── response-error.d.ts │ │ ├── response-error.js │ │ ├── response.d.ts │ │ ├── response.js │ │ ├── statistics.d.ts │ │ ├── statistics.js │ │ └── statistics.spec.js │ ├── constants │ │ └── index.js │ ├── helpers │ │ ├── array-to-json.d.ts │ │ ├── array-to-json.js │ │ ├── array-to-json.spec.js │ │ ├── convert-keys.d.ts │ │ ├── convert-keys.js │ │ ├── convert-keys.spec.js │ │ ├── deep-clone.d.ts │ │ ├── deep-clone.js │ │ ├── deep-clone.spec.js │ │ ├── html-to-plain-text.js │ │ ├── html-to-plain-text.spec.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── merge-data-deep.d.ts │ │ ├── merge-data-deep.js │ │ ├── merge-data-deep.spec.js │ │ ├── merge-data.d.ts │ │ ├── merge-data.js │ │ ├── merge-data.spec.js │ │ ├── split-name-email.d.ts │ │ ├── split-name-email.js │ │ ├── split-name-email.spec.js │ │ ├── str-to-camel-case.js │ │ ├── str-to-camel-case.spec.js │ │ ├── str-to-snake-case.js │ │ ├── str-to-snake-case.spec.js │ │ ├── to-camel-case.d.ts │ │ ├── to-camel-case.js │ │ ├── to-snake-case.d.ts │ │ ├── to-snake-case.js │ │ ├── validate-settings.js │ │ ├── wrap-substitutions.d.ts │ │ ├── wrap-substitutions.js │ │ └── wrap-substitutions.spec.js │ ├── index.d.ts │ ├── index.js │ └── package.json ├── inbound-mail-parser │ ├── README.md │ ├── index.d.ts │ ├── package.json │ └── src │ │ ├── parser.d.ts │ │ ├── parser.js │ │ └── parser.spec.js ├── mail │ ├── README.md │ ├── USE_CASES.md │ ├── index.d.ts │ ├── index.js │ ├── package.json │ └── src │ │ ├── classes │ │ └── mail-service.js │ │ ├── mail.d.ts │ │ ├── mail.js │ │ └── mail.spec.js └── subscription-widget │ ├── .gitignore │ ├── LICENSE.txt │ ├── Procfile │ ├── README.md │ ├── app.json │ ├── index.js │ ├── package.json │ ├── server │ ├── controllers │ │ └── contact_list_controller.js │ ├── router.js │ └── static │ │ ├── check-inbox.html │ │ ├── error.html │ │ ├── index.html │ │ ├── sample-form.png │ │ ├── success.html │ │ └── template.png │ └── settings.js ├── static └── img │ ├── github-fork.png │ └── github-sign-up.png ├── test ├── files.spec.js ├── mocha.js ├── mocha.opts └── typescript │ ├── client.ts │ ├── eventwebhook.ts │ ├── helpers.ts │ └── mail.ts ├── tsconfig.json └── twilio_sendgrid_logo.png /.env_sample: -------------------------------------------------------------------------------- 1 | export SENDGRID_API_KEY = '' 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | examples 3 | test 4 | coverage 5 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | env: 2 | es6: true 3 | node: true 4 | mocha: true 5 | jasmine: true 6 | rules: 7 | array-bracket-spacing: 8 | - 2 9 | - never 10 | - {} 11 | brace-style: 12 | - 2 13 | - 1tbs 14 | - allowSingleLine: true 15 | camelcase: 0 16 | comma-dangle: 17 | - 2 18 | - always-multiline 19 | comma-spacing: 20 | - 2 21 | - after: true 22 | before: false 23 | comma-style: 24 | - 2 25 | - last 26 | curly: 27 | - 2 28 | - all 29 | dot-notation: 0 30 | eol-last: 2 31 | indent: 32 | - 2 33 | - 2 34 | - SwitchCase: 1 35 | key-spacing: 36 | - 2 37 | - afterColon: true 38 | beforeColon: false 39 | keyword-spacing: 40 | - 2 41 | - {} 42 | linebreak-style: 43 | - 2 44 | - unix 45 | no-empty: 46 | - 2 47 | - allowEmptyCatch: true 48 | no-mixed-spaces-and-tabs: 2 49 | no-multi-str: 2 50 | no-multiple-empty-lines: 2 51 | no-spaced-func: 2 52 | no-trailing-spaces: 2 53 | no-with: 2 54 | one-var: 55 | - 2 56 | - never 57 | quote-props: 58 | - 2 59 | - as-needed 60 | quotes: 61 | - 2 62 | - single 63 | semi: 64 | - 2 65 | - always 66 | semi-spacing: 67 | - 2 68 | - after: true 69 | before: false 70 | space-before-blocks: 71 | - 2 72 | - always 73 | space-before-function-paren: 74 | - 2 75 | - anonymous: ignore 76 | named: never 77 | space-in-parens: 78 | - 2 79 | - never 80 | space-infix-ops: 2 81 | space-unary-ops: 82 | - 2 83 | - nonwords: false 84 | words: false 85 | wrap-iife: 2 86 | yoda: 87 | - 2 88 | - never 89 | -------------------------------------------------------------------------------- /.github/workflows/pr-lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint PR 2 | on: 3 | pull_request_target: 4 | types: [ opened, edited, synchronize, reopened ] 5 | 6 | jobs: 7 | validate: 8 | name: Validate title 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: amannn/action-semantic-pull-request@v4 12 | with: 13 | types: chore docs fix feat test misc 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/test-and-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Test and Deploy 2 | on: 3 | push: 4 | branches: [ '*' ] 5 | tags: [ '*' ] 6 | pull_request: 7 | branches: [ main ] 8 | schedule: 9 | # Run automatically at 8AM PST Monday-Friday 10 | - cron: '0 15 * * 1-5' 11 | workflow_dispatch: 12 | 13 | jobs: 14 | test: 15 | name: Test 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 20 18 | strategy: 19 | matrix: 20 | node: [12, 14, 16, lts] 21 | env: 22 | version: ${{ matrix.node }} 23 | DOCKER_LOGIN: ${{ secrets.DOCKER_USERNAME && secrets.DOCKER_AUTH_TOKEN }} 24 | steps: 25 | - name: Checkout sendgrid-nodejs 26 | uses: actions/checkout@v2 27 | 28 | - name: Login to Docker Hub 29 | if: env.DOCKER_LOGIN 30 | uses: docker/login-action@v1 31 | with: 32 | username: ${{ secrets.DOCKER_USERNAME }} 33 | password: ${{ secrets.DOCKER_AUTH_TOKEN }} 34 | 35 | - name: Install Docker Compose 36 | run: | 37 | sudo apt-get update 38 | sudo apt-get install -y docker-compose 39 | 40 | - name: Run Tests 41 | run: make test-docker 42 | 43 | deploy: 44 | name: Deploy 45 | if: success() && github.ref_type == 'tag' 46 | needs: [ test ] 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Checkout sendgrid-nodejs 50 | uses: actions/checkout@v2 51 | with: 52 | fetch-depth: 0 53 | 54 | - name: Set up Node 55 | uses: actions/setup-node@v2 56 | with: 57 | node-version: 16 58 | 59 | - name: Create GitHub Release 60 | uses: sendgrid/dx-automator/actions/release@main 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | 64 | - run: npm install 65 | - name: Publish 66 | run: | 67 | npm config set //registry.npmjs.org/:_authToken ${NODE_AUTH_TOKEN} 68 | lerna publish from-package --yes --no-verify-access 69 | env: 70 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 71 | 72 | - name: Submit metric to Datadog 73 | uses: sendgrid/dx-automator/actions/datadog-release-metric@main 74 | env: 75 | DD_API_KEY: ${{ secrets.DATADOG_API_KEY }} 76 | 77 | notify-on-failure: 78 | name: Slack notify on failure 79 | if: failure() && github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag') 80 | needs: [ test, deploy ] 81 | runs-on: ubuntu-latest 82 | steps: 83 | - uses: rtCamp/action-slack-notify@v2 84 | env: 85 | SLACK_COLOR: failure 86 | SLACK_ICON_EMOJI: ':github:' 87 | SLACK_MESSAGE: ${{ format('Test *{0}*, Deploy *{1}*, {2}/{3}/actions/runs/{4}', needs.test.result, needs.deploy.result, github.server_url, github.repository, github.run_id) }} 88 | SLACK_TITLE: Action Failure - ${{ github.repository }} 89 | SLACK_USERNAME: GitHub Actions 90 | SLACK_MSG_AUTHOR: twilio-dx 91 | SLACK_FOOTER: Posted automatically using GitHub Actions 92 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 93 | MSG_MINIMAL: true 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | coverage/ 3 | node_modules/ 4 | test/config.js 5 | .env* 6 | *.log 7 | .vscode/ 8 | prism* 9 | package-lock.json 10 | yarn.lock 11 | *.bak 12 | .idea/ 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at open-source@twilio.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG version=lts 2 | FROM node:$version 3 | 4 | ENV NODE_TLS_REJECT_UNAUTHORIZED 0 5 | 6 | WORKDIR /app 7 | COPY . . 8 | 9 | RUN make install 10 | -------------------------------------------------------------------------------- /FIRST_TIMERS.md: -------------------------------------------------------------------------------- 1 | # How To Contribute to Twilio SendGrid Repositories via GitHub 2 | Contributing to the Twilio SendGrid repositories is easy! All you need to do is find an open issue (see the bottom of this page for a list of repositories containing open issues), fix it and submit a pull request. Once you have submitted your pull request, the team can easily review it before it is merged into the repository. 3 | 4 | To make a pull request, follow these steps: 5 | 6 | 1. Log into GitHub. If you do not already have a GitHub account, you will have to create one in order to submit a change. Click the Sign up link in the upper right-hand corner to create an account. Enter your username, password, and email address. If you are an employee of Twilio SendGrid, please use your full name with your GitHub account and enter Twilio SendGrid as your company so we can easily identify you. 7 | 8 | 9 | 10 | 2. __[Fork](https://help.github.com/fork-a-repo/)__ the [sendgrid-nodejs](https://github.com/sendgrid/sendgrid-nodejs) repository: 11 | 12 | 13 | 14 | 3. __Clone__ your fork via the following commands: 15 | 16 | ```bash 17 | # Clone your fork of the repo into the current directory 18 | git clone https://github.com/your_username/sendgrid-nodejs 19 | # Navigate to the newly cloned directory 20 | cd sendgrid-nodejs 21 | # Assign the original repo to a remote called "upstream" 22 | git remote add upstream https://github.com/sendgrid/sendgrid-nodejs 23 | ``` 24 | 25 | > Don't forget to replace *your_username* in the URL by your real GitHub username. 26 | 27 | 4. __Create a new topic branch__ (off the main project development branch) to contain your feature, change, or fix: 28 | 29 | ```bash 30 | git checkout -b 31 | ``` 32 | 33 | 5. __Commit your changes__ in logical chunks. 34 | 35 | Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is unlikely be merged into the main project. Use Git's [interactive rebase](https://help.github.com/articles/interactive-rebase) feature to tidy up your commits before making them public. Probably you will also have to create tests (if needed) or create or update the example code that demonstrates the functionality of this change to the code. 36 | 37 | 6. __Locally merge (or rebase)__ the upstream development branch into your topic branch: 38 | 39 | ```bash 40 | git pull [--rebase] upstream main 41 | ``` 42 | 43 | 7. __Push__ your topic branch up to your fork: 44 | 45 | ```bash 46 | git push origin 47 | ``` 48 | 49 | 8. __[Open a Pull Request](https://help.github.com/articles/creating-a-pull-request/#changing-the-branch-range-and-destination-repository/)__ with a clear title and description against the `main` branch. All tests must be passing before we will review the PR. 50 | 51 | ## Important notice 52 | 53 | Before creating a pull request, make sure that you respect the repository's constraints regarding contributions. You can find them in the [CONTRIBUTING.md](CONTRIBUTING.md) file. 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2025, Twilio SendGrid, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean install test test-integ test-docker 2 | 3 | clean: 4 | @rm -rf node_modules 5 | 6 | install: clean 7 | npm install --quiet 8 | ./node_modules/.bin/lerna bootstrap 9 | 10 | test: 11 | yarn test:files 12 | yarn test:license 13 | yarn test:typescript 14 | yarn test:eventwebhook 15 | 16 | test-integ: test 17 | yarn test:mail 18 | yarn test:client 19 | yarn test:helpers 20 | yarn lint 21 | 22 | version ?= lts 23 | test-docker: 24 | curl -s https://raw.githubusercontent.com/sendgrid/sendgrid-oai/HEAD/prism/prism.sh -o prism.sh 25 | version=$(version) bash ./prism.sh 26 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | # Fixes # 18 | 19 | A short description of what this PR does. 20 | 21 | ### Checklist 22 | - [x] I acknowledge that all my contributions will be made under the project's license 23 | - [ ] I have made a material change to the repo (functionality, testing, spelling, grammar) 24 | - [ ] I have read the [Contribution Guidelines](https://github.com/sendgrid/sendgrid-nodejs/blob/main/CONTRIBUTING.md) and my PR follows them 25 | - [ ] I have titled the PR appropriately 26 | - [ ] I have updated my branch with the main branch 27 | - [ ] I have added tests that prove my fix is effective or that my feature works 28 | - [ ] I have added the necessary documentation about the functionality in the appropriate .md file 29 | - [ ] I have added inline documentation to the code I modified 30 | 31 | If you have questions, please file a [support ticket](https://support.sendgrid.com). -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | 3 | _All `MAJOR` version bumps will have upgrade notes posted here._ 4 | 5 | ## [2023-12-05] 7.x.x to 8.x.x 6 | 7 | --- 8 | 9 | * Supported Node.js versions updated 10 | * Upgrade to Node.js = [12, 14, 16, lts] 11 | * Dropped support for Node.js < 12 ([#1391](https://github.com/sendgrid/sendgrid-nodejs/pull/1391)) 12 | * Axios security vulnerability upgrade ([#1391](https://github.com/sendgrid/sendgrid-nodejs/pull/1391)) 13 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This library is broken up into several packages as a monorepo so that you only need to install the packages necessary for your use case. 4 | This USAGE.md contains information about all packages. For examples on how to get started quickly, head over to the READMEs of each package (linked and described below), which includes detailed examples. 5 | 6 | * [@sendgrid/mail](packages/mail) - if you just want to send email 7 | * [@sendgrid/client](packages/client) - to use all other [SendGrid v3 Web API endpoints](https://sendgrid.com/docs/api-reference/) 8 | * [@sendgrid/inbound-mail-parser](packages/inbound-mail-parser) - help with parsing the SendGrid Inbound Parse API 9 | * [@sendgrid/contact-importer](packages/contact-importer) - help with importing contacts into the ContactDB 10 | * [@sendgrid/helpers](packages/helpers) - a collection of classes and helpers used internally by the above packages 11 | 12 | 13 | # Documentation 14 | 15 | If you would like to auto-generate documentation of the packages, you can do so locally by running: 16 | ``` 17 | ./node_modules/.bin/esdoc 18 | ``` 19 | Using the .esdoc.json file, esdoc will create documentation in the docs directory. 20 | 21 | ## Checking docs coverage 22 | 23 | You will find a coverage.json file in the docs directory. This will contain information about the documentation coverage for each of the different files in this repo. 24 | -------------------------------------------------------------------------------- /docs/examples/webhooks-docker/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile* 4 | docker-compose* 5 | .dockerignore 6 | .git 7 | .gitignore 8 | .env 9 | */bin 10 | */obj 11 | README.md 12 | LICENSE 13 | .vscode -------------------------------------------------------------------------------- /docs/examples/webhooks-docker/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /docs/examples/webhooks-docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.9-alpine 2 | ENV NODE_ENV production 3 | WORKDIR /usr/src/app 4 | COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"] 5 | RUN npm install --production --silent && mv node_modules ../ 6 | COPY . . 7 | EXPOSE 3000 8 | CMD npm start -------------------------------------------------------------------------------- /docs/examples/webhooks-docker/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2019 Twilio SendGrid, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 5 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 6 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of 9 | the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 12 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 13 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 14 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 15 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/examples/webhooks-docker/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | const port = 3000 4 | 5 | const eventWebhook = require('./routes/events') 6 | const parseWebhook = require('./routes/inbound-parse') 7 | 8 | app.get('/', (req, res) => res.status(200).json({status: 'ok'})) 9 | 10 | app.use('/event_webhook', eventWebhook) 11 | app.use('/parse_webhook', parseWebhook) 12 | 13 | 14 | app.listen(port, () => console.log(`Twilio SendGrid webhook listener started on port ${port}!`)) -------------------------------------------------------------------------------- /docs/examples/webhooks-docker/docker-compose.debug.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | inbound-parse-docker: 5 | image: inbound-parse-docker 6 | build: . 7 | environment: 8 | NODE_ENV: development 9 | ports: 10 | - 3000:3000 11 | - 9229:9229 12 | ## set your startup file here 13 | command: node --inspect app.js -------------------------------------------------------------------------------- /docs/examples/webhooks-docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | inbound-parse-docker: 5 | image: inbound-parse-docker 6 | build: . 7 | environment: 8 | NODE_ENV: production 9 | ports: 10 | - 3000:3000 11 | # volumes: 12 | # - /tmp:/tmp -------------------------------------------------------------------------------- /docs/examples/webhooks-docker/k8s/inbound-parse.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: inbound-parse-deployment 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: inbound-parse 9 | replicas: 2 # tells deployment to run 2 pods matching the template 10 | template: 11 | metadata: 12 | labels: 13 | app: inbound-parse 14 | spec: 15 | containers: 16 | - name: inbound-parse 17 | image: inbound-parse-docker:latest 18 | imagePullPolicy: IfNotPresent # enables pull from local system 19 | ports: 20 | - containerPort: 3000 21 | --- 22 | apiVersion: v1 23 | kind: Service 24 | metadata: 25 | name: inbound-parse-svc 26 | spec: 27 | ports: 28 | - protocol: TCP 29 | port: 3000 30 | targetPort: 3000 31 | selector: 32 | app: inbound-parse 33 | type: LoadBalancer 34 | -------------------------------------------------------------------------------- /docs/examples/webhooks-docker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inbound-parse-docker", 3 | "version": "1.0.0", 4 | "description": "A containter to capture webhook data from Twilio SendGrid inbound parse", 5 | "main": "app.js", 6 | "dependencies": { 7 | "body-parser": "^1.18.3", 8 | "express": "^4.16.3", 9 | "express-formidable": "^1.0.0" 10 | }, 11 | "devDependencies": {}, 12 | "scripts": { 13 | "start": "node app.js", 14 | "test": "echo \"Error: no test specified\" && exit 1" 15 | }, 16 | "author": "Ashley Roach ", 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /docs/examples/webhooks-docker/routes/events.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const bodyParser = require('body-parser') 4 | 5 | router.use(bodyParser.json()) 6 | 7 | router.post('/', (req, res) => { 8 | 9 | // Modify and extend the business logic applied to the req.body 10 | // array. You could send this to a file, database, or other system 11 | // for storage and analysis. 12 | console.log(req.body) 13 | 14 | let events = req.body 15 | 16 | events.forEach(element => { 17 | console.log() 18 | console.log('---------- EVENT START ----------') 19 | console.log('EMAIL:', element.email) 20 | console.log('TIMESTAMP:', element.timestamp) 21 | // TODO: invalid JSON key name 22 | // console.log('EMAIL:', element.smtp-id) 23 | console.log('EVENT:', element.event) 24 | console.log('CATEGORY:', element.category) 25 | console.log('SG_EVENT_ID:', element.sg_event_id) 26 | console.log('SG_MESSAGE_ID:', element.sg_message_id) 27 | console.log('USERAGENT:', element.useragent) 28 | console.log('IP:', element.ip) 29 | console.log('URL:', element.url) 30 | console.log('ASM_GROUP_ID:', element.asm_group_id) 31 | console.log('---------- EVENT END ----------') 32 | console.log() 33 | }); 34 | 35 | res.sendStatus(200) 36 | }) 37 | 38 | module.exports = router -------------------------------------------------------------------------------- /docs/examples/webhooks-docker/routes/inbound-parse.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const formidable = require('express-formidable') 4 | 5 | router.use(formidable()) 6 | 7 | router.post('/', (req, res) => { 8 | console.log('---------- START RECEIVED WEBHOOK DATA ----------') 9 | // Email data comes in as a form. Using express-formidable to 10 | // handle the form data. Form fields are available in req.fields 11 | // Below, extracting the from and text. 12 | // You can take this data and do something more interesting with it 13 | // such as sending it to a database. 14 | // Attachments are stored in /tmp based on the default configuration 15 | // of the formidable middleware. 16 | // console.log(req.fields) 17 | console.log() 18 | console.log('HEADERS: ' + req.fields.headers) 19 | console.log() 20 | console.log('DKIM: ' + req.fields.dkim) 21 | console.log() 22 | console.log('CONTENT-IDS: ' + req.fields["content-ids"]) 23 | console.log() 24 | console.log('TO: ' + req.fields.to) 25 | console.log() 26 | console.log('HTML: ' + req.fields.html) 27 | console.log() 28 | console.log('FROM: ' + req.fields.from) 29 | console.log() 30 | console.log('SENDER-IP: ' + req.fields.sender_ip) 31 | console.log() 32 | console.log('SPAM-REPORT: ' + req.fields.spam_report) 33 | console.log() 34 | console.log('ENVELOPE: ' + req.fields.envelope) 35 | console.log() 36 | console.log('ATTACHMENTS: ' + req.fields.attachments) 37 | console.log() 38 | console.log('SPAM-SCORE: ' + req.fields.spam_score) 39 | console.log() 40 | console.log('ATTACHMENT-INFO: ' + req.fields["attachment-info"]) 41 | console.log() 42 | console.log('CHARSETS: ' + req.fields.charsets) 43 | console.log() 44 | console.log('SPF: ' + req.fields.SPF) 45 | console.log() 46 | console.log('MESSAGE TEXT: ' + req.fields.text) 47 | console.log() 48 | console.log('---------- END RECEIVED WEBHOOK DATA ----------') 49 | 50 | res.sendStatus(200) 51 | }) 52 | 53 | module.exports = router -------------------------------------------------------------------------------- /docs/migration-guides/migrating-from-version-6-to-7.md: -------------------------------------------------------------------------------- 1 | This guide relates to changes implemented in [PR 1058](https://github.com/sendgrid/sendgrid-nodejs/pull/1058). 2 | 3 | The [request](https://github.com/request/request#deprecated) HTTP client was deprecated as of Feb 11th 2020. We have updated this helper library with the [axios](https://www.npmjs.com/package/axios) HTTP client. 4 | 5 | In version 6.X.X, our `ClientRequest` and `ClientResponse` types exposed the raw interfaces from the `request` module directly. In version 7.X.X, the [`ClientRequest`](../../packages/helpers/classes/request.d.ts) and [`ClientResponse`](../../packages/helpers/classes/response.d.ts) objects untethers from a specific HTTP client implementation. We have maintained the core request and response interfaces exposed in test cases and usage examples, but mask everything else. 6 | 7 | If you are passing in request options, outside of what’s documented, to the HTTP request client, this may be a breaking change for you. Please check the [`RequestOptions` interface](../../packages/helpers/classes/request.d.ts) to inspect the updated options. If you rely on HTTP response data [previously available](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/HEAD/types/request/index.d.ts#L319), this may also be a breaking change for you. Please see the [`ClientResponse`](../../packages/helpers/classes/response.d.ts) object for details about what data is now exposed. 8 | 9 | ## Action steps: 10 | 11 | 1. Locate any areas in your code that utilizes request and response properties that are no longer exposed in the new interfaces. 12 | 1. Update the response values to use the new [`Response`](../../packages/helpers/classes/response.d.ts) object. 13 | 1. Update the request values to use the new options exposed in the [`RequestOptions`](../../packages/helpers/classes/request.d.ts) object. 14 | -------------------------------------------------------------------------------- /docs/use-cases/README.md: -------------------------------------------------------------------------------- 1 | This documentation provides examples for specific Twilio SendGrid v3 API use cases. Please [open an issue](https://github.com/sendgrid/sendgrid-nodejs/issues) or make a pull request for any email use cases you would like us to document here. Thank you! 2 | 3 | # Email Use Cases 4 | * [Send a Single Email to a Single Recipient](single-email-single-recipient.md) 5 | * [Send a Single Email to Multiple Recipients](single-email-multiple-recipients.md) 6 | * [Send Multiple Emails to Multiple Recipients](multiple-emails-multiple-recipients.md) 7 | * [Send Multiple Emails with Personalizations](multiple-emails-personalizations.md) 8 | * [Send Multiple Emails with Personalizations and Substitutions](multiple-emails-personalizations-with-substitutions.md) 9 | * [CC, BCC and Reply To](cc-bcc-reply-to.md) 10 | * [Flexible Email Address Fields](flexible-address-fields.md) 11 | * [Handling Success/Failure/Errors](success-failure-errors.md) 12 | * [Show Email Activity](email-activity.md) 13 | * [Advanced Usage](advanced.md) 14 | * [Transactional Templates](transactional-templates.md) 15 | * [Legacy Transactional Templates](transactional-legacy-templates.md) 16 | * [Hide Warnings](hide-warnings.md) 17 | * [Attachments](attachments.md) 18 | * [Customization Per Recipient](customization.md) 19 | * [Manually Providing Content](manual-content.md) 20 | * [Scheduled Send](scheduled-send.md) 21 | * [Specifying Custom Headers](custom-headers.md) 22 | * [Specifying Categories](categories.md) 23 | * [Timeout](timeout.md) 24 | * [Kitchen Sink - an example with all settings used](kitchen-sink.md) 25 | 26 | # Twilio Use Cases 27 | * [Twilio Setup](twilio-setup.md) 28 | * [Send an Email With Twilio Email (Pilot)](twilio-email.md) 29 | * [Send an SMS Message](sms.md) 30 | 31 | # Non-Email Use Cases 32 | * [How to Set up a Domain Authentication](domain-authentication.md) 33 | * [How to View Email Statistics](email-stats.md) 34 | -------------------------------------------------------------------------------- /docs/use-cases/advanced.md: -------------------------------------------------------------------------------- 1 | # Advanced Usage 2 | 3 | All other advanced settings are supported and can be passed in through the msg object according to the expected format as per the [API v3 documentation](https://sendgrid.com/docs/api-reference/). Note that you can use either `camelCase` or `snake_case` for property names. 4 | -------------------------------------------------------------------------------- /docs/use-cases/attachments.md: -------------------------------------------------------------------------------- 1 | # Attachments 2 | 3 | Attachments can be sent by providing an array of `attachments` as per the [API specification](https://sendgrid.com/docs/api-reference/): 4 | 5 | ```js 6 | const sgMail = require('@sendgrid/mail'); 7 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 8 | const msg = { 9 | to: 'recipient@example.org', 10 | from: 'sender@example.org', 11 | subject: 'Hello attachment', 12 | html: '

Here’s an attachment for you!

', 13 | attachments: [ 14 | { 15 | content: 'Some base 64 encoded attachment content', 16 | filename: 'some-attachment.txt', 17 | type: 'plain/text', 18 | disposition: 'attachment', 19 | content_id: 'mytext' 20 | }, 21 | ], 22 | }; 23 | ``` 24 | 25 | Reading and converting a local PDF file. 26 | 27 | ```js 28 | import fs from 'fs'; 29 | 30 | fs.readFile(('Document.pdf'), (err, data) => { 31 | if (err) { 32 | // do something with the error 33 | } 34 | if (data) { 35 | const msg = { 36 | to: 'recipient@test.org', 37 | from: 'sender@test.org', 38 | subject: 'Attachment', 39 | html: '

Here’s an attachment for you!

', 40 | attachments: [ 41 | { 42 | content: data.toString('base64'), 43 | filename: 'some-attachment.pdf', 44 | type: 'application/pdf', 45 | disposition: 'attachment', 46 | content_id: 'mytext', 47 | }, 48 | ], 49 | }; 50 | } 51 | }); 52 | ``` 53 | 54 | If you are using a PDF URL: 55 | 56 | ```js 57 | import request from 'request'; 58 | 59 | request(fileURl, { encoding: null }, (err, res, body) => { 60 | if (err) { return err; } 61 | if (body) { 62 | const textBuffered = Buffer.from(body); 63 | 64 | const msg = { 65 | to: 'recipient@test.org', 66 | from: 'sender@test.org', 67 | subject: 'Attachment', 68 | html: '

Here’s an attachment for you!

', 69 | attachments: [ 70 | { 71 | content: textBuffered.toString('base64'), 72 | filename: 'some-attachment.pdf', 73 | type: 'application/pdf', 74 | disposition: 'attachment', 75 | content_id: 'mytext', 76 | }, 77 | ], 78 | }; 79 | // send msg here 80 | } 81 | }); 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/use-cases/categories.md: -------------------------------------------------------------------------------- 1 | # Specifying Categories 2 | 3 | Use the `categories` property to provide an array of categories for your email: 4 | 5 | ```js 6 | const msg = { 7 | to: 'recipient@example.org', 8 | from: 'sender@example.org', 9 | subject: 'Hello email with categories', 10 | html: '

Some email content

', 11 | categories: [ 12 | 'transactional', 'customer', 'weekly', 13 | ], 14 | }; 15 | ``` 16 | 17 | Specifying a single `category` is also supported: 18 | 19 | ```js 20 | const msg = { 21 | to: 'recipient@example.org', 22 | from: 'sender@example.org', 23 | subject: 'Hello email with categories', 24 | html: '

Some email content

', 25 | category: 'transactional', 26 | }; 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/use-cases/cc-bcc-reply-to.md: -------------------------------------------------------------------------------- 1 | # CC, BCC and Reply To 2 | 3 | You can specify the `cc`, `bcc`, and `replyTo` fields for more control over who you send the email to and where people will reply to: 4 | 5 | ```js 6 | const msg = { 7 | to: 'recipient@example.org', 8 | cc: 'someone@example.org', 9 | bcc: ['me@example.org', 'you@example.org'], 10 | from: 'sender@example.org', 11 | replyTo: 'othersender@example.org', 12 | subject: 'Hello world', 13 | text: 'Hello plain world!', 14 | html: '

Hello HTML world!

', 15 | }; 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/use-cases/custom-headers.md: -------------------------------------------------------------------------------- 1 | # Specifying Custom Headers 2 | 3 | Use the `headers` property to specify any custom headers (note that these can also be set globally per the [API specification](https://sendgrid.com/docs/api-reference/): 4 | 5 | ```js 6 | const msg = { 7 | to: 'recipient@example.org', 8 | from: 'sender@example.org', 9 | subject: 'Hello custom header', 10 | html: '

Some email content

', 11 | headers: { 12 | 'X-CustomHeader': 'Custom header value', 13 | }, 14 | }; 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/use-cases/customization.md: -------------------------------------------------------------------------------- 1 | # Customization Per Recipient 2 | 3 | To send multiple individual emails to multiple recipients with additional customization (like a different subject), use the `personalizations` field as per the [API definition](https://sendgrid.com/docs/api-reference/) instead of `to`, leveraging all customization options: 4 | 5 | ```js 6 | const msg = { 7 | personalizations: [ 8 | { 9 | to: 'recipient1@example.org', 10 | subject: 'Hello recipient 1', 11 | dynamicTemplateData: { 12 | name: 'Recipient 1', 13 | id: '123', 14 | }, 15 | headers: { 16 | 'X-Custom-Header': 'Recipient 1', 17 | }, 18 | customArgs: { 19 | myArg: 'Recipient 1', // must be a string 20 | }, 21 | }, 22 | { 23 | to: 'recipient2@example.org', 24 | subject: 'Hello recipient 2', 25 | dynamicTemplateData: { 26 | name: 'Recipient 2', 27 | id: '456', 28 | }, 29 | headers: { 30 | 'X-Custom-Header': 'Recipient 2', 31 | }, 32 | customArgs: { 33 | myArg: 'Recipient 1', // must be a string 34 | }, 35 | sendAt: 1500077141, 36 | } 37 | ], 38 | from: 'sender@example.org', 39 | templateId: 'd-12345678901234567890123456789012', 40 | }; 41 | ``` 42 | 43 | If the `dynamicTemplateData` field is provided globally as well, these substitutions will be merged with any custom `dynamicTemplateData` you provide in the `personalizations`. 44 | -------------------------------------------------------------------------------- /docs/use-cases/data-residency-set-hostname.md: -------------------------------------------------------------------------------- 1 | # Choosing a data-residency to send messages to 2 | 3 | Use the `setDataResidency` setter to specify which host to send to: 4 | 5 | Send to EU (data-residency: `https://api.eu.sendgrid.com/`) 6 | ```js 7 | const client = require('@sendgrid/client'); 8 | const sgMail = require('@sendgrid/mail'); 9 | client.setDataResidency('eu'); 10 | const msg = { 11 | to: 'recipient@example.org', 12 | from: 'sender@example.org', 13 | subject: 'Hello world', 14 | text: 'Hello plain world!', 15 | html: '

Hello HTML world!

', 16 | }; 17 | sgMail.setClient(client); 18 | sgMail.send(msg); 19 | ``` 20 | Send to Global region, this is also the default host, if the setter is not used 21 | (data-residency: `https://api.sendgrid.com/`) 22 | ```js 23 | const client = require('@sendgrid/client'); 24 | const sgMail = require('@sendgrid/mail'); 25 | client.setDataResidency('global'); 26 | const msg = { 27 | to: 'recipient@example.org', 28 | from: 'sender@example.org', 29 | subject: 'Hello world', 30 | text: 'Hello plain world!', 31 | html: '

Hello HTML world!

', 32 | }; 33 | sgMail.setClient(client); 34 | sgMail.send(msg); 35 | ``` 36 | 37 | ## Limitations 38 | 39 | 1. Emails can only be sent to two hosts for now; 'eu' (https://api.eu.sendgrid.com/) and 'global' (https://api.eu.sendgrid.com/) 40 | 2. The default data-residency is https://api.sendgrid.com/ 41 | 3. The valid values for `region` in `client.setDataResidency(region)` are only `eu` and `global`. Case-sensitive. 42 | -------------------------------------------------------------------------------- /docs/use-cases/domain-authentication.md: -------------------------------------------------------------------------------- 1 | # How to Setup a Domain Authentication 2 | 3 | You can find documentation for how to setup a domain authentication via the UI [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) and via API [here](../../packages/client/USAGE.md#sender-authentication). 4 | 5 | Find more information about all of Twilio SendGrid's authentication related documentation [here](https://sendgrid.com/docs/ui/account-and-settings/). 6 | -------------------------------------------------------------------------------- /docs/use-cases/email-activity.md: -------------------------------------------------------------------------------- 1 | # How to View Email Activity 2 | 3 | You can find documentation for how to use the Email Activity Feed via the UI [here](https://sendgrid.com/docs/ui/analytics-and-reporting/email-activity-feed/) and via API [here](../../packages/client/USAGE.md#messages). 4 | 5 | Find more information about getting started with the Email Activity Feed API [here](https://sendgrid.com/docs/API_Reference/Web_API_v3/Tutorials/getting_started_email_activity_api.html). 6 | -------------------------------------------------------------------------------- /docs/use-cases/email-stats.md: -------------------------------------------------------------------------------- 1 | # How to View Email Statistics 2 | 3 | You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](../../packages/client/USAGE.md#stats). 4 | 5 | Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://docs.sendgrid.com/for-developers/tracking-events/event) about events that occur as Twilio SendGrid processes your email. 6 | -------------------------------------------------------------------------------- /docs/use-cases/event-webhook.md: -------------------------------------------------------------------------------- 1 | First, follow the guide to [set up signature verification for event webhooks](https://sendgrid.com/docs/for-developers/tracking-events/getting-started-event-webhook-security-features/) 2 | 3 | **Note:** It is important that the body of the request is verified raw (as a `Buffer` or `string`), not after it was parsed as `json`. 4 | If you are using `express.json()` or `bodyParser.json()` you will need to exclude the webhook path from it (one way to do it is using [express-unless](https://www.npmjs.com/package/express-unless)). 5 | 6 | An example of a server to process incoming event webhooks: 7 | ```javascript 8 | const bodyParser = require("body-parser"); // With Express 4.16+ you do not need this and can use express.json() and express.raw() instead 9 | const unless = require('express-unless'); 10 | const express = require('express'); 11 | const functions = require("firebase-functions"); 12 | const app = express(); 13 | 14 | const {EventWebhook, EventWebhookHeader} = require('@sendgrid/eventwebhook'); 15 | 16 | const verifyRequest = function (publicKey, payload, signature, timestamp) { 17 | const eventWebhook = new EventWebhook(); 18 | const ecPublicKey = eventWebhook.convertPublicKeyToECDSA(publicKey); 19 | return eventWebhook.verifySignature(ecPublicKey, payload, signature, timestamp); 20 | } 21 | 22 | // Exclude the webhook path from any json parsing 23 | const json_parser = bodyParser.json(); 24 | json_parser.unless = unless; 25 | app.use(json_parser.unless({ path: ["/sendgrid/webhook"]})); 26 | 27 | app.post("/sendgrid/webhook", 28 | // parse req.body as a Buffer 29 | bodyParser.raw({ type: 'application/json' }), 30 | async (req, resp) => { 31 | try { 32 | const key = ''; 33 | // Alternatively, you can get your key from your firebase function cloud config 34 | // const key = getConfig().sendgrid.webhook_verification_key; 35 | 36 | const signature = req.get(EventWebhookHeader.SIGNATURE()); 37 | const timestamp = req.get(EventWebhookHeader.TIMESTAMP()); 38 | 39 | // Be sure to _not_ remove any leading/trailing whitespace characters (e.g., '\r\n'). 40 | const requestBody = req.body; 41 | // Alternatively, if using firebase cloud functions, remove the middleware and use: 42 | // const requestBody = (req as functions.https.Request).rawBody; 43 | 44 | if (verifyRequest(key, requestBody, signature, timestamp)) { 45 | resp.sendStatus(204); 46 | } else { 47 | resp.sendStatus(403); 48 | } 49 | } catch (error) { 50 | resp.status(500).send(error); 51 | } 52 | }) 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/use-cases/flexible-address-fields.md: -------------------------------------------------------------------------------- 1 | ## Flexible email address fields 2 | The email address fields (`to`, `from`, `cc`, `bcc`, `replyTo`) are flexible and can be any of the following: 3 | 4 | ```js 5 | const msg = { 6 | 7 | //Simple email address string 8 | to: 'someone@example.org', 9 | 10 | //Email address with name 11 | to: 'Some One ', 12 | 13 | //Object with name/email 14 | to: { 15 | name: 'Some One', 16 | email: 'someone@example.org', 17 | }, 18 | 19 | //Arrays are supported for to, cc and bcc 20 | to: [ 21 | 'someone@example.org', 22 | 'Some One ', 23 | { 24 | name: 'Some One', 25 | email: 'someone@example.org', 26 | }, 27 | ], 28 | }; 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/use-cases/hide-warnings.md: -------------------------------------------------------------------------------- 1 | # Hide Warnings 2 | 3 | When using dynamic templates, if one of the values in the template data contains a single quote, a double quote or an ampersand, a warning will be logged. 4 | 5 | To hide this warning, set `hideWarnings` to `true` in the message. 6 | 7 | ```js 8 | const sgMail = require('@sendgrid/mail'); 9 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 10 | const msg = { 11 | to: 'recipient@example.org', 12 | from: 'sender@example.org', 13 | templateId: 'd-f43daeeaef504760851f727007e0b5d0', 14 | dynamic_template_data: { 15 | subject: 'Testing Templates', 16 | name: 'Some One', 17 | city: 'Denver', 18 | company: 'Recipient & Sender' 19 | }, 20 | hideWarnings: true // now the warning won't be logged 21 | }; 22 | sgMail.send(msg); 23 | ``` -------------------------------------------------------------------------------- /docs/use-cases/kitchen-sink.md: -------------------------------------------------------------------------------- 1 | # Kitchen Sink - an example with all settings used 2 | 3 | All other options from the [API definition](https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/index.html) are supported (note that some settings can be used in multiple ways, see above for full details for each setting): 4 | 5 | ```js 6 | const sgMail = require('@sendgrid/mail'); 7 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 8 | const msg = { 9 | to: 'recipient@example.org', 10 | cc: 'someone@example.org', 11 | bcc: ['me@example.org', 'you@example.org'], 12 | from: 'sender@example.org', 13 | replyTo: 'othersender@example.org', 14 | subject: 'Hello world', 15 | text: 'Hello plain world!', 16 | html: '

Hello HTML world!

', 17 | templateId: 'd-12345678901234567890123456789012', 18 | substitutionWrappers: ['{{', '}}'], 19 | dynamicTemplateData: { 20 | name: 'Some One', 21 | id: '123', 22 | }, 23 | attachments: [ 24 | { 25 | content: 'Some attachment content', 26 | filename: 'some-attachment.txt', 27 | }, 28 | ], 29 | categories: ['Transactional', 'My category'], 30 | sendAt: 1500077141, 31 | headers: { 32 | 'X-CustomHeader': 'Custom header value', 33 | }, 34 | sections: {}, 35 | customArgs: { 36 | myCustomArg: 'some string', // must be a string 37 | }, 38 | batchId: 'sendgrid-batch-id', 39 | asm: { 40 | groupId: 1 41 | }, 42 | ipPoolName: 'sendgrid-ip-pool-name', 43 | mailSettings: { 44 | sandboxMode: { 45 | enable: true, 46 | }, 47 | }, 48 | trackingSettings: {}, 49 | }; 50 | sgMail 51 | .send(msg) 52 | .then(() => console.log('Mail sent successfully')) 53 | .catch(error => { 54 | console.error(error); 55 | 56 | if (error.response) { 57 | console.error(error.response.body) 58 | } 59 | }); 60 | ``` 61 | 62 | ### Caveats: 63 | 64 | As per [issue #288](https://github.com/sendgrid/sendgrid-nodejs/issues/288), please note that the `customArgs` field *must* have a string value. 65 | -------------------------------------------------------------------------------- /docs/use-cases/manual-content.md: -------------------------------------------------------------------------------- 1 | # Manually Providing Content 2 | 3 | Instead of using the `text` and `html` shorthand properties, you can manually use the `content` property: 4 | 5 | ```js 6 | const msg = { 7 | to: 'recipient@example.org', 8 | from: 'sender@example.org', 9 | subject: 'Hello manual content', 10 | content: [ 11 | { 12 | type: 'text/html', 13 | value: '

Hello HTML world!

', 14 | }, 15 | { 16 | type: 'text/plain', 17 | value: 'Hello plain world!', 18 | }, 19 | ], 20 | }; 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/use-cases/multiple-emails-multiple-recipients.md: -------------------------------------------------------------------------------- 1 | # Send Multiple Emails to Multiple Recipients 2 | 3 | The `send` method also accepts an array of email msg if you want to send multiple different single emails with for example different content and sender values. This will send multiple requests (in parallel), so be aware of any API rate restrictions: 4 | 5 | ```js 6 | const emails = [ 7 | { 8 | to: 'recipient1@example.org', 9 | from: 'sender@example.org', 10 | subject: 'Hello recipient 1', 11 | text: 'Hello plain world!', 12 | html: '

Hello HTML world!

', 13 | }, 14 | { 15 | to: 'recipient2@example.org', 16 | from: 'other-sender@example.org', 17 | subject: 'Hello recipient 2', 18 | text: 'Hello other plain world!', 19 | html: '

Hello other HTML world!

', 20 | }, 21 | ]; 22 | sgMail.send(emails); 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/use-cases/multiple-emails-personalizations-with-substitutions.md: -------------------------------------------------------------------------------- 1 | # Send Multiple Emails with Personalizations and Substitutions 2 | 3 | Personalizations are an array of objects, each representing a separate email, that allow you to customize the metadata of each email sent within a request. With substitutions you can also have the email template dynamically be updated through [substitution tags](https://docs.sendgrid.com/for-developers/sending-email/substitution-tags). 4 | 5 | The below example shows how multiple emails, each with varying metadata and substitutions, are sent with personalizations. 6 | 7 | ```js 8 | const sgMail = require('@sendgrid/mail'); 9 | 10 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 11 | 12 | const msg = { 13 | from: 'sender1@example.org', 14 | subject: 'Ahoy!', 15 | text: 'Ahoy {{name}}!', 16 | html: '

Ahoy {{name}}!

', 17 | personalizations: [ 18 | { 19 | to: 'recipient1@example.org', 20 | substitutions: { 21 | name: 'Jon' 22 | } 23 | }, 24 | { 25 | to: 'recipient2@example.org', 26 | substitutions: { 27 | name: 'Jane' 28 | } 29 | }, 30 | { 31 | to: 'recipient3@example.org', 32 | substitutions: { 33 | name: 'Jack' 34 | } 35 | } 36 | ], 37 | }; 38 | 39 | sgMail.send(msg); 40 | ``` 41 | 42 | The default `substitutionWrappers` are `{{` and `}}` in the node.js library, but you can change it using the `substitutionWrappers` property. 43 | 44 | You can also use the SendGrid helper classes to achieve the same outcome: 45 | 46 | ```js 47 | const sgMail = require('@sendgrid/mail'); 48 | const sgHelpers = require('@sendgrid/helpers'); 49 | const Mail = sgHelpers.classes.Mail; 50 | const Personalization = sgHelpers.classes.Personalization; 51 | 52 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 53 | const mail = new Mail(); 54 | mail.setFrom('sender1@example.org'); 55 | mail.setSubject('Ahoy'); 56 | mail.addTextContent('Ahoy {{name}}!'); 57 | mail.addHtmlContent('

Ahoy {{name}}!

'); 58 | 59 | const personalization1 = new Personalization(); 60 | personalization1.setTo('recipient1@example.org'); 61 | personalization1.addSubstitution('name', 'Jon'); 62 | mail.addPersonalization(personalization1); 63 | 64 | const personalization2 = new Personalization(); 65 | personalization1.setTo('recipient2@example.org'); 66 | personalization1.addSubstitution('name', 'Jane'); 67 | mail.addPersonalization(personalization2); 68 | 69 | const personalization3 = new Personalization(); 70 | personalization1.setTo('recipient3@example.org'); 71 | personalization1.addSubstitution('name', 'Jack'); 72 | mail.addPersonalization(personalization3); 73 | 74 | sgMail.send(mail); 75 | ``` 76 | 77 | Refer to [the SendGrid documentation](https://docs.sendgrid.com/for-developers/sending-email/personalizations) for more details about personalizations. 78 | -------------------------------------------------------------------------------- /docs/use-cases/multiple-emails-personalizations.md: -------------------------------------------------------------------------------- 1 | # Send Multiple Emails with Personalizations 2 | 3 | Personalizations are an array of objects, each representing a separate email, that allow you to customize the metadata of each email sent within a request. The below example shows how multiple emails, each with varying metadata, are sent with personalizations. 4 | 5 | Refer to [the Sendgrid documentation](https://docs.sendgrid.com/for-developers/sending-email/personalizations) for more details about personalizations. 6 | ```js 7 | const sgMail = require('@sendgrid/mail'); 8 | const sgHelpers = require('@sendgrid/helpers'); 9 | const Personalization = sgHelpers.classes.Personalization; 10 | 11 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 12 | const msg = { 13 | from: 'sender1@example.org', 14 | subject: 'Hello world', 15 | text: 'Hello plain world!', 16 | html: '

Hello HTML world!

', 17 | personalizations: [] 18 | }; 19 | 20 | const personalization1 = new Personalization(); 21 | personalization1.setTo(['recipient2@example.org', 'recipient3@example.org']); 22 | personalization1.setCc('recipient4@example.org'); 23 | msg.personalizations.push(personalization1); 24 | 25 | const personalization2 = new Personalization(); 26 | personalization2.setTo(['recipient5@example.org', 'recipient6@example.org', 'recipient7@example.org']); 27 | personalization2.setFrom('sender2@example.org'); 28 | personalization2.setCc('recipient8@example.org'); 29 | msg.personalizations.push(personalization2); 30 | 31 | const personalization3 = new Personalization(); 32 | personalization3.setTo('recipient9@example.org'); 33 | personalization3.setFrom('sender3@example.org'); 34 | personalization3.setCc('recipient10@example.org'); 35 | personalization3.setSubject('Greetings world'); 36 | msg.personalizations.push(personalization3); 37 | 38 | sgMail.send(msg); 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/use-cases/multiple-reply-to-email.md: -------------------------------------------------------------------------------- 1 | # Multiple emails in replyTo 2 | 3 | An array of recipients who will receive replies and/or bounces. Each object in this array must contain the recipient's email address. Each object in the array may optionally contain the recipient's name. You can either choose to use “reply_to” field or “reply_to_list” but not both. [API specification](https://docs.sendgrid.com/api-reference/mail-send/mail-send#multiple-reply-to-emails) 4 | 5 | ```js 6 | const sgMail = require('@sendgrid/mail'); 7 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 8 | const msg = { 9 | to: 'recipient@example.org', 10 | from: 'sender@example.org', 11 | subject: 'Multiple mail in replyTo', 12 | html: '

Here’s an example of multiple replyTo email for you!

', 13 | replyToList: [ 14 | { 15 | 'name': 'Test User1', 16 | 'email': 'test_user1@example.org' 17 | }, 18 | { 19 | 'email': 'test_user2@example.org' 20 | } 21 | ], 22 | }; 23 | ``` 24 | 25 | -------------------------------------------------------------------------------- /docs/use-cases/scheduled-send.md: -------------------------------------------------------------------------------- 1 | # Creating a Scheduled Send 2 | 3 | Use the `sendAt` property to specify when to send the emails (in [UNIX timestamp](https://en.wikipedia.org/wiki/Unix_time) seconds, not milliseconds): 4 | 5 | ```js 6 | const msg = { 7 | to: 'recipient@example.org', 8 | from: 'sender@example.org', 9 | subject: 'Hello delayed email', 10 | html: '

Some email content

', 11 | sendAt: 1500077141, 12 | }; 13 | 14 | await sgMail.send(msg); 15 | ``` 16 | 17 | ## Limitations 18 | 19 | 1. Emails can only be scheduled, at most, 72 hours in advance. 20 | 2. If successful, without a `batchId` set, the call to `sgMail.send()` returns a 202 status code with an empty response body. Currently, cancelling a scheduled email without a `batchId` set [requires a change of password or contacting our support team](https://sendgrid.com/docs/for-developers/sending-email/stopping-an-in-progress-send/#stopping-transactional-email). 21 | 22 | ## [To Cancel or Pause Your Scheduled Send](https://sendgrid.com/docs/for-developers/sending-email/stopping-a-scheduled-send/#canceling-transactional-email): 23 | 24 | 1. Create a [Batch ID](../../packages/client/USAGE.md#create-a-batch-id). 25 | 2. Assign Batch ID to a `msg` object: 26 | ```js 27 | const msg = { 28 | to: 'recipient@example.org', 29 | from: 'sender@example.org', 30 | subject: 'Hello delayed email', 31 | html: '

Some email content

', 32 | sendAt: 1500077141, 33 | batchId: 'YOUR_BATCH_ID' 34 | }; 35 | 36 | await sgMail.send(msg); 37 | ``` 38 | 3. [Update your Batch ID](../../packages/client/USAGE.md#post-userscheduled_sends) with a `cancel` or `pause` status. 39 | -------------------------------------------------------------------------------- /docs/use-cases/single-email-multiple-recipients.md: -------------------------------------------------------------------------------- 1 | # Send a Single Email to Multiple Recipients 2 | 3 | The `to` field can contain an array of recipients, which will send a single email with all of the recipients in the `to` field. The recipients will be able to see each other: 4 | 5 | ```js 6 | const sgMail = require('@sendgrid/mail'); 7 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 8 | const msg = { 9 | to: ['recipient1@example.org', 'recipient2@example.org'], 10 | from: 'sender@example.org', 11 | subject: 'Hello world', 12 | text: 'Hello plain world!', 13 | html: '

Hello HTML world!

', 14 | }; 15 | sgMail.send(msg); 16 | ``` 17 | 18 | If you want to send multiple _individual_ emails to multiple recipient where they don't see each other's email addresses, use `sendMultiple` instead: 19 | 20 | ```js 21 | const sgMail = require('@sendgrid/mail'); 22 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 23 | const msg = { 24 | to: ['recipient1@example.org', 'recipient2@example.org'], 25 | from: 'sender@example.org', 26 | subject: 'Hello world', 27 | text: 'Hello plain world!', 28 | html: '

Hello HTML world!

', 29 | }; 30 | sgMail.sendMultiple(msg); 31 | ``` 32 | 33 | Note that `sendMultiple(msg)` is a convenience shortcut for `send(msg, true)`, and alternatively, you can also set the `isMultiple` flag to `true` on your `msg` object. 34 | -------------------------------------------------------------------------------- /docs/use-cases/single-email-single-recipient.md: -------------------------------------------------------------------------------- 1 | # Send a Single Email to a Single Recipient 2 | 3 | ```js 4 | const sgMail = require('@sendgrid/mail'); 5 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 6 | const msg = { 7 | to: 'recipient@example.org', 8 | from: 'sender@example.org', 9 | subject: 'Hello world', 10 | text: 'Hello plain world!', 11 | html: '

Hello HTML world!

', 12 | }; 13 | sgMail.send(msg); 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/use-cases/sms.md: -------------------------------------------------------------------------------- 1 | First, follow the [Twilio Setup](twilio-setup.md) guide for creating a Twilio account and setting up environment variables with the proper credentials. 2 | 3 | Then, install the Twilio Helper Library. 4 | 5 | ```bash 6 | npm install twilio 7 | ``` 8 | 9 | Finally, send a message. 10 | 11 | ```js 12 | var accountSid = process.env.TWILIO_ACCOUNT_SID; 13 | var authToken = process.env.TWILIO_AUTH_TOKEN; 14 | 15 | var twilio = require('twilio'); 16 | var client = new twilio(accountSid, authToken); 17 | 18 | client.messages.create({ 19 | body: 'Hello from Node', 20 | to: '+12345678901', // Text this number 21 | from: '+12345678901' // From a valid Twilio number 22 | }) 23 | .then((message) => console.log(message.sid)); 24 | ``` 25 | 26 | For more information, please visit the [Twilio SMS Node.js documentation](https://www.twilio.com/docs/sms/quickstart/node). 27 | -------------------------------------------------------------------------------- /docs/use-cases/success-failure-errors.md: -------------------------------------------------------------------------------- 1 | # Handling Success/Failure/Errors 2 | 3 | The `send` and `sendMultiple` methods return a `Promise`, so you can handle success and capture errors: 4 | 5 | ```js 6 | sgMail 7 | .send(msg) 8 | .then(() => { 9 | // Celebrate 10 | }) 11 | .catch(error => { 12 | // Log friendly error 13 | console.error(error); 14 | 15 | if (error.response) { 16 | // Extract error msg 17 | const {message, code, response} = error; 18 | 19 | // Extract response msg 20 | const {headers, body} = response; 21 | 22 | console.error(body); 23 | } 24 | }); 25 | ``` 26 | 27 | Alternatively, pass a callback function as the last parameter: 28 | 29 | ```js 30 | sgMail 31 | .send(msg, (error, result) => { 32 | if (error) { 33 | // Do something with the error 34 | } 35 | else { 36 | // Celebrate 37 | } 38 | }); 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/use-cases/timeout.md: -------------------------------------------------------------------------------- 1 | # Timeout 2 | 3 | The `setTimeout` method allows you to set a timeout on a request. The request will be aborted after the timeout. Note that the timeout is in milliseconds. Details [here](https://github.com/axios/axios#request-config). 4 | 5 | ```js 6 | const sgMail = require('@sendgrid/mail'); 7 | sgMail.setTimeout(3000); 8 | ``` 9 | -------------------------------------------------------------------------------- /docs/use-cases/transactional-legacy-templates.md: -------------------------------------------------------------------------------- 1 | # (LEGACY) Transactional Templates 2 | 3 | For this example, we assume you have created a [legacy transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html) in the UI or via the API. Following is the template content we used for testing. 4 | 5 | Template ID (replace with your own): 6 | 7 | ```text 8 | 13b8f94f-bcae-4ec6-b752-70d6cb59f932 9 | ``` 10 | 11 | Email Subject: 12 | 13 | ```text 14 | <%subject%> 15 | ``` 16 | 17 | Template Body: 18 | 19 | ```html 20 | 21 | 22 | 23 | 24 | 25 | Hello {{name}}, 26 |

27 | I'm glad you are trying out the template feature! 28 |

29 | <%body%> 30 |

31 | I hope you are having a great day in {{city}} :) 32 |

33 | 34 | 35 | ``` 36 | 37 | ```js 38 | const sgMail = require('@sendgrid/mail'); 39 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 40 | sgMail.setSubstitutionWrappers('{{', '}}'); // Configure the substitution tag wrappers globally 41 | const msg = { 42 | to: 'recipient@example.org', 43 | from: 'sender@example.org', 44 | subject: 'Hello world', 45 | text: 'Hello plain world!', 46 | html: '

Hello HTML world!

', 47 | templateId: '13b8f94f-bcae-4ec6-b752-70d6cb59f932', 48 | substitutions: { 49 | name: 'Some One', 50 | city: 'Denver', 51 | }, 52 | }; 53 | sgMail.send(msg); 54 | ``` 55 | 56 | Alternatively, you may specify the substitution wrappers via the msg object as well. This will override any wrappers you may have configured globally. 57 | 58 | ```js 59 | const msg = { 60 | ... 61 | substitutionWrappers: ['{{', '}}'], 62 | ... 63 | }; 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/use-cases/transactional-templates.md: -------------------------------------------------------------------------------- 1 | # Transactional Templates 2 | 3 | For this example, we assume you have created a [dynamic transactional template](https://sendgrid.com/docs/ui/sending-email/how-to-send-an-email-with-dynamic-transactional-templates/) in the UI or via the API. Following is the template content we used for testing. 4 | 5 | Email Subject: 6 | 7 | ```text 8 | {{ subject }} 9 | ``` 10 | 11 | Template Body: 12 | 13 | ```html 14 | 15 | 16 | 17 | 18 | 19 | Hello {{ name }}, 20 |

21 | I'm glad you are trying out the template feature! 22 |

23 | I hope you are having a great day in {{ city }} :) 24 |

25 | 26 | 27 | ``` 28 | 29 | ```js 30 | const sgMail = require('@sendgrid/mail'); 31 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 32 | const msg = { 33 | to: 'recipient@example.org', 34 | from: 'sender@example.org', 35 | templateId: 'd-f43daeeaef504760851f727007e0b5d0', 36 | dynamicTemplateData: { 37 | subject: 'Testing Templates', 38 | name: 'Some One', 39 | city: 'Denver', 40 | }, 41 | }; 42 | sgMail.send(msg); 43 | ``` 44 | 45 | There's no need to specify the substitution wrappers as it will assume that you're using [Handlebars](https://handlebarsjs.com/) templating by default. 46 | 47 | ## Prevent Escaping Characters 48 | 49 | Per Handlebars' documentation: If you don't want Handlebars to escape a value, use the "triple-stash", {{{ 50 | 51 | > If you include the characters ', " or & in a subject line replacement be sure to use three brackets. 52 | 53 | Email Subject: 54 | 55 | ```text 56 | {{{ subject }}} 57 | ``` 58 | 59 | Template Body: 60 | 61 | ```html 62 | 63 | 64 | 65 | 66 | 67 | Hello {{{ name }}}, 68 |

69 | I'm glad you are trying out the template feature! 70 |

71 | <%body%> 72 |

73 | I hope you are having a great day in {{{ city }}} :) 74 |

75 | 76 | 77 | ``` 78 | 79 | ```js 80 | const sgMail = require('@sendgrid/mail'); 81 | sgMail.setApiKey(process.env.SENDGRID_API_KEY); 82 | const msg = { 83 | to: 'recipient@example.org', 84 | from: 'sender@example.org', 85 | subject: 'Hello world', 86 | text: 'Hello plain world!', 87 | html: '

Hello HTML world!

', 88 | templateId: 'd-f43daeeaef504760851f727007e0b5d0', 89 | dynamic_template_data: { 90 | subject: 'Testing Templates & Stuff', 91 | name: 'Some "Testing" One', 92 | city: 'Denver', 93 | }, 94 | }; 95 | sgMail.send(msg); 96 | ``` 97 | -------------------------------------------------------------------------------- /docs/use-cases/twilio-email.md: -------------------------------------------------------------------------------- 1 | First, follow the [Twilio Setup](twilio-setup.md) guide for creating a Twilio account and setting up environment variables with the proper credentials. 2 | 3 | Then, initialize the Twilio Email Client. 4 | 5 | ```js 6 | const client = require('@sendgrid/client'); 7 | 8 | client.setTwilioEmailAuth(process.env.TWILIO_API_KEY, process.env.TWILIO_API_SECRET); 9 | 10 | // or 11 | 12 | client.setTwilioEmailAuth(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); 13 | ``` 14 | 15 | Or similarly using the mail helper. 16 | 17 | ```js 18 | const mail = require('@sendgrid/mail'); 19 | 20 | mail.setTwilioEmailAuth(process.env.TWILIO_API_KEY, process.env.TWILIO_API_SECRET); 21 | 22 | // or 23 | 24 | mail.setTwilioEmailAuth(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); 25 | ``` 26 | 27 | This sets the client to use Twilio Auth and the Twilio Email API. 28 | -------------------------------------------------------------------------------- /docs/use-cases/twilio-setup.md: -------------------------------------------------------------------------------- 1 | ## 1. Obtain a Free Twilio Account 2 | 3 | Sign up for a free Twilio account [here](https://www.twilio.com/try-twilio?source=sendgrid-nodejs). 4 | 5 | ## 2. Set Up Your Environment Variables 6 | 7 | The Twilio API allows for authentication using with either an API key/secret or your Account SID/Auth Token. You can create an API key [here](https://twil.io/get-api-key) or obtain your Account SID and Auth Token [here](https://twil.io/console). 8 | 9 | Once you have those, follow the steps below based on your operating system. 10 | 11 | ### Linux/Mac 12 | 13 | ```bash 14 | echo "export TWILIO_API_KEY='YOUR_TWILIO_API_KEY'" > twilio.env 15 | echo "export TWILIO_API_SECRET='YOUR_TWILIO_API_SECRET'" >> twilio.env 16 | 17 | # or 18 | 19 | echo "export TWILIO_ACCOUNT_SID='YOUR_TWILIO_ACCOUNT_SID'" > twilio.env 20 | echo "export TWILIO_AUTH_TOKEN='YOUR_TWILIO_AUTH_TOKEN'" >> twilio.env 21 | ``` 22 | 23 | Then: 24 | 25 | ```bash 26 | echo "twilio.env" >> .gitignore 27 | source ./twilio.env 28 | ``` 29 | 30 | ### Windows 31 | 32 | Temporarily set the environment variable (accessible only during the current CLI session): 33 | 34 | ```bash 35 | set TWILIO_API_KEY=YOUR_TWILIO_API_KEY 36 | set TWILIO_API_SECRET=YOUR_TWILIO_API_SECRET 37 | 38 | : or 39 | 40 | set TWILIO_ACCOUNT_SID=YOUR_TWILIO_ACCOUNT_SID 41 | set TWILIO_AUTH_TOKEN=YOUR_TWILIO_AUTH_TOKEN 42 | ``` 43 | 44 | Or permanently set the environment variable (accessible in all subsequent CLI sessions): 45 | 46 | ```bash 47 | setx TWILIO_API_KEY "YOUR_TWILIO_API_KEY" 48 | setx TWILIO_API_SECRET "YOUR_TWILIO_API_SECRET" 49 | 50 | : or 51 | 52 | setx TWILIO_ACCOUNT_SID "YOUR_TWILIO_ACCOUNT_SID" 53 | setx TWILIO_AUTH_TOKEN "YOUR_TWILIO_AUTH_TOKEN" 54 | ``` 55 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "8.1.5", 7 | "tagVersionPrefix": "", 8 | "lerna": "2.0.0" 9 | } 10 | -------------------------------------------------------------------------------- /license.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const fs = require('fs'); 7 | 8 | /** 9 | * Tests 10 | */ 11 | describe('LICENSE', () => { 12 | it('should contain the current year as the end year of the license', () => { 13 | const license = fs.readFileSync(`${process.cwd()}/LICENSE`, 'utf8'); 14 | const currentYear = (new Date()).getFullYear(); 15 | return expect(license.indexOf(`${currentYear}`)).to.not.equal(-1); 16 | }); 17 | }); 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sendgrid", 3 | "version": "6.0.0", 4 | "private": false, 5 | "license": "SEE LICENSE", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/sendgrid/sendgrid-nodejs.git" 9 | }, 10 | "devDependencies": { 11 | "@babel/cli": "^7.7.7", 12 | "@babel/core": "^7.7.7", 13 | "@babel/node": "^7.7.7", 14 | "chai": "4.2.0", 15 | "chai-as-promised": "^7.1.1", 16 | "dirty-chai": "^2.0.1", 17 | "eslint": "^6.8.0", 18 | "istanbul": "^1.0.0-alpha.2", 19 | "lerna": "^3.19.0", 20 | "mocha": "^6.2.2", 21 | "mocha-clean": "^1.0.0", 22 | "mocha-sinon": "^2.0.0", 23 | "moment": "^2.19.3", 24 | "sinon": "^2.3.2", 25 | "sinon-chai": "^2.10.0", 26 | "typescript": "^4.0.0" 27 | }, 28 | "scripts": { 29 | "lint": "if [ `node --version | cut -d'.' -f1 | cut -c 2` -ge \"8\" ]; then eslint . --fix; else echo \"eslint is not available for node < 8.0\"; fi", 30 | "test:all": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"**/*.spec.js\"", 31 | "test:helpers": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/helpers/**/*.spec.js\"", 32 | "test:client": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/client/**/*.spec.js\"", 33 | "test:mail": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/mail/**/*.spec.js\"", 34 | "test:eventwebhook": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/eventwebhook/**/*.spec.js\"", 35 | "test:inbound": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/inbound-mail-parser/**/*.spec.js\"", 36 | "test:contact": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/contact-importer/**/*.spec.js\"", 37 | "test:files": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"test/files.spec.js\"", 38 | "test:license": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha license.spec.js", 39 | "test:typescript": "tsc", 40 | "test": "npm run test:all -s", 41 | "coverage": "open -a \"Google Chrome\" ./coverage/lcov-report/index.html" 42 | }, 43 | "description": "![Twilio SendGrid Logo](https://github.com/sendgrid/sendgrid-nodejs/raw/HEAD/twilio_sendgrid_logo.png)", 44 | "bugs": { 45 | "url": "https://github.com/sendgrid/sendgrid-nodejs/issues" 46 | }, 47 | "homepage": "https://github.com/sendgrid/sendgrid-nodejs#readme", 48 | "main": "index.js", 49 | "directories": { 50 | "test": "test" 51 | }, 52 | "author": "Twilio SendGrid" 53 | } 54 | -------------------------------------------------------------------------------- /packages/client/USE_CASES.md: -------------------------------------------------------------------------------- 1 | This document provides examples for specific Twilio SendGrid v3 API non-mail/send use cases. Please [open an issue](https://github.com/sendgrid/sendgrid-nodejs/issues) or make a pull request for any use cases you would like us to document here. Thank you! 2 | 3 | # Table of Contents 4 | 5 | - [How to Setup a Domain Authentication](#domain-authentication) 6 | - [How to View Email Statistics](#how-to-view-email-statistics) 7 | - [How to use the Email Activity Feed](#how-to-use-the-email-activity-feed) 8 | 9 | 10 | 11 | # How to Setup a Domain Authentication 12 | 13 | You can find documentation for how to setup a domain authentication via the UI [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) and via API [here](USAGE.md#sender-authentication). 14 | 15 | Find more information about all of Twilio SendGrid's authentication related documentation [here](https://sendgrid.com/docs/ui/account-and-settings/). 16 | 17 | 18 | # How to View Email Statistics 19 | 20 | You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](USAGE.md#stats). 21 | 22 | Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) about events that occur as Twilio SendGrid processes your email. 23 | 24 | 25 | # How to use the Email Activity Feed 26 | 27 | You can find documentation for how to use the Email Activity Feed via the UI [here](https://sendgrid.com/docs/ui/analytics-and-reporting/email-activity-feed/) and via API [here](USAGE.md#messages). 28 | 29 | Find more information about getting started with the Email Activity Feed API [here](https://sendgrid.com/docs/API_Reference/Web_API_v3/Tutorials/getting_started_email_activity_api.html). 30 | -------------------------------------------------------------------------------- /packages/client/index.d.ts: -------------------------------------------------------------------------------- 1 | import Client = require("@sendgrid/client/src/client"); 2 | 3 | export = Client; -------------------------------------------------------------------------------- /packages/client/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const client = require('./src/client'); 4 | const Client = require('./src/classes/client'); 5 | 6 | module.exports = client; 7 | module.exports.Client = Client; 8 | -------------------------------------------------------------------------------- /packages/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sendgrid/client", 3 | "description": "Twilio SendGrid NodeJS API client", 4 | "version": "8.1.5", 5 | "author": "Twilio SendGrid (sendgrid.com)", 6 | "contributors": [ 7 | "Kyle Partridge ", 8 | "David Tomberlin ", 9 | "Swift ", 10 | "Brandon West ", 11 | "Scott Motte ", 12 | "Robert Acosta ", 13 | "Elmer Thomas ", 14 | "Adam Reis " 15 | ], 16 | "license": "MIT", 17 | "homepage": "https://sendgrid.com", 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/sendgrid/sendgrid-nodejs.git" 21 | }, 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "main": "index.js", 26 | "engines": { 27 | "node": ">=12.*" 28 | }, 29 | "dependencies": { 30 | "@sendgrid/helpers": "^8.0.0", 31 | "axios": "^1.8.2" 32 | }, 33 | "devDependencies": { 34 | "chai": "4.2.0", 35 | "nock": "^10.0.6" 36 | }, 37 | "resolutions": { 38 | "chai": "4.2.0" 39 | }, 40 | "tags": [ 41 | "http", 42 | "rest", 43 | "api", 44 | "mail", 45 | "sendgrid" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /packages/client/src/client.d.ts: -------------------------------------------------------------------------------- 1 | import {ResponseError} from "@sendgrid/helpers/classes"; 2 | import {ClientRequest} from "@sendgrid/client/src/request"; 3 | import {ClientResponse} from "@sendgrid/client/src/response"; 4 | 5 | declare class Client { 6 | constructor(); 7 | 8 | /** 9 | * Set the SendGrid API key. 10 | */ 11 | setApiKey(apiKey: string): void; 12 | 13 | /** 14 | * Set the Twilio Email credentials. 15 | */ 16 | setTwilioEmailAuth(username: string, password: string): void; 17 | 18 | /** 19 | * Set client requests to impersonate a subuser 20 | */ 21 | setImpersonateSubuser(subuser: string): void; 22 | 23 | /** 24 | * Set default header 25 | */ 26 | setDefaultHeader(key: string | { [s: string]: string }, value ?: string): this; 27 | 28 | /** 29 | * Set default request 30 | */ 31 | setDefaultRequest(key: K | ClientRequest, value ?: ClientRequest[K]): this; 32 | 33 | /** 34 | * Sets the data residency as per region provided 35 | */ 36 | setDataResidency(region: string): this; 37 | 38 | /** 39 | * Create headers for request 40 | */ 41 | createHeaders(data: { [key: string]: string }): { [key: string]: string }; 42 | 43 | /** 44 | * Create request 45 | */ 46 | createRequest(data: ClientRequest): ClientRequest; 47 | 48 | /** 49 | * Do a request 50 | */ 51 | request(data: ClientRequest, cb?: (err: ResponseError, response: [ClientResponse, any]) => void): Promise<[ClientResponse, any]>; 52 | } 53 | 54 | declare const client: Client; 55 | // @ts-ignore 56 | export = client 57 | 58 | export {Client}; 59 | -------------------------------------------------------------------------------- /packages/client/src/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Client = require('./classes/client'); 7 | 8 | //Export singleton instance 9 | module.exports = new Client(); 10 | -------------------------------------------------------------------------------- /packages/client/src/request.d.ts: -------------------------------------------------------------------------------- 1 | import RequestOptions from "@sendgrid/helpers/classes/request"; 2 | 3 | export type ClientRequest = RequestOptions; 4 | -------------------------------------------------------------------------------- /packages/client/src/response.d.ts: -------------------------------------------------------------------------------- 1 | import Response from "@sendgrid/helpers/classes/response"; 2 | 3 | export type ClientResponse = Response; 4 | -------------------------------------------------------------------------------- /packages/contact-importer/README.md: -------------------------------------------------------------------------------- 1 | [![Test and Deploy](https://github.com/sendgrid/sendgrid-nodejs/actions/workflows/test-and-deploy.yml/badge.svg)](https://github.com/sendgrid/sendgrid-nodejs/actions/workflows/test-and-deploy.yml) 2 | [![npm version](https://badge.fury.io/js/%40sendgrid%2Fclient.svg)](https://www.npmjs.com/org/sendgrid) 3 | 4 | **This package is part of a monorepo, please see [this README](../../README.md) for details.** 5 | 6 | 7 | # How to Contribute 8 | 9 | We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](../../CONTRIBUTING.md) guide for details. 10 | 11 | * [Feature Request](../../CONTRIBUTING.md#feature-request) 12 | * [Bug Reports](../../CONTRIBUTING.md#submit-a-bug-report) 13 | * [Improvements to the Codebase](../../CONTRIBUTING.md#improvements-to-the-codebase) 14 | 15 | 16 | # Troubleshooting 17 | 18 | Please see our [troubleshooting guide](https://github.com/sendgrid/sendgrid-nodejs/blob/main/TROUBLESHOOTING.md) for common library issues. 19 | 20 | 21 | # About 22 | 23 | @sendgrid/contact-importer is maintained and funded by Twilio SendGrid, Inc. The names and logos for @sendgrid/contact-importer are trademarks of Twilio SendGrid, Inc. 24 | 25 | If you need help installing or using the library, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com). 26 | 27 | If you've instead found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo! 28 | 29 | ![Twilio SendGrid Logo](https://github.com/sendgrid/sendgrid-nodejs/blob/main/twilio_sendgrid_logo.png?raw=true) 30 | -------------------------------------------------------------------------------- /packages/contact-importer/index.d.ts: -------------------------------------------------------------------------------- 1 | import Importer = require("@sendgrid/contact-importer/src/importer"); 2 | 3 | export = Importer; -------------------------------------------------------------------------------- /packages/contact-importer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sendgrid/contact-importer", 3 | "description": "Twilio SendGrid NodeJS contact importer", 4 | "version": "8.1.5", 5 | "author": "Twilio SendGrid (sendgrid.com)", 6 | "contributors": [ 7 | "Kyle Partridge ", 8 | "David Tomberlin ", 9 | "Swift ", 10 | "Brandon West ", 11 | "Scott Motte ", 12 | "Robert Acosta ", 13 | "Elmer Thomas " 14 | ], 15 | "license": "MIT", 16 | "homepage": "https://sendgrid.com", 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/sendgrid/sendgrid-nodejs.git" 20 | }, 21 | "main": "src/importer.js", 22 | "engines": { 23 | "node": ">=12.*" 24 | }, 25 | "publishConfig": { 26 | "access": "public" 27 | }, 28 | "dependencies": { 29 | "@sendgrid/client": "^8.1.5", 30 | "async.ensureasync": "^0.5.2", 31 | "async.queue": "^0.5.2", 32 | "bottleneck": "^1.12.0", 33 | "debug": "^4.0.1", 34 | "lodash.chunk": "^4.2.0" 35 | }, 36 | "tags": [ 37 | "sendgrid" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /packages/contact-importer/src/importer.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ContactImporterOptions { 2 | batchSize?: number; 3 | rateLimitLimit?: number; 4 | rateLimitPeriod?: number; 5 | } 6 | 7 | declare interface Contact { 8 | email: string; 9 | first_name?: string; 10 | last_name?: string; 11 | age?: number; 12 | } 13 | 14 | declare class ContactImporter { 15 | constructor(sg, options?: ContactImporterOptions); 16 | 17 | push(data: Contact|Contact[]) 18 | 19 | on(event: "success", cb: (result: any, batch: Contact[]) => void): void; 20 | on(event: "error", cb: (err: Error, batch?: Contact[]) => void): void; 21 | on(event: "drain", cb: () => void): void; 22 | on(event: string, cb: (data: any) => void): void; 23 | 24 | once(event: "success", cb: (result: any, batch: Contact[]) => void): void; 25 | once(event: "error", cb: (err: Error, batch?: Contact[]) => void): void; 26 | once(event: "drain", cb: () => void): void; 27 | once(event: string, cb: (data: any) => void): void; 28 | } 29 | 30 | export = ContactImporter; -------------------------------------------------------------------------------- /packages/contact-importer/src/importer.spec.js: -------------------------------------------------------------------------------- 1 | const sendgrid = require('@sendgrid/client'); 2 | const ContactImporter = require('./importer'); 3 | 4 | describe('test_contact_importer', function() { 5 | beforeEach(function() { 6 | // Create a new Twilio SendGrid instance. 7 | const API_KEY = process.env.API_KEY; 8 | sendgrid.setApiKey(API_KEY); 9 | 10 | // Create a new importer with a batch size of 2. 11 | this.contactImporter = new ContactImporter(sendgrid, { 12 | batchSize: 2, 13 | }); 14 | // this.spy = sinon.spy(ContactImporter.prototype, '_sendBatch') 15 | this.sinon.spy(ContactImporter.prototype, '_sendBatch'); 16 | 17 | // Generate some test data. 18 | const data = []; 19 | for (i = 0; i < 5; i++) { 20 | const item = { 21 | email: 'example' + i + '@example.com', 22 | first_name: 'Test', 23 | last_name: 'User', 24 | }; 25 | // Lets make the first user produce an error. 26 | if (i === 1) { 27 | item.invalid_field = 'some value'; 28 | } 29 | data.push(item); 30 | } 31 | this.contactImporter.push(data); 32 | }); 33 | 34 | it('test_contact_importer sends items in batches', function(done) { 35 | const self = this; 36 | this.timeout(30000); 37 | this.contactImporter.on('success', function(result, batch) { 38 | console.log('SUCCESS result', result); 39 | console.log('SUCCESS batch', batch); 40 | }); 41 | this.contactImporter.on('error', function(error, batch) { 42 | console.log('ERROR error', error); 43 | console.log('ERROR batch', batch); 44 | }); 45 | this.contactImporter.on('drain', function() { 46 | expect(self.contactImporter._sendBatch).to.have.callCount(3); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/eventwebhook/index.d.ts: -------------------------------------------------------------------------------- 1 | import EventWebhook = require('./src/eventwebhook'); 2 | 3 | export = EventWebhook; 4 | -------------------------------------------------------------------------------- /packages/eventwebhook/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EventWebhook = require('./src/eventwebhook'); 4 | 5 | module.exports = EventWebhook; 6 | -------------------------------------------------------------------------------- /packages/eventwebhook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sendgrid/eventwebhook", 3 | "description": "Twilio SendGrid NodeJS Event Webhook", 4 | "version": "8.0.0", 5 | "author": "Twilio SendGrid (sendgrid.com)", 6 | "contributors": [ 7 | "Elise Shanholtz " 8 | ], 9 | "license": "MIT", 10 | "homepage": "https://sendgrid.com", 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/sendgrid/sendgrid-nodejs.git" 14 | }, 15 | "publishConfig": { 16 | "access": "public" 17 | }, 18 | "main": "src/eventwebhook.js", 19 | "engines": { 20 | "node": ">=12.*" 21 | }, 22 | "dependencies": { 23 | "starkbank-ecdsa": "^1.1.1" 24 | }, 25 | "tags": [ 26 | "sendgrid" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /packages/eventwebhook/src/eventwebhook.d.ts: -------------------------------------------------------------------------------- 1 | // From starkbank-ecdsa, minimal interface of PublicKey 2 | declare class PublicKey { 3 | toString(encode?: boolean): string; 4 | static fromPem(pem: string): PublicKey; 5 | static fromDer(der: string): PublicKey; 6 | static fromString(key: string): PublicKey; 7 | } 8 | 9 | declare class EventWebhook { 10 | /** 11 | * 12 | * @param {string} publicKey verification key under Mail Settings 13 | * @return {PublicKey} A public key using the ECDSA algorithm 14 | */ 15 | convertPublicKeyToECDSA(publicKey: string): PublicKey; 16 | 17 | /** 18 | * 19 | * @param {PublicKey} publicKey elliptic curve public key 20 | * @param {string|Buffer} payload event payload in the request body 21 | * @param {string} signature value obtained from the 'X-Twilio-Email-Event-Webhook-Signature' header 22 | * @param {string} timestamp value obtained from the 'X-Twilio-Email-Event-Webhook-Timestamp' header 23 | * @return {Boolean} true or false if signature is valid 24 | */ 25 | verifySignature(publicKey: PublicKey, payload: string|Buffer, signature: string, timestamp: string): boolean; 26 | } 27 | 28 | /* 29 | * This class lists headers that get posted to the webhook. Read the docs for 30 | * more details: https://sendgrid.com/docs/for-developers/tracking-events/event 31 | */ 32 | declare class EventWebhookHeader { 33 | static SIGNATURE(): string; 34 | static TIMESTAMP(): string 35 | } 36 | 37 | export { 38 | EventWebhook, 39 | EventWebhookHeader, 40 | }; 41 | -------------------------------------------------------------------------------- /packages/eventwebhook/src/eventwebhook.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ecdsa = require('starkbank-ecdsa'); 4 | const Ecdsa = ecdsa.Ecdsa; 5 | const Signature = ecdsa.Signature; 6 | const PublicKey = ecdsa.PublicKey; 7 | 8 | /* 9 | * This class allows you to use the Event Webhook feature. Read the docs for 10 | * more details: https://sendgrid.com/docs/for-developers/tracking-events/event 11 | */ 12 | class EventWebhook { 13 | /** 14 | * Convert the public key string to a ECPublicKey. 15 | * 16 | * @param {string} publicKey verification key under Mail Settings 17 | * @return {PublicKey} A public key using the ECDSA algorithm 18 | */ 19 | convertPublicKeyToECDSA(publicKey) { 20 | return PublicKey.fromPem(publicKey); 21 | } 22 | 23 | /** 24 | * Verify signed event webhook requests. 25 | * 26 | * @param {PublicKey} publicKey elliptic curve public key 27 | * @param {string|Buffer} payload event payload in the request body 28 | * @param {string} signature value obtained from the 'X-Twilio-Email-Event-Webhook-Signature' header 29 | * @param {string} timestamp value obtained from the 'X-Twilio-Email-Event-Webhook-Timestamp' header 30 | * @return {Boolean} true or false if signature is valid 31 | */ 32 | verifySignature(publicKey, payload, signature, timestamp) { 33 | let timestampPayload = Buffer.isBuffer(payload) ? payload.toString() : payload; 34 | timestampPayload = timestamp + timestampPayload; 35 | const decodedSignature = Signature.fromBase64(signature); 36 | 37 | return Ecdsa.verify(timestampPayload, decodedSignature, publicKey); 38 | } 39 | } 40 | 41 | /* 42 | * This class lists headers that get posted to the webhook. Read the docs for 43 | * more details: https://sendgrid.com/docs/for-developers/tracking-events/event 44 | */ 45 | class EventWebhookHeader { 46 | static SIGNATURE() { 47 | return 'X-Twilio-Email-Event-Webhook-Signature'; 48 | } 49 | 50 | static TIMESTAMP() { 51 | return 'X-Twilio-Email-Event-Webhook-Timestamp'; 52 | } 53 | } 54 | 55 | module.exports = { 56 | EventWebhook, 57 | EventWebhookHeader, 58 | }; 59 | -------------------------------------------------------------------------------- /packages/helpers/README.md: -------------------------------------------------------------------------------- 1 | [![BuildStatus](https://travis-ci.com/sendgrid/sendgrid-nodejs.svg?branch=main)](https://travis-ci.com/sendgrid/sendgrid-nodejs) 2 | [![npm version](https://badge.fury.io/js/%40sendgrid%2Fclient.svg)](https://www.npmjs.com/org/sendgrid) 3 | 4 | **This package is part of a monorepo, please see [this README](../../README.md) for details.** 5 | 6 | # Support classes and helpers for the SendGrid NodeJS libraries 7 | This is a collection of classes and helpers used internally by the 8 | [SendGrid NodeJS libraries](https://www.npmjs.com/org/sendgrid). 9 | 10 | Note that not all objects represented in the SendGrid API have helper classes assigned to them because it is not expected that developers will use these classes themselves. They are primarily for internal use and developers are expected to use the publicly exposed API in the [various endpoint services](https://www.npmjs.com/org/sendgrid). 11 | 12 | ## Mail class 13 | Used to compose a `Mail` object that converts itself to proper JSON for use with the [SendGrid v3 API](https://sendgrid.com/docs/api-reference/). This class supports a slightly different API to make sending emails easier in many cases by not having to deal with personalization arrays, instead offering a more straightforward interface for composing emails. 14 | 15 | ## Attachment class 16 | Used by the inbound mail parser to compose `Attachment` objects. 17 | 18 | ## Personalization class 19 | Used by the Mail class to compose `Personalization` objects. 20 | 21 | ## Email address 22 | `Helper` class to represent an email address with name/email. Used by both the `Mail` and `Personalization` classes to deal with email addresses of various formats. 23 | 24 | ## Helpers 25 | Internal helpers that mostly speak for themselves. 26 | 27 | 28 | # How to Contribute 29 | 30 | We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](https://github.com/sendgrid/sendgrid-nodejs/blob/HEAD/CONTRIBUTING.md) guide for details. 31 | 32 | * [Feature Request](../../CONTRIBUTING.md#feature-request) 33 | * [Bug Reports](../../CONTRIBUTING.md#submit-a-bug-report) 34 | * [Improvements to the Codebase](../../CONTRIBUTING.md#improvements-to-the-codebase) 35 | 36 | 37 | # About 38 | 39 | @sendgrid/helpers are maintained and funded by Twilio SendGrid, Inc. The names and logos for @sendgrid/helpers are trademarks of Twilio SendGrid, Inc. 40 | 41 | If you need help installing or using the library, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com). 42 | 43 | If you've instead found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo! 44 | 45 | ![Twilio SendGrid Logo](https://github.com/sendgrid/sendgrid-nodejs/blob/main/twilio_sendgrid_logo.png?raw=true) 46 | -------------------------------------------------------------------------------- /packages/helpers/attachment.txt: -------------------------------------------------------------------------------- 1 | Just a little file for testing attachments. -------------------------------------------------------------------------------- /packages/helpers/classes/attachment.d.ts: -------------------------------------------------------------------------------- 1 | export interface AttachmentData { 2 | content: string; 3 | filename: string; 4 | type?: string; 5 | disposition?: string; 6 | contentId?: string; 7 | } 8 | 9 | export interface AttachmentJSON { 10 | content: string; 11 | filename: string; 12 | type?: string; 13 | disposition?: string; 14 | content_id?: string; 15 | } 16 | 17 | export default class Attachment implements AttachmentData { 18 | content: string; 19 | filename: string; 20 | type?: string; 21 | disposition?: string; 22 | contentId?: string; 23 | 24 | constructor(data?: AttachmentData); 25 | 26 | fromData(data: AttachmentData): void; 27 | setContent(content: string): void; 28 | setFilename(filename: string): void; 29 | setType(type: string): void; 30 | setDisposition(disposition: string): void; 31 | setContentId(contentId: string): void; 32 | toJSON(): AttachmentJSON; 33 | } 34 | -------------------------------------------------------------------------------- /packages/helpers/classes/attachment.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | /** 7 | * Dependencies 8 | */ 9 | const Attachment = require('./attachment'); 10 | 11 | /** 12 | * Tests 13 | */ 14 | describe('Attachment', function() { 15 | let attachment; 16 | beforeEach(function() { 17 | attachment = new Attachment(); 18 | }); 19 | 20 | //Set content as string 21 | describe('setContent(), string', function() { 22 | it('should set string as content', function() { 23 | attachment.setContent('Just a string.'); 24 | expect(attachment.content).to.equal('Just a string.'); 25 | }); 26 | }); 27 | 28 | //Set content as stream 29 | describe('setContent(), stream', function() { 30 | it('should convert stream to string and set as content', function() { 31 | const fileData = fs.readFileSync('./packages/helpers/attachment.txt'); 32 | attachment.setContent(fileData); 33 | expect(attachment.content).to.equal('Just a little file for testing attachments.'); 34 | }); 35 | }); 36 | 37 | //Set content as wrong type 38 | describe('setContent(), wrong type', function() { 39 | it('should not allow setting content of wrong type', function() { 40 | expect(() => attachment.setContent(null)).to.throw('`content` expected to be either Buffer or string'); 41 | }); 42 | }); 43 | 44 | //Constructor 45 | describe('constructor(data)', function() { 46 | it('should not accept both content and filePath', function() { 47 | expect(function() { 48 | attachment = new Attachment({ 49 | filename: 'attachment.txt', 50 | type: 'plain/text', 51 | disposition: 'attachment', 52 | contentId: 'myattachment', 53 | content: '', 54 | filePath: '', 55 | }); 56 | }).to.throw(Error); 57 | }); 58 | }); 59 | }); 60 | 61 | //Set content 62 | describe('setContent()', function() { 63 | let attachment; 64 | 65 | beforeEach(function() { 66 | attachment = new Attachment({ 67 | filename: 'attachment.txt', 68 | type: 'plain/text', 69 | disposition: 'attachment', 70 | contentId: 'myattachment', 71 | content: 'SGVsbG8gV29ybGQK', 72 | }); 73 | }); 74 | 75 | it('should set the given value', function() { 76 | expect(attachment.content).to.equal('SGVsbG8gV29ybGQK'); 77 | }); 78 | 79 | it('should accept a buffer', function() { 80 | attachment.setContent(new Buffer('Hello World\n')); 81 | expect(attachment.content).to.equal('SGVsbG8gV29ybGQK'); 82 | }); 83 | 84 | it('should accept a raw file', function() { 85 | attachment = new Attachment({ 86 | filename: 'attachment.txt', 87 | type: 'plain/text', 88 | disposition: 'attachment', 89 | contentId: 'myattachment', 90 | filePath: path.join(__dirname, '/attachment.js'), 91 | }); 92 | 93 | expect(attachment.content).to.be.a('string'); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /packages/helpers/classes/email-address.d.ts: -------------------------------------------------------------------------------- 1 | export type EmailData = string|{ name?: string; email: string; } 2 | 3 | export type EmailJSON = { name?: string; email: string } 4 | 5 | export default class EmailAddress { 6 | constructor(data?: EmailData); 7 | 8 | /** 9 | * From data 10 | */ 11 | fromData(data: EmailData): void; 12 | 13 | /** 14 | * Set name 15 | */ 16 | setName(name: string): void; 17 | 18 | /** 19 | * Set email (mandatory) 20 | */ 21 | setEmail(email: string): void; 22 | 23 | toJSON(): EmailJSON; 24 | } -------------------------------------------------------------------------------- /packages/helpers/classes/email-address.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const splitNameEmail = require('../helpers/split-name-email'); 7 | 8 | /** 9 | * Email address class 10 | */ 11 | class EmailAddress { 12 | 13 | /** 14 | * Constructor 15 | */ 16 | constructor(data) { 17 | 18 | //Construct from data 19 | if (data) { 20 | this.fromData(data); 21 | } 22 | } 23 | 24 | /** 25 | * From data 26 | */ 27 | fromData(data) { 28 | 29 | //String given 30 | if (typeof data === 'string') { 31 | const [name, email] = splitNameEmail(data); 32 | data = {name, email}; 33 | } 34 | 35 | //Expecting object 36 | if (typeof data !== 'object') { 37 | throw new Error('Expecting object or string for EmailAddress data'); 38 | } 39 | 40 | //Extract name and email 41 | const {name, email} = data; 42 | 43 | //Set 44 | this.setEmail(email); 45 | this.setName(name); 46 | } 47 | 48 | /** 49 | * Set name 50 | */ 51 | setName(name) { 52 | if (typeof name === 'undefined') { 53 | return; 54 | } 55 | if (typeof name !== 'string') { 56 | throw new Error('String expected for `name`'); 57 | } 58 | this.name = name; 59 | } 60 | 61 | /** 62 | * Set email (mandatory) 63 | */ 64 | setEmail(email) { 65 | if (typeof email === 'undefined') { 66 | throw new Error('Must provide `email`'); 67 | } 68 | if (typeof email !== 'string') { 69 | throw new Error('String expected for `email`'); 70 | } 71 | this.email = email; 72 | } 73 | 74 | /** 75 | * To JSON 76 | */ 77 | toJSON() { 78 | 79 | //Get properties 80 | const {email, name} = this; 81 | 82 | //Initialize with mandatory properties 83 | const json = {email}; 84 | 85 | //Add name if present 86 | if (name !== '') { 87 | json.name = name; 88 | } 89 | 90 | //Return 91 | return json; 92 | } 93 | 94 | /************************************************************************** 95 | * Static helpers 96 | ***/ 97 | 98 | /** 99 | * Create an EmailAddress instance from given data 100 | */ 101 | static create(data) { 102 | 103 | //Array? 104 | if (Array.isArray(data)) { 105 | return data 106 | .filter(item => !!item) 107 | .map(item => this.create(item)); 108 | } 109 | 110 | //Already instance of EmailAddress class? 111 | if (data instanceof EmailAddress) { 112 | return data; 113 | } 114 | 115 | //Create instance 116 | return new EmailAddress(data); 117 | } 118 | } 119 | 120 | //Export class 121 | module.exports = EmailAddress; 122 | -------------------------------------------------------------------------------- /packages/helpers/classes/index.d.ts: -------------------------------------------------------------------------------- 1 | import Attachment from "@sendgrid/helpers/classes/attachment"; 2 | import EmailAddress from "@sendgrid/helpers/classes/email-address"; 3 | import Mail from "@sendgrid/helpers/classes/mail" 4 | import Personalization from "@sendgrid/helpers/classes/personalization"; 5 | import Response from "@sendgrid/helpers/classes/response"; 6 | import ResponseError from "@sendgrid/helpers/classes/response-error"; 7 | 8 | export { 9 | Attachment, 10 | EmailAddress, 11 | Mail, 12 | Personalization, 13 | Response, 14 | ResponseError, 15 | } 16 | -------------------------------------------------------------------------------- /packages/helpers/classes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Expose classes 5 | */ 6 | const Attachment = require('./attachment'); 7 | const EmailAddress = require('./email-address'); 8 | const Mail = require('./mail'); 9 | const Personalization = require('./personalization'); 10 | const Response = require('./response'); 11 | const ResponseError = require('./response-error'); 12 | const Statistics = require('./statistics'); 13 | 14 | /** 15 | * Export 16 | */ 17 | module.exports = { 18 | Attachment, 19 | EmailAddress, 20 | Mail, 21 | Personalization, 22 | Response, 23 | ResponseError, 24 | Statistics, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization.d.ts: -------------------------------------------------------------------------------- 1 | import { EmailData, EmailJSON } from "./email-address"; 2 | 3 | export interface PersonalizationData { 4 | to: EmailData | EmailData[], 5 | from?: EmailData, 6 | cc?: EmailData | EmailData[], 7 | bcc?: EmailData | EmailData[], 8 | subject?: string; 9 | headers?: { [key: string]: string }; 10 | substitutions?: { [key: string]: string }; 11 | dynamicTemplateData?: { [key: string]: any; }; 12 | customArgs?: { [key: string]: string }; 13 | sendAt?: number; 14 | } 15 | 16 | export interface PersonalizationJSON { 17 | to: EmailJSON | EmailJSON[]; 18 | from?: EmailJSON; 19 | cc?: EmailJSON[]; 20 | bcc?: EmailJSON[]; 21 | headers?: { [key: string]: string; }; 22 | substitutions?: { [key: string]: string; }; 23 | dynamic_template_data?: { [key: string]: string; }; 24 | custom_args?: { [key: string]: string; }; 25 | subject?: string; 26 | send_at?: number; 27 | } 28 | 29 | export default class Personalization { 30 | constructor(data?: PersonalizationData); 31 | 32 | fromData(data: PersonalizationData): void; 33 | 34 | /** 35 | * Set subject 36 | */ 37 | setSubject(subject: string): void; 38 | 39 | /** 40 | * Set send at 41 | */ 42 | setSendAt(sendAt: number): void; 43 | 44 | /** 45 | * Set to 46 | */ 47 | setTo(to: EmailData | EmailData[]): void; 48 | 49 | /** 50 | * Set from 51 | */ 52 | setFrom(from: EmailData): void; 53 | 54 | /** 55 | * Add a single to 56 | */ 57 | addTo(to: EmailData): void; 58 | 59 | /** 60 | * Set cc 61 | */ 62 | setCc(cc: EmailData | EmailData[]): void; 63 | 64 | /** 65 | * Add a single cc 66 | */ 67 | addCc(cc: EmailData): void; 68 | 69 | /** 70 | * Set bcc 71 | */ 72 | setBcc(bcc: EmailData | EmailData[]): void; 73 | 74 | /** 75 | * Add a single bcc 76 | */ 77 | addBcc(bcc: EmailData): void; 78 | 79 | /** 80 | * Set headers 81 | */ 82 | setHeaders(headers: { [key: string]: string }): void; 83 | 84 | /** 85 | * Add a header 86 | */ 87 | addHeader(key: string, value: string): void; 88 | 89 | /** 90 | * Set custom args 91 | */ 92 | setCustomArgs(customArgs: { [key: string]: string }): void; 93 | 94 | /** 95 | * Add a custom arg 96 | */ 97 | addCustomArg(key: string, value: string): void; 98 | 99 | /** 100 | * Set substitutions 101 | */ 102 | setSubstitutions(substitutions: { [key: string]: string }): void; 103 | 104 | /** 105 | * Add a substitution 106 | */ 107 | addSubstitution(key: string, value: string): void; 108 | 109 | /** 110 | * Reverse merge substitutions, preserving existing ones 111 | */ 112 | reverseMergeSubstitutions(substitutions: { [key: string]: string }): void; 113 | 114 | /** 115 | * Set substitution wrappers 116 | */ 117 | setSubstitutionWrappers(wrappers: string[]): void; 118 | 119 | /** 120 | * Set dynamic template data 121 | */ 122 | setDynamicTemplateData(dynamicTemplateData: { [key: string]: any }): void; 123 | 124 | /** 125 | * To JSON 126 | */ 127 | toJSON(): PersonalizationJSON; 128 | } -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/527-camel-case-headers.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | // camel case headers test 21 | describe('#527', function() { 22 | it('shouldn\'t convert the headers to camel/snake case', function() { 23 | const p = new Personalization({ 24 | to: 'test@example.com', 25 | headers: { 26 | 'List-Unsubscribe': '', 27 | }, 28 | }); 29 | 30 | expect(p.headers['List-Unsubscribe']).to.equal(''); 31 | 32 | expect(p.toJSON().headers['List-Unsubscribe']).to 33 | .equal(''); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/PERSONALIZATION-SPECS-README.md: -------------------------------------------------------------------------------- 1 | #### Personalization helper specs 2 | 3 | - setSubject() - set-subject.spec.js 4 | - setSendAt() - set-send-at.spec.js 5 | - setTo() - set-to.spec.js 6 | - setFrom() - set-from.spec.js 7 | - addTo() - add-to.spec.js 8 | - setCc() - set-cc.spec.js 9 | - addCc() - add-cc.spec.js 10 | - setBcc() - set-bcc.spec.js 11 | - addBcc() - add-bcc.spec.js 12 | - setHeaders() - set-headers.spec.js 13 | - addHeader() - add-headers.spec.js 14 | - setCustomArgs() - set-custom-args.spec.js 15 | - addCustomArg() - add-custom-args.spec.js 16 | - setSubstitutions() - set-substitutions.spec.js 17 | - addSubstitution() - add-substitutions.spec.js 18 | - reverseMergeSubstitutions() - reverse-merge-substitutions.spec.js 19 | - setSubstitutionWrappers() - set-substitutions-wrappers.spec.js 20 | - deepMergeDynamicTemplateData() - reverse-merge-dynamic_template_data.spec.js 21 | - toJSON() - to-json.spec.js 22 | - fromData() - from-data.spec.js 23 | - #527 - 527-camel-case-headers.spec.js -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/add-bcc.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Add bcc 21 | describe('addBcc()', function() { 22 | it('should add the item', function() { 23 | p.addBcc('test@example.org'); 24 | expect(p.bcc).to.be.an.instanceof(Array); 25 | expect(p.bcc).to.have.a.lengthOf(1); 26 | expect(p.bcc[0]).to.be.an.instanceof(EmailAddress); 27 | expect(p.bcc[0].email).to.equal('test@example.org'); 28 | }); 29 | it('should handle multiple values', function() { 30 | p.addBcc('test1@example.org'); 31 | p.addBcc('test2@example.org'); 32 | expect(p.bcc).to.be.an.instanceof(Array); 33 | expect(p.bcc).to.have.a.lengthOf(2); 34 | expect(p.bcc[0]).to.be.an.instanceof(EmailAddress); 35 | expect(p.bcc[0].email).to.equal('test1@example.org'); 36 | expect(p.bcc[1]).to.be.an.instanceof(EmailAddress); 37 | expect(p.bcc[1].email).to.equal('test2@example.org'); 38 | }); 39 | it('should accept no input', function() { 40 | expect(function() { 41 | p.addBcc(); 42 | }).not.to.throw(Error); 43 | }); 44 | it('should not overwrite with no input', function() { 45 | p.addBcc('test@example.org'); 46 | p.addBcc(); 47 | expect(p.bcc).to.be.an.instanceof(Array); 48 | expect(p.bcc).to.have.a.lengthOf(1); 49 | expect(p.bcc[0]).to.be.an.instanceof(EmailAddress); 50 | expect(p.bcc[0].email).to.equal('test@example.org'); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/add-cc.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Add cc 21 | describe('addCc()', function() { 22 | it('should add the item', function() { 23 | p.addCc('test@example.org'); 24 | expect(p.cc).to.be.an.instanceof(Array); 25 | expect(p.cc).to.have.a.lengthOf(1); 26 | expect(p.cc[0]).to.be.an.instanceof(EmailAddress); 27 | expect(p.cc[0].email).to.equal('test@example.org'); 28 | }); 29 | it('should handle multiple values', function() { 30 | p.addCc('test1@example.org'); 31 | p.addCc('test2@example.org'); 32 | expect(p.cc).to.be.an.instanceof(Array); 33 | expect(p.cc).to.have.a.lengthOf(2); 34 | expect(p.cc[0]).to.be.an.instanceof(EmailAddress); 35 | expect(p.cc[0].email).to.equal('test1@example.org'); 36 | expect(p.cc[1]).to.be.an.instanceof(EmailAddress); 37 | expect(p.cc[1].email).to.equal('test2@example.org'); 38 | }); 39 | it('should accept no input', function() { 40 | expect(function() { 41 | p.addCc(); 42 | }).not.to.throw(Error); 43 | }); 44 | it('should not overwrite with no input', function() { 45 | p.addCc('test@example.org'); 46 | p.addCc(); 47 | expect(p.cc).to.be.an.instanceof(Array); 48 | expect(p.cc).to.have.a.lengthOf(1); 49 | expect(p.cc[0]).to.be.an.instanceof(EmailAddress); 50 | expect(p.cc[0].email).to.equal('test@example.org'); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/add-custom-args.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Add custom arg 21 | describe('addCustomArg()', function() { 22 | it('should set the given value', function() { 23 | p.addCustomArg('test', 'Test'); 24 | expect(p.customArgs).to.be.an.instanceof(Object); 25 | expect(p.customArgs).to.have.a.property('test'); 26 | expect(p.customArgs.test).to.equal('Test'); 27 | }); 28 | it('should add multiple values', function() { 29 | p.addCustomArg('test1', 'Test1'); 30 | p.addCustomArg('test2', 'Test2'); 31 | expect(p.customArgs).to.have.a.property('test1'); 32 | expect(p.customArgs).to.have.a.property('test2'); 33 | expect(p.customArgs.test1).to.equal('Test1'); 34 | expect(p.customArgs.test2).to.equal('Test2'); 35 | }); 36 | it('should throw an error for invalid input', function() { 37 | expect(function() { 38 | p.addCustomArg('test'); 39 | }).to.throw(Error); 40 | expect(function() { 41 | p.addCustomArg(null, 'test'); 42 | }).to.throw(Error); 43 | expect(function() { 44 | p.addCustomArg(3, 5); 45 | }).to.throw(Error); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/add-headers.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Add header 21 | describe('addHeader()', function() { 22 | it('should set the given value', function() { 23 | p.addHeader('test', 'Test'); 24 | expect(p.headers).to.be.an.instanceof(Object); 25 | expect(p.headers).to.have.a.property('test'); 26 | expect(p.headers.test).to.equal('Test'); 27 | }); 28 | it('should add multiple values', function() { 29 | p.addHeader('test1', 'Test1'); 30 | p.addHeader('test2', 'Test2'); 31 | expect(p.headers).to.have.a.property('test1'); 32 | expect(p.headers).to.have.a.property('test2'); 33 | expect(p.headers.test1).to.equal('Test1'); 34 | expect(p.headers.test2).to.equal('Test2'); 35 | }); 36 | it('should throw an error for invalid input', function() { 37 | expect(function() { 38 | p.addHeader('test'); 39 | }).to.throw(Error); 40 | expect(function() { 41 | p.addHeader(null, 'test'); 42 | }).to.throw(Error); 43 | expect(function() { 44 | p.addHeader(3, 5); 45 | }).to.throw(Error); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/add-substitutions.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Add substitution 21 | describe('addSubstitution()', function() { 22 | it('should set the given value', function() { 23 | p.addSubstitution('test', 'Test'); 24 | expect(p.substitutions).to.be.an.instanceof(Object); 25 | expect(p.substitutions).to.have.a.property('test'); 26 | expect(p.substitutions.test).to.equal('Test'); 27 | }); 28 | it('should add multiple values', function() { 29 | p.addSubstitution('test1', 'Test1'); 30 | p.addSubstitution('test2', 2); 31 | expect(p.substitutions).to.have.a.property('test1'); 32 | expect(p.substitutions).to.have.a.property('test2'); 33 | expect(p.substitutions.test1).to.equal('Test1'); 34 | expect(p.substitutions.test2).to.equal(2); 35 | }); 36 | it('should throw an error for invalid input', function() { 37 | expect(function() { 38 | p.addSubstitution('test'); 39 | }).to.throw(Error); 40 | expect(function() { 41 | p.addSubstitution(null, 'test'); 42 | }).to.throw(Error); 43 | expect(function() { 44 | p.addSubstitution(3, false); 45 | }).to.throw(Error); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/from-data.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //From data 21 | describe('fromData()', function() { 22 | 23 | //Data 24 | const data = { 25 | to: 'to@example.org', 26 | from: 'from@example.org', 27 | cc: ['cc1@example.org', 'cc2@example.org'], 28 | bcc: ['bcc1@example.org', 'bcc2@example.org'], 29 | subject: 'Test', 30 | sendAt: 1000, 31 | headers: {test: 'Test'}, 32 | customArgs: {snake_case: 'Test', T_EST: 'Test', camelCase: 'Test'}, 33 | substitutions: {snake_case: 'Test', T_EST: 'Test', camelCase: 'Test'}, 34 | substitutionWrappers: ['[', ']'], 35 | }; 36 | 37 | //Tests 38 | it('should call fromData() from the constructor', () => { 39 | p = new Personalization(data); 40 | expect(p.to[0].email).to.equal('to@example.org'); 41 | expect(p.subject).to.equal('Test'); 42 | }); 43 | it('should throw an error for invalid input', () => { 44 | expect(function() { 45 | p.fromData(5); 46 | }).to.throw(Error); 47 | }); 48 | it('should have set all properties', () => { 49 | p.fromData(data); 50 | expect(p.to[0].email).to.equal('to@example.org'); 51 | expect(p.from.email).to.equal('from@example.org'); 52 | expect(p.cc[0].email).to.equal('cc1@example.org'); 53 | expect(p.cc[1].email).to.equal('cc2@example.org'); 54 | expect(p.bcc[0].email).to.equal('bcc1@example.org'); 55 | expect(p.bcc[1].email).to.equal('bcc2@example.org'); 56 | expect(p.subject).to.equal('Test'); 57 | expect(p.sendAt).to.equal(1000); 58 | expect(p.headers.test).to.equal('Test'); 59 | expect(p.customArgs.snake_case).to.equal('Test'); 60 | expect(p.substitutions.snake_case).to.equal('Test'); 61 | expect(p.substitutionWrappers).to.have.members(['[', ']']); 62 | }); 63 | it('should not modify the keys of substitutions and custom args', () => { 64 | p.fromData(data); 65 | expect(p.substitutions.T_EST).to.equal('Test'); 66 | expect(p.substitutions.camelCase).to.equal('Test'); 67 | expect(p.substitutions.snake_case).to.equal('Test'); 68 | expect(p.customArgs.T_EST).to.equal('Test'); 69 | expect(p.customArgs.camelCase).to.equal('Test'); 70 | expect(p.customArgs.snake_case).to.equal('Test'); 71 | }); 72 | }); 73 | 74 | describe('#527', function() { 75 | it('shouldn\'t convert the headers to camel/snake case', function() { 76 | const p = new Personalization({ 77 | to: 'test@example.com', 78 | headers: { 79 | 'List-Unsubscribe': '', 80 | }, 81 | }); 82 | 83 | expect(p.headers['List-Unsubscribe']).to.equal(''); 84 | 85 | expect(p.toJSON().headers['List-Unsubscribe']).to 86 | .equal(''); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/reverse-merge-dynamic_teplate_data.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Reverse merge substitutions 21 | describe('deepMergeDynamicTemplateData()', function() { 22 | it('should reverse merge dynamicTemplateData', function() { 23 | p.setDynamicTemplateData({ test1: 'Test1' }); 24 | p.deepMergeDynamicTemplateData({ test2: 'Test2' }); 25 | expect(p.dynamicTemplateData).to.have.a.property('test1'); 26 | expect(p.dynamicTemplateData).to.have.a.property('test2'); 27 | expect(p.dynamicTemplateData.test1).to.equal('Test1'); 28 | expect(p.dynamicTemplateData.test2).to.equal('Test2'); 29 | }); 30 | it('should not overwrite existing keys', function() { 31 | p.setDynamicTemplateData({ test1: 'Test1' }); 32 | p.deepMergeDynamicTemplateData({ test1: 'Test3', test2: 'Test2' }); 33 | expect(p.dynamicTemplateData).to.have.a.property('test1'); 34 | expect(p.dynamicTemplateData).to.have.a.property('test2'); 35 | expect(p.dynamicTemplateData.test1).to.equal('Test1'); 36 | expect(p.dynamicTemplateData.test2).to.equal('Test2'); 37 | }); 38 | it('should work without prior dynamicTemplateData', function() { 39 | p.deepMergeDynamicTemplateData({ test2: 'Test2' }); 40 | expect(p.dynamicTemplateData).to.have.a.property('test2'); 41 | expect(p.dynamicTemplateData.test2).to.equal('Test2'); 42 | }); 43 | it('should throw an error for invalid input', function() { 44 | expect(function() { 45 | p.deepMergeDynamicTemplateData(3); 46 | }).to.throw(Error); 47 | }); 48 | it('should accept no input', function() { 49 | expect(function() { 50 | p.deepMergeDynamicTemplateData(); 51 | }).not.to.throw(Error); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/reverse-merge-substitutions.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Reverse merge substitutions 21 | describe('reverseMergeSubstitutions()', function() { 22 | it('should reverse merge substitutions', function() { 23 | p.setSubstitutions({test1: 'Test1'}); 24 | p.reverseMergeSubstitutions({test2: 'Test2'}); 25 | expect(p.substitutions).to.have.a.property('test1'); 26 | expect(p.substitutions).to.have.a.property('test2'); 27 | expect(p.substitutions.test1).to.equal('Test1'); 28 | expect(p.substitutions.test2).to.equal('Test2'); 29 | }); 30 | it('should not overwrite existing keys', function() { 31 | p.setSubstitutions({test1: 'Test1'}); 32 | p.reverseMergeSubstitutions({test1: 'Test3', test2: 'Test2'}); 33 | expect(p.substitutions).to.have.a.property('test1'); 34 | expect(p.substitutions).to.have.a.property('test2'); 35 | expect(p.substitutions.test1).to.equal('Test1'); 36 | expect(p.substitutions.test2).to.equal('Test2'); 37 | }); 38 | it('should work without prior substitutions', function() { 39 | p.reverseMergeSubstitutions({test2: 'Test2'}); 40 | expect(p.substitutions).to.have.a.property('test2'); 41 | expect(p.substitutions.test2).to.equal('Test2'); 42 | }); 43 | it('should throw an error for invalid input', function() { 44 | expect(function() { 45 | p.reverseMergeSubstitutions(3); 46 | }).to.throw(Error); 47 | }); 48 | it('should accept no input', function() { 49 | expect(function() { 50 | p.reverseMergeSubstitutions(); 51 | }).not.to.throw(Error); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/set-add-to.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Add to 21 | describe('addTo()', function() { 22 | it('should add the item', function() { 23 | p.addTo('test@example.org'); 24 | expect(p.to).to.be.an.instanceof(Array); 25 | expect(p.to).to.have.a.lengthOf(1); 26 | expect(p.to[0]).to.be.an.instanceof(EmailAddress); 27 | expect(p.to[0].email).to.equal('test@example.org'); 28 | }); 29 | it('should handle multiple values', function() { 30 | p.addTo('test1@example.org'); 31 | p.addTo('test2@example.org'); 32 | expect(p.to).to.be.an.instanceof(Array); 33 | expect(p.to).to.have.a.lengthOf(2); 34 | expect(p.to[0]).to.be.an.instanceof(EmailAddress); 35 | expect(p.to[0].email).to.equal('test1@example.org'); 36 | expect(p.to[1]).to.be.an.instanceof(EmailAddress); 37 | expect(p.to[1].email).to.equal('test2@example.org'); 38 | }); 39 | it('should accept no input', function() { 40 | expect(function() { 41 | p.addTo(); 42 | }).not.to.throw(Error); 43 | }); 44 | it('should not overwrite with no input', function() { 45 | p.addTo('test@example.org'); 46 | p.addTo(); 47 | expect(p.to).to.be.an.instanceof(Array); 48 | expect(p.to).to.have.a.lengthOf(1); 49 | expect(p.to[0]).to.be.an.instanceof(EmailAddress); 50 | expect(p.to[0].email).to.equal('test@example.org'); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/set-bcc.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Set bcc 21 | describe('setBcc()', function() { 22 | it('should handle array values', function() { 23 | p.setBcc(['test@example.org']); 24 | expect(p.bcc).to.be.an.instanceof(Array); 25 | expect(p.bcc).to.have.a.lengthOf(1); 26 | expect(p.bcc[0]).to.be.an.instanceof(EmailAddress); 27 | expect(p.bcc[0].email).to.equal('test@example.org'); 28 | }); 29 | it('should handle string values', function() { 30 | p.setBcc('test@example.org'); 31 | expect(p.bcc).to.be.an.instanceof(Array); 32 | expect(p.bcc).to.have.a.lengthOf(1); 33 | expect(p.bcc[0]).to.be.an.instanceof(EmailAddress); 34 | expect(p.bcc[0].email).to.equal('test@example.org'); 35 | }); 36 | it('should handle multiple values', function() { 37 | p.setBcc(['test1@example.org', 'test2@example.org']); 38 | expect(p.bcc).to.be.an.instanceof(Array); 39 | expect(p.bcc).to.have.a.lengthOf(2); 40 | expect(p.bcc[0]).to.be.an.instanceof(EmailAddress); 41 | expect(p.bcc[0].email).to.equal('test1@example.org'); 42 | expect(p.bcc[1]).to.be.an.instanceof(EmailAddress); 43 | expect(p.bcc[1].email).to.equal('test2@example.org'); 44 | }); 45 | it('should accept no input', function() { 46 | expect(function() { 47 | p.setBcc(); 48 | }).not.to.throw(Error); 49 | }); 50 | it('should not overwrite with no input', function() { 51 | p.setBcc('test@example.org'); 52 | p.setBcc(); 53 | expect(p.bcc[0].email).to.equal('test@example.org'); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/set-cc.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Set cc 21 | describe('setCc()', function() { 22 | it('should handle array values', function() { 23 | p.setCc(['test@example.org']); 24 | expect(p.cc).to.be.an.instanceof(Array); 25 | expect(p.cc).to.have.a.lengthOf(1); 26 | expect(p.cc[0]).to.be.an.instanceof(EmailAddress); 27 | expect(p.cc[0].email).to.equal('test@example.org'); 28 | }); 29 | it('should handle string values', function() { 30 | p.setCc('test@example.org'); 31 | expect(p.cc).to.be.an.instanceof(Array); 32 | expect(p.cc).to.have.a.lengthOf(1); 33 | expect(p.cc[0]).to.be.an.instanceof(EmailAddress); 34 | expect(p.cc[0].email).to.equal('test@example.org'); 35 | }); 36 | it('should handle multiple values', function() { 37 | p.setCc(['test1@example.org', 'test2@example.org']); 38 | expect(p.cc).to.be.an.instanceof(Array); 39 | expect(p.cc).to.have.a.lengthOf(2); 40 | expect(p.cc[0]).to.be.an.instanceof(EmailAddress); 41 | expect(p.cc[0].email).to.equal('test1@example.org'); 42 | expect(p.cc[1]).to.be.an.instanceof(EmailAddress); 43 | expect(p.cc[1].email).to.equal('test2@example.org'); 44 | }); 45 | it('should accept no input', function() { 46 | expect(function() { 47 | p.setCc(); 48 | }).not.to.throw(Error); 49 | }); 50 | it('should not overwrite with no input', function() { 51 | p.setCc('test@example.org'); 52 | p.setCc(); 53 | expect(p.cc[0].email).to.equal('test@example.org'); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/set-custom-args.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Set custom args 21 | describe('setCustomArgs()', function() { 22 | it('should set the given value', function() { 23 | p.setCustomArgs({test: 'Test'}); 24 | expect(p.customArgs).to.be.an.instanceof(Object); 25 | expect(p.customArgs).to.have.a.property('test'); 26 | expect(p.customArgs.test).to.equal('Test'); 27 | }); 28 | it('should throw an error for invalid input', function() { 29 | expect(function() { 30 | p.setCustomArgs('Invalid'); 31 | }).to.throw(Error); 32 | expect(function() { 33 | p.setCustomArgs(null); 34 | }).to.throw(Error); 35 | }); 36 | it('should accept no input', function() { 37 | expect(function() { 38 | p.setCustomArgs(); 39 | }).not.to.throw(Error); 40 | }); 41 | it('should not overwrite with no input', function() { 42 | p.setCustomArgs({test: 'Test'}); 43 | p.setCustomArgs(); 44 | expect(p.customArgs.test).to.equal('Test'); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/set-from.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Set from 21 | describe('setFrom()', function() { 22 | it('should accept string values', function() { 23 | p.setFrom('test@example.org'); 24 | expect(p.from).to.be.an.instanceof(EmailAddress); 25 | expect(p.from.email).to.equal('test@example.org'); 26 | }); 27 | it('should properly update from value', function() { 28 | p.setFrom('test1@example.com'); 29 | p.setFrom('test2@example.com'); 30 | p.setFrom('test3@example.com'); 31 | p.setFrom('test4@example.com'); 32 | expect(p.from.email).to.equal('test4@example.com'); 33 | }); 34 | it('should accept no input', function() { 35 | expect(function() { 36 | p.setFrom(); 37 | }).not.to.throw(Error); 38 | }); 39 | it('should not overwrite value with no input', function() { 40 | p.setFrom('test@example.org'); 41 | p.setFrom(); 42 | expect(p.from.email).to.equal('test@example.org'); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/set-headers.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Set headers 21 | describe('setHeaders()', function() { 22 | it('should set the given value', function() { 23 | p.setHeaders({test: 'Test'}); 24 | expect(p.headers).to.be.an.instanceof(Object); 25 | expect(p.headers).to.have.a.property('test'); 26 | expect(p.headers.test).to.equal('Test'); 27 | }); 28 | it('should throw an error for invalid input', function() { 29 | expect(function() { 30 | p.setHeaders('Invalid'); 31 | }).to.throw(Error); 32 | expect(function() { 33 | p.setHeaders(null); 34 | }).to.throw(Error); 35 | }); 36 | it('should accept no input', function() { 37 | expect(function() { 38 | p.setHeaders(); 39 | }).not.to.throw(Error); 40 | }); 41 | it('should not overwrite with no input', function() { 42 | p.setHeaders({test: 'Test'}); 43 | p.setHeaders(); 44 | expect(p.headers.test).to.equal('Test'); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/set-send-at.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Set send at 21 | describe('setSendAt()', function() { 22 | it('should set the given value', function() { 23 | p.setSendAt(1500077141); 24 | expect(p.sendAt).to.equal(1500077141); 25 | }); 26 | it('should throw an error for invalid input', function() { 27 | expect(function() { 28 | p.setSendAt('Invalid'); 29 | }).to.throw(Error); 30 | expect(function() { 31 | p.setSendAt(null); 32 | }).to.throw(Error); 33 | }); 34 | it('should accept no input', function() { 35 | expect(function() { 36 | p.setSendAt(); 37 | }).not.to.throw(Error); 38 | }); 39 | it('should not overwrite with no input', function() { 40 | p.setSendAt(1500077141); 41 | p.setSendAt(); 42 | expect(p.sendAt).to.equal(1500077141); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/set-subject.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Set subject 21 | describe('setSubject()', function() { 22 | it('should set the given value', function() { 23 | p.setSubject('Test'); 24 | expect(p.subject).to.equal('Test'); 25 | }); 26 | it('should throw an error for invalid input', function() { 27 | expect(function() { 28 | p.setSubject(5); 29 | }).to.throw(Error); 30 | expect(function() { 31 | p.setSubject(null); 32 | }).to.throw(Error); 33 | }); 34 | it('should accept no input', function() { 35 | expect(function() { 36 | p.setSubject(); 37 | }).not.to.throw(Error); 38 | }); 39 | it('should not overwrite with no input', function() { 40 | p.setSubject('Test'); 41 | p.setSubject(); 42 | expect(p.subject).to.equal('Test'); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/set-substitutions.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Set substitutions 21 | describe('setSubstitutions()', function() { 22 | it('should set the given value', function() { 23 | p.setSubstitutions({test: 'Test'}); 24 | expect(p.substitutions).to.be.an.instanceof(Object); 25 | expect(p.substitutions).to.have.a.property('test'); 26 | expect(p.substitutions.test).to.equal('Test'); 27 | }); 28 | it('should throw an error for invalid input', function() { 29 | expect(function() { 30 | p.setSubstitutions('Invalid'); 31 | }).to.throw(Error); 32 | expect(function() { 33 | p.setSubstitutions(3); 34 | }).to.throw(Error); 35 | }); 36 | it('should accept no input', function() { 37 | expect(function() { 38 | p.setSubstitutions(); 39 | }).not.to.throw(Error); 40 | }); 41 | it('should not overwrite with no input', function() { 42 | p.setSubstitutions({test: 'Test'}); 43 | p.setSubstitutions(); 44 | expect(p.substitutions.test).to.equal('Test'); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/set-to.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Set to 21 | describe('setTo()', function() { 22 | it('should handle array values', function() { 23 | p.setTo(['test@example.org']); 24 | expect(p.to).to.be.an.instanceof(Array); 25 | expect(p.to).to.have.a.lengthOf(1); 26 | expect(p.to[0]).to.be.an.instanceof(EmailAddress); 27 | expect(p.to[0].email).to.equal('test@example.org'); 28 | }); 29 | it('should handle string values', function() { 30 | p.setTo('test@example.org'); 31 | expect(p.to).to.be.an.instanceof(Array); 32 | expect(p.to).to.have.a.lengthOf(1); 33 | expect(p.to[0]).to.be.an.instanceof(EmailAddress); 34 | expect(p.to[0].email).to.equal('test@example.org'); 35 | }); 36 | it('should handle multiple values', function() { 37 | p.setTo(['test1@example.org', 'test2@example.org']); 38 | expect(p.to).to.be.an.instanceof(Array); 39 | expect(p.to).to.have.a.lengthOf(2); 40 | expect(p.to[0]).to.be.an.instanceof(EmailAddress); 41 | expect(p.to[0].email).to.equal('test1@example.org'); 42 | expect(p.to[1]).to.be.an.instanceof(EmailAddress); 43 | expect(p.to[1].email).to.equal('test2@example.org'); 44 | }); 45 | it('should accept no input', function() { 46 | expect(function() { 47 | p.setTo(); 48 | }).not.to.throw(Error); 49 | }); 50 | it('should not overwrite with no input', function() { 51 | p.setTo('test@example.org'); 52 | p.setTo(); 53 | expect(p.to[0].email).to.equal('test@example.org'); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/set_dynamic_template_data.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Set substitutions 21 | describe('setDynamicTemplateData()', function() { 22 | it('should set the given value', function() { 23 | p.setDynamicTemplateData({ test: 'Test' }); 24 | expect(p.dynamicTemplateData).to.be.an.instanceof(Object); 25 | expect(p.dynamicTemplateData).to.have.a.property('test'); 26 | expect(p.dynamicTemplateData.test).to.equal('Test'); 27 | }); 28 | it('should throw an error for invalid input', function() { 29 | expect(function() { 30 | p.setDynamicTemplateData('Invalid'); 31 | }).to.throw(Error); 32 | expect(function() { 33 | p.setDynamicTemplateData(3); 34 | }).to.throw(Error); 35 | }); 36 | it('should accept no input', function() { 37 | expect(function() { 38 | p.setDynamicTemplateData(); 39 | }).not.to.throw(Error); 40 | }); 41 | it('should not overwrite with no input', function() { 42 | p.setDynamicTemplateData({ test: 'Test' }); 43 | p.setDynamicTemplateData(); 44 | expect(p.dynamicTemplateData.test).to.equal('Test'); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/helpers/classes/personalization_specs/substitutions-wrappers.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const Personalization = require('../personalization'); 7 | const EmailAddress = require('../email-address'); 8 | 9 | /** 10 | * Tests 11 | */ 12 | describe('Personalization', function() { 13 | 14 | //Create new personalization before each test 15 | let p; 16 | beforeEach(function() { 17 | p = new Personalization(); 18 | }); 19 | 20 | //Set substitutions wrappers 21 | describe('setSubstitutionWrappers()', function() { 22 | it('should have defaults', function() { 23 | expect(p.substitutionWrappers).to.be.an.instanceof(Array); 24 | expect(p.substitutionWrappers).to.have.a.lengthOf(2); 25 | expect(p.substitutionWrappers[0]).to.equal('{{'); 26 | expect(p.substitutionWrappers[1]).to.equal('}}'); 27 | }); 28 | it('should set the given value', function() { 29 | p.setSubstitutionWrappers(['a', 'b']); 30 | expect(p.substitutionWrappers).to.be.an.instanceof(Array); 31 | expect(p.substitutionWrappers).to.have.a.lengthOf(2); 32 | expect(p.substitutionWrappers[0]).to.equal('a'); 33 | expect(p.substitutionWrappers[1]).to.equal('b'); 34 | }); 35 | it('should throw an error for invalid input', function() { 36 | expect(function() { 37 | p.setSubstitutionWrappers('Invalid'); 38 | }).to.throw(Error); 39 | expect(function() { 40 | p.setSubstitutionWrappers(['a']); 41 | }).to.throw(Error); 42 | expect(function() { 43 | p.setSubstitutionWrappers(['a', 'b', 'c']); 44 | }).to.throw(Error); 45 | }); 46 | it('should accept no input', function() { 47 | expect(function() { 48 | p.setSubstitutionWrappers(); 49 | }).not.to.throw(Error); 50 | }); 51 | it('should not overwrite with no input', function() { 52 | p.setSubstitutionWrappers(['a', 'b']); 53 | p.setSubstitutionWrappers(); 54 | expect(p.substitutionWrappers[0]).to.equal('a'); 55 | expect(p.substitutionWrappers[1]).to.equal('b'); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/helpers/classes/request.d.ts: -------------------------------------------------------------------------------- 1 | import * as https from 'https'; 2 | 3 | type HttpMethod = 'get'|'GET'|'post'|'POST'|'put'|'PUT'|'patch'|'PATCH'|'delete'|'DELETE'; 4 | 5 | export default interface RequestOptions { 6 | url: string; 7 | method?: HttpMethod; 8 | baseUrl?: string; 9 | qs?: TParams; 10 | body?: TData; 11 | headers?: object; 12 | httpsAgent?: https.Agent; 13 | } 14 | -------------------------------------------------------------------------------- /packages/helpers/classes/response-error.d.ts: -------------------------------------------------------------------------------- 1 | export default class ResponseError extends Error { 2 | code: number; 3 | message: string; 4 | response: { 5 | headers: { [key: string]: string; }; 6 | body: string; 7 | }; 8 | } -------------------------------------------------------------------------------- /packages/helpers/classes/response-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Response error class 5 | */ 6 | class ResponseError extends Error { 7 | 8 | /** 9 | * Constructor 10 | */ 11 | constructor(response) { 12 | 13 | //Super 14 | super(); 15 | 16 | //Extract data from response 17 | const { headers, status, statusText, data } = response; 18 | 19 | //Set data 20 | this.code = status; 21 | this.message = statusText; 22 | this.response = { headers, body: data }; 23 | 24 | //Capture stack trace 25 | if (!this.stack) { 26 | Error.captureStackTrace(this, this.constructor); 27 | } 28 | 29 | //Clean up stack trace 30 | const regex = new RegExp(process.cwd() + '/', 'gi'); 31 | this.stack = this.stack.replace(regex, ''); 32 | } 33 | 34 | /** 35 | * Convert to string 36 | */ 37 | toString() { 38 | const { body } = this.response; 39 | let err = `${this.message} (${this.code})`; 40 | if (body && Array.isArray(body.errors)) { 41 | body.errors.forEach(error => { 42 | const message = error.message; 43 | const field = error.field; 44 | const help = error.help; 45 | err += `\n ${message}\n ${field}\n ${help}`; 46 | }); 47 | } 48 | return err; 49 | } 50 | 51 | /** 52 | * Convert to simple object for JSON responses 53 | */ 54 | toJSON() { 55 | const { message, code, response } = this; 56 | return { message, code, response }; 57 | } 58 | } 59 | 60 | //Export 61 | module.exports = ResponseError; 62 | -------------------------------------------------------------------------------- /packages/helpers/classes/response.d.ts: -------------------------------------------------------------------------------- 1 | export default class Response { 2 | statusCode: number; 3 | body: TPayload; 4 | headers: any; 5 | constructor(statusCode: number, body: TPayload, headers?: any); 6 | toString(): string; 7 | } 8 | -------------------------------------------------------------------------------- /packages/helpers/classes/response.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Response { 4 | constructor(statusCode, body, headers) { 5 | this.statusCode = statusCode; 6 | this.body = body; 7 | this.headers = headers; 8 | } 9 | 10 | toString() { 11 | return 'HTTP ' + this.statusCode + ' ' + this.body; 12 | } 13 | } 14 | 15 | module.exports = Response; 16 | -------------------------------------------------------------------------------- /packages/helpers/classes/statistics.d.ts: -------------------------------------------------------------------------------- 1 | export class Stats { 2 | startDate: Date; 3 | endDate?: Date; 4 | aggregatedBy?: string; 5 | } 6 | 7 | export default class Statistics { 8 | constructor(data?: Stats); 9 | 10 | fromData(data: Stats): void; 11 | 12 | /** 13 | * To JSON 14 | */ 15 | toJSON(): Stats; 16 | 17 | /** 18 | * Get Advanced Statistics 19 | */ 20 | getAdvanced(); 21 | 22 | /** 23 | * Get Category Statistics 24 | */ 25 | getCategory(); 26 | 27 | /** 28 | * Get Global Statistics 29 | */ 30 | getGlobal(); 31 | 32 | /** 33 | * Get Parse Statistics 34 | */ 35 | getParse(); 36 | 37 | /** 38 | * Get Subuser Statistics 39 | */ 40 | getSubuser(); 41 | 42 | /** 43 | * Set StartDate 44 | */ 45 | setStartDate(startDate: Date): void; 46 | 47 | /** 48 | * Set EndDate 49 | */ 50 | setEndDate(endDate: Date): void; 51 | 52 | /** 53 | * Set AggregatedBy 54 | */ 55 | setAggregatedBy(aggregatedBy: string): void; 56 | } -------------------------------------------------------------------------------- /packages/helpers/constants/index.js: -------------------------------------------------------------------------------- 1 | const DYNAMIC_TEMPLATE_CHAR_WARNING = ` 2 | Content with characters ', " or & may need to be escaped with three brackets 3 | {{{ content }}} 4 | See https://sendgrid.com/docs/for-developers/sending-email/using-handlebars/ for more information.`; 5 | 6 | module.exports = { 7 | DYNAMIC_TEMPLATE_CHAR_WARNING, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/helpers/helpers/array-to-json.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper to convert an array of objects to JSON 3 | */ 4 | declare function arrayToJSON(arr: any[]): any[]; 5 | 6 | export = arrayToJSON; -------------------------------------------------------------------------------- /packages/helpers/helpers/array-to-json.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Helper to convert an array of objects to JSON 5 | */ 6 | module.exports = function arrayToJSON(arr) { 7 | return arr.map(item => { 8 | if (typeof item === 'object' && item !== null && typeof item.toJSON === 'function') { 9 | return item.toJSON(); 10 | } 11 | return item; 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/helpers/helpers/array-to-json.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const arrayToJSON = require('./array-to-json'); 7 | 8 | /** 9 | * Tests 10 | */ 11 | describe('arrayToJSON', function() { 12 | 13 | //Test object with toJSON function 14 | const obj1 = { 15 | toJSON() { 16 | return {a: 1, b: 2}; 17 | }, 18 | }; 19 | 20 | //Test plain object 21 | const obj2 = {c: 3, d: 4}; 22 | 23 | //Create mixed array 24 | const test = [obj1, obj2, null, obj2, obj1, 2, 'test']; 25 | const json = arrayToJSON(test); 26 | 27 | //Tests 28 | it('should leave non object values as is', function() { 29 | expect(json[2]).to.be.null(); 30 | expect(json[5]).to.equal(2); 31 | expect(json[6]).to.equal('test'); 32 | }); 33 | it('should leave plain objects as they are', function() { 34 | expect(json[1]).to.have.property('c'); 35 | expect(json[3]).to.have.property('d'); 36 | }); 37 | it('should use the toJSON() handler if specified', function() { 38 | expect(json[0]).to.have.property('a'); 39 | expect(json[4]).to.have.property('b'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/helpers/helpers/convert-keys.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper to convert an object's keys 3 | */ 4 | declare function convertKeys(obj: T, converter: (key: string) => string, ignored?: string[]): S; 5 | 6 | export = convertKeys; -------------------------------------------------------------------------------- /packages/helpers/helpers/convert-keys.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Helper to convert an object's keys 5 | */ 6 | module.exports = function convertKeys(obj, converter, ignored) { 7 | 8 | //Validate 9 | if (typeof obj !== 'object' || obj === null) { 10 | throw new Error('Non object passed to convertKeys: ' + obj); 11 | } 12 | 13 | //Ignore arrays 14 | if (Array.isArray(obj)) { 15 | return obj; 16 | } 17 | 18 | //Ensure array for ignored values 19 | if (!Array.isArray(ignored)) { 20 | ignored = []; 21 | } 22 | 23 | //Process all properties 24 | for (const key in obj) { 25 | //istanbul ignore else 26 | if (obj.hasOwnProperty(key)) { 27 | 28 | //Convert key to snake case 29 | const converted = converter(key); 30 | 31 | //Recursive for child objects, unless ignored 32 | //The ignored check checks both variants of the key 33 | if (typeof obj[key] === 'object' && obj[key] !== null) { 34 | if (!ignored.includes(key) && !ignored.includes(converted)) { 35 | obj[key] = convertKeys(obj[key], converter, ignored); 36 | } 37 | } 38 | 39 | //Convert key to snake case and set if needed 40 | if (converted !== key) { 41 | obj[converted] = obj[key]; 42 | delete obj[key]; 43 | } 44 | } 45 | } 46 | 47 | //Return object 48 | return obj; 49 | }; 50 | -------------------------------------------------------------------------------- /packages/helpers/helpers/convert-keys.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const convertKeys = require('./convert-keys'); 7 | const deepClone = require('./deep-clone'); 8 | const strToCamelCase = require('./str-to-camel-case'); 9 | 10 | /** 11 | * Tests 12 | */ 13 | describe('convertKeys', function() { 14 | 15 | //Test object 16 | const obj = { 17 | a: 1, 18 | snake_case: 2, 19 | camelCase: 3, 20 | nested_snake_case: { 21 | a: 1, 22 | snake_case: 2, 23 | camelCase: 3, 24 | }, 25 | nestedCamelCase: { 26 | a: 1, 27 | snake_case: 2, 28 | camelCase: 3, 29 | }, 30 | arr: ['a', 'b'], 31 | }; 32 | 33 | //Create copy of the object 34 | const objCopy = deepClone(obj); 35 | 36 | //Convert keys 37 | convertKeys(obj, strToCamelCase); 38 | 39 | //Tests 40 | it('should convert top level keys properly', function() { 41 | expect(obj).to.have.property('a'); 42 | expect(obj).to.have.property('snakeCase'); 43 | expect(obj).to.have.property('camelCase'); 44 | expect(obj).to.have.property('nestedSnakeCase'); 45 | expect(obj).to.have.property('nestedCamelCase'); 46 | expect(obj).not.to.have.property('snake_case'); 47 | expect(obj).not.to.have.property('nested_snake_case'); 48 | }); 49 | it('should convert nested keys properly', function() { 50 | expect(obj.nestedSnakeCase).to.have.property('a'); 51 | expect(obj.nestedSnakeCase).to.have.property('snakeCase'); 52 | expect(obj.nestedSnakeCase).to.have.property('camelCase'); 53 | expect(obj.nestedSnakeCase).not.to.have.property('snake_case'); 54 | expect(obj.nestedCamelCase).to.have.property('a'); 55 | expect(obj.nestedCamelCase).to.have.property('snakeCase'); 56 | expect(obj.nestedCamelCase).to.have.property('camelCase'); 57 | expect(obj.nestedCamelCase).not.to.have.property('snake_case'); 58 | }); 59 | it('should handle arrays properly', function() { 60 | expect(obj.arr).to.be.an.instanceof(Array); 61 | expect(obj.arr).to.have.lengthOf(2); 62 | expect(obj.arr).to.have.members(['a', 'b']); 63 | }); 64 | it('should not converted nested objects if ignored', function() { 65 | convertKeys(objCopy, strToCamelCase, ['nestedSnakeCase']); 66 | expect(objCopy.nestedCamelCase).to.have.property('a'); 67 | expect(objCopy.nestedCamelCase).to.have.property('snakeCase'); 68 | expect(objCopy.nestedCamelCase).to.have.property('camelCase'); 69 | expect(objCopy.nestedCamelCase).not.to.have.property('snake_case'); 70 | expect(objCopy.nestedSnakeCase).to.have.property('a'); 71 | expect(objCopy.nestedSnakeCase).to.have.property('camelCase'); 72 | expect(objCopy.nestedSnakeCase).to.have.property('snake_case'); 73 | expect(objCopy.nestedSnakeCase).not.to.have.property('snakeCase'); 74 | }); 75 | it('should throw an error for non object input', function() { 76 | expect(function() { 77 | convertKeys(null); 78 | }).to.throw(Error); 79 | expect(function() { 80 | convertKeys(5); 81 | }).to.throw(Error); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /packages/helpers/helpers/deep-clone.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Deep cloning helper for objects 3 | */ 4 | declare function deepClone(obj: T): T; 5 | 6 | export = deepClone; -------------------------------------------------------------------------------- /packages/helpers/helpers/deep-clone.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Deep cloning helper for objects 5 | */ 6 | module.exports = function deepClone(obj) { 7 | return JSON.parse(JSON.stringify(obj)); 8 | }; 9 | -------------------------------------------------------------------------------- /packages/helpers/helpers/deep-clone.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const deepClone = require('./deep-clone'); 7 | 8 | /** 9 | * Tests 10 | */ 11 | describe('deepClone', function() { 12 | 13 | //Test object 14 | const obj = { 15 | nested: {a: 1, b: 2, c: {d: 4}}, 16 | e: 5, 17 | arr: ['a', 'b'], 18 | }; 19 | 20 | //Create clone 21 | const clone = deepClone(obj); 22 | 23 | //Tests 24 | it('should equal the objects to themselves', function() { 25 | expect(obj).to.equal(obj); 26 | expect(clone).to.equal(clone); 27 | }); 28 | it('should make a copy of the object', function() { 29 | expect(obj).to.not.equal(clone); 30 | expect(obj).to.deep.equal(clone); 31 | }); 32 | it('should make a copy of nested objects', function() { 33 | expect(obj.nested).to.not.equal(clone.nested); 34 | expect(obj.nested).to.deep.equal(clone.nested); 35 | expect(obj.nested.c).to.not.equal(clone.nested.c); 36 | expect(obj.nested.c).to.deep.equal(clone.nested.c); 37 | }); 38 | it('should handle arrays properly', function() { 39 | expect(clone.arr).to.be.an.instanceof(Array); 40 | expect(clone.arr).to.have.lengthOf(2); 41 | expect(clone.arr).to.have.members(['a', 'b']); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/helpers/helpers/html-to-plain-text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Helper to convert an HTML string to a plain text string 5 | */ 6 | module.exports = function convertHTML2PlainString(html) { 7 | let text = html.replace(/(<([^>]+)>)/g, ''); 8 | text = text.replace(/\s+/g, ' '); 9 | return text; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/helpers/helpers/html-to-plain-text.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const convertHTML2PlainString = require('./html-to-plain-text'); 7 | 8 | /** 9 | * Tests 10 | */ 11 | describe('convertHTML2PlainString', function() { 12 | 13 | //Test string with one html tag 14 | const html1 = '

Hello world

'; 15 | 16 | //Test string with nested html tags 17 | const html2 = '

Hello World!

'; 18 | 19 | //Test string with html tag with attributes 20 | const html3 = '
Hello World!
'; 21 | 22 | //Tests 23 | it('should strip out html tags', function() { 24 | expect(convertHTML2PlainString(html1)).to.be.equal('Hello world'); 25 | }); 26 | it('should strip out nested html tags', function() { 27 | expect(convertHTML2PlainString(html2)).to.be.equal('Hello World!'); 28 | }); 29 | it('should strip out html tags with attributes', function() { 30 | expect(convertHTML2PlainString(html3)).to.be.equal('Hello World!'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/helpers/helpers/index.d.ts: -------------------------------------------------------------------------------- 1 | import arrayToJSON = require("@sendgrid/helpers/helpers/array-to-json"); 2 | import convertKeys = require("@sendgrid/helpers/helpers/convert-keys"); 3 | import deepClone = require("@sendgrid/helpers/helpers/deep-clone"); 4 | import mergeData = require("@sendgrid/helpers/helpers/merge-data"); 5 | import splitNameEmail = require("@sendgrid/helpers/helpers/split-name-email"); 6 | import toCamelCase = require("@sendgrid/helpers/helpers/to-camel-case"); 7 | import toSnakeCase = require("@sendgrid/helpers/helpers/to-snake-case"); 8 | import wrapSubstitutions = require("@sendgrid/helpers/helpers/wrap-substitutions"); 9 | 10 | export { 11 | arrayToJSON, 12 | convertKeys, 13 | deepClone, 14 | mergeData, 15 | splitNameEmail, 16 | toCamelCase, 17 | toSnakeCase, 18 | wrapSubstitutions, 19 | } -------------------------------------------------------------------------------- /packages/helpers/helpers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Expose helpers 5 | */ 6 | const arrayToJSON = require('./array-to-json'); 7 | const convertKeys = require('./convert-keys'); 8 | const deepClone = require('./deep-clone'); 9 | const mergeData = require('./merge-data'); 10 | const splitNameEmail = require('./split-name-email'); 11 | const toCamelCase = require('./to-camel-case'); 12 | const toSnakeCase = require('./to-snake-case'); 13 | const wrapSubstitutions = require('./wrap-substitutions'); 14 | 15 | /** 16 | * Export 17 | */ 18 | module.exports = { 19 | arrayToJSON, 20 | convertKeys, 21 | deepClone, 22 | mergeData, 23 | splitNameEmail, 24 | toCamelCase, 25 | toSnakeCase, 26 | wrapSubstitutions, 27 | }; 28 | -------------------------------------------------------------------------------- /packages/helpers/helpers/merge-data-deep.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Merge data deep helper 3 | */ 4 | declare function mergeDataDeep(base: T, data: S): T & S; 5 | 6 | export = mergeDataDeep; -------------------------------------------------------------------------------- /packages/helpers/helpers/merge-data-deep.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Merge data deep helper 5 | */ 6 | 7 | function isObject(item) { 8 | return (item && typeof item === 'object' && !Array.isArray(item)); 9 | } 10 | 11 | module.exports = function mergeDeep(base, data) { 12 | //Validate data 13 | if (typeof base !== 'object' || base === null) { 14 | throw new Error('Not an object provided for base'); 15 | } 16 | if (typeof data !== 'object' || data === null) { 17 | throw new Error('Not an object provided for data'); 18 | } 19 | let output = Object.assign({}, base); 20 | if (isObject(base) && isObject(data)) { 21 | Object.keys(data).forEach(key => { 22 | if (isObject(data[key])) { 23 | if (!(key in base)) { 24 | Object.assign(output, { [key]: data[key] }); 25 | } else { 26 | output[key] = mergeDeep(base[key], data[key]); 27 | } 28 | } else { 29 | Object.assign(output, { [key]: data[key] }); 30 | } 31 | }); 32 | } 33 | return output; 34 | }; 35 | -------------------------------------------------------------------------------- /packages/helpers/helpers/merge-data-deep.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const mergeDataDeep = require('./merge-data-deep'); 7 | 8 | /** 9 | * Tests 10 | */ 11 | describe('mergeDataDeep', function() { 12 | 13 | //Test objects 14 | const obj1 = { 15 | a: 1, 16 | b: 2, 17 | e: { g: 9 }, 18 | arr: ['a', 'b'], 19 | }; 20 | const obj2 = { 21 | a: 3, 22 | c: 3, 23 | d: 4, 24 | e: { f: 6 }, 25 | arr: ['c'], 26 | }; 27 | 28 | //Merge 29 | const merged = mergeDataDeep(obj1, obj2); 30 | 31 | //Tests 32 | it('should merge the two objects', function() { 33 | expect(merged).to.have.property('a'); 34 | expect(merged).to.have.property('b'); 35 | expect(merged).to.have.property('c'); 36 | expect(merged).to.have.property('d'); 37 | expect(merged).to.have.property('e'); 38 | expect(merged.a).to.equal(3); 39 | expect(merged.e).to.have.property('f'); 40 | expect(merged.e).to.have.property('g'); 41 | }); 42 | it('should throw on invalid input', function() { 43 | expect(function() { 44 | mergeDataDeep(null, obj2); 45 | }).to.throw(Error); 46 | expect(function() { 47 | mergeDataDeep(obj1, 4); 48 | }).to.throw(Error); 49 | }); 50 | it('should overwrite arrays', function() { 51 | expect(merged).to.have.property('arr'); 52 | expect(merged.arr).to.be.an.instanceof(Array); 53 | expect(merged.arr).to.have.lengthOf(1); 54 | expect(merged.arr[0]).to.equal('c'); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/helpers/helpers/merge-data.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Merge data helper 3 | */ 4 | declare function mergeData(base: T, data: S): T&S; 5 | 6 | export = mergeData; -------------------------------------------------------------------------------- /packages/helpers/helpers/merge-data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Merge data helper 5 | */ 6 | module.exports = function mergeData(base, data) { 7 | 8 | //Validate data 9 | if (typeof base !== 'object' || base === null) { 10 | throw new Error('Not an object provided for base'); 11 | } 12 | if (typeof data !== 'object' || data === null) { 13 | throw new Error('Not an object provided for data'); 14 | } 15 | 16 | //Copy base 17 | const merged = Object.assign({}, base); 18 | 19 | //Add data 20 | for (const key in data) { 21 | //istanbul ignore else 22 | if (data.hasOwnProperty(key)) { 23 | if (data[key] && Array.isArray(data[key])) { 24 | merged[key] = data[key]; 25 | } else if (data[key] && typeof data[key] === 'object') { 26 | merged[key] = Object.assign({}, data[key]); 27 | } else if (data[key]) { 28 | merged[key] = data[key]; 29 | } 30 | } 31 | } 32 | 33 | //Return 34 | return merged; 35 | }; 36 | -------------------------------------------------------------------------------- /packages/helpers/helpers/merge-data.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const mergeData = require('./merge-data'); 7 | 8 | /** 9 | * Tests 10 | */ 11 | describe('mergeData', function() { 12 | 13 | //Test objects 14 | const obj1 = { 15 | a: 1, 16 | b: 2, 17 | arr: ['a', 'b'], 18 | }; 19 | const obj2 = { 20 | c: 3, 21 | d: 4, 22 | e: {f: 6}, 23 | arr: ['c'], 24 | }; 25 | 26 | //Merge 27 | const merged = mergeData(obj1, obj2); 28 | 29 | //Tests 30 | it('should merge the two objects', function() { 31 | expect(merged).to.have.property('a'); 32 | expect(merged).to.have.property('b'); 33 | expect(merged).to.have.property('c'); 34 | expect(merged).to.have.property('d'); 35 | expect(merged).to.have.property('e'); 36 | expect(merged.e).to.have.property('f'); 37 | }); 38 | it('should throw on invalid input', function() { 39 | expect(function() { 40 | mergeData(null, obj2); 41 | }).to.throw(Error); 42 | expect(function() { 43 | mergeData(obj1, 4); 44 | }).to.throw(Error); 45 | }); 46 | it('should overwrite arrays', function() { 47 | expect(merged).to.have.property('arr'); 48 | expect(merged.arr).to.be.an.instanceof(Array); 49 | expect(merged.arr).to.have.lengthOf(1); 50 | expect(merged.arr[0]).to.equal('c'); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/helpers/helpers/split-name-email.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Split name and email address from string 3 | */ 4 | declare function splitNameEmail(str: string): string[]; 5 | 6 | export = splitNameEmail; -------------------------------------------------------------------------------- /packages/helpers/helpers/split-name-email.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Split name and email address from string 5 | */ 6 | module.exports = function splitNameEmail(str) { 7 | 8 | //If no email bracket present, return as is 9 | if (str.indexOf('<') === -1) { 10 | return ['', str]; 11 | } 12 | 13 | //Split into name and email 14 | let [name, email] = str.split('<'); 15 | 16 | //Trim and fix up 17 | name = name.trim(); 18 | email = email.replace('>', '').trim(); 19 | 20 | //Return as array 21 | return [name, email]; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/helpers/helpers/split-name-email.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const splitNameEmail = require('./split-name-email'); 7 | 8 | /** 9 | * Tests 10 | */ 11 | describe('splitNameEmail', function() { 12 | it('should not split strings without < symbol', function() { 13 | const [name, email] = splitNameEmail('test@test.com'); 14 | expect(name).to.equal(''); 15 | expect(email).to.equal('test@test.com'); 16 | }); 17 | it('should split strings with < symbol', function() { 18 | const [name, email] = splitNameEmail('Tester '); 19 | expect(name).to.equal('Tester'); 20 | expect(email).to.equal('test@test.com'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/helpers/helpers/str-to-camel-case.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Internal conversion helper 5 | */ 6 | module.exports = function strToCamelCase(str) { 7 | if (typeof str !== 'string') { 8 | throw new Error('String expected for conversion to snake case'); 9 | } 10 | return str 11 | .trim() 12 | .replace(/_+|\-+/g, ' ') 13 | .replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) { 14 | if (Number(match) === 0) { 15 | return ''; 16 | } 17 | return (index === 0) ? match.toLowerCase() : match.toUpperCase(); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/helpers/helpers/str-to-camel-case.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const toCamelCase = require('./str-to-camel-case'); 7 | 8 | /** 9 | * Tests 10 | */ 11 | describe('toCamelCase', function() { 12 | it('should camel case an already camel cased string', function() { 13 | expect(toCamelCase('camelCase')).to.equal('camelCase'); 14 | }); 15 | it('should camel case a snake cased string', function() { 16 | expect(toCamelCase('camel_case')).to.equal('camelCase'); 17 | }); 18 | it('should camel case a dasherized string', function() { 19 | expect(toCamelCase('camel-case')).to.equal('camelCase'); 20 | }); 21 | it('should camel case a string with spaces', function() { 22 | expect(toCamelCase('camel case')).to.equal('camelCase'); 23 | }); 24 | it('should camel case a string with multiple spaces', function() { 25 | expect(toCamelCase('camel case')).to.equal('camelCase'); 26 | expect(toCamelCase('camel ca se')).to.equal('camelCaSe'); 27 | }); 28 | it('should camel case a mixed string', function() { 29 | expect(toCamelCase('CamelCase With snake_case _and dash-erized -andCamel')) 30 | .to.equal('camelCaseWithSnakeCaseAndDashErizedAndCamel'); 31 | expect(toCamelCase('camel_case With vari-ety andCamel')) 32 | .to.equal('camelCaseWithVariEtyAndCamel'); 33 | }); 34 | it('should lowercase single letters', function() { 35 | expect(toCamelCase('A')).to.equal('a'); 36 | expect(toCamelCase('F')).to.equal('f'); 37 | expect(toCamelCase('Z')).to.equal('z'); 38 | }); 39 | it('should trim and camel case properly with leading/trailing spaces', function() { 40 | expect(toCamelCase(' test_me ')).to.equal('testMe'); 41 | expect(toCamelCase(' test_me')).to.equal('testMe'); 42 | expect(toCamelCase('test_me ')).to.equal('testMe'); 43 | expect(toCamelCase(' test_me ')).to.equal('testMe'); 44 | }); 45 | it('should throw an error for non string input', function() { 46 | expect(function() { 47 | toCamelCase(2); 48 | }).to.throw(Error); 49 | expect(function() { 50 | toCamelCase(null); 51 | }).to.throw(Error); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/helpers/helpers/str-to-snake-case.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Internal conversion helper 5 | */ 6 | module.exports = function strToSnakeCase(str) { 7 | if (typeof str !== 'string') { 8 | throw new Error('String expected for conversion to snake case'); 9 | } 10 | return str.trim().replace(/(\s*\-*\b\w|[A-Z])/g, function($1) { 11 | $1 = $1.trim().toLowerCase().replace('-', ''); 12 | return ($1[0] === '_' ? '' : '_') + $1; 13 | }).slice(1); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/helpers/helpers/str-to-snake-case.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const toSnakeCase = require('./str-to-snake-case'); 7 | 8 | /** 9 | * Tests 10 | */ 11 | describe('toSnakeCase', function() { 12 | it('should snake case an already snake cased string', function() { 13 | expect(toSnakeCase('snake_case')).to.equal('snake_case'); 14 | }); 15 | it('should snake case a camel cased string', function() { 16 | expect(toSnakeCase('snakeCase')).to.equal('snake_case'); 17 | expect(toSnakeCase('SnakeCase')).to.equal('snake_case'); 18 | expect(toSnakeCase('SnAkeCASe')).to.equal('sn_ake_c_a_se'); 19 | }); 20 | it('should snake case a dasherized string', function() { 21 | expect(toSnakeCase('snake-case')).to.equal('snake_case'); 22 | expect(toSnakeCase('Snake-Case')).to.equal('snake_case'); 23 | }); 24 | it('should snake case a string with spaces', function() { 25 | expect(toSnakeCase('Snake Case')).to.equal('snake_case'); 26 | }); 27 | it('should snake case a string with multiple spaces', function() { 28 | expect(toSnakeCase('Snake Case')).to.equal('snake_case'); 29 | expect(toSnakeCase('Snake Ca se')).to.equal('snake_ca_se'); 30 | }); 31 | it('should snake case a mixed string', function() { 32 | expect(toSnakeCase('Snake-Case mixEd Stri_ng te-st')) 33 | .to.equal('snake_case_mix_ed_stri_ng_te_st'); 34 | expect(toSnakeCase('CamelCase With snake_case _and dash-erized -andCamel')) 35 | .to.equal('camel_case_with_snake_case_and_dash_erized_and_camel'); 36 | }); 37 | it('should lowercase single letters', function() { 38 | expect(toSnakeCase('A')).to.equal('a'); 39 | expect(toSnakeCase('F')).to.equal('f'); 40 | expect(toSnakeCase('Z')).to.equal('z'); 41 | }); 42 | it('should trim and snake case properly with leading/trailing spaces', function() { 43 | expect(toSnakeCase(' TestMe ')).to.equal('test_me'); 44 | expect(toSnakeCase(' TestMe')).to.equal('test_me'); 45 | expect(toSnakeCase('TestMe ')).to.equal('test_me'); 46 | expect(toSnakeCase(' TestMe ')).to.equal('test_me'); 47 | }); 48 | it('should throw an error for non string input', function() { 49 | expect(function() { 50 | toSnakeCase(2); 51 | }).to.throw(Error); 52 | expect(function() { 53 | toSnakeCase(null); 54 | }).to.throw(Error); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/helpers/helpers/to-camel-case.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert object keys to camel case 3 | */ 4 | declare function toCamelCase(obj: T, ignored?: string[]): S; 5 | 6 | export = toCamelCase; -------------------------------------------------------------------------------- /packages/helpers/helpers/to-camel-case.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const convertKeys = require('./convert-keys'); 7 | const strToCamelCase = require('./str-to-camel-case'); 8 | 9 | /** 10 | * Convert object keys to camel case 11 | */ 12 | module.exports = function toCamelCase(obj, ignored) { 13 | return convertKeys(obj, strToCamelCase, ignored); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/helpers/helpers/to-snake-case.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert object keys to snake case 3 | */ 4 | declare function toSnakeCase(obj: T, ignored?: string[]): S; 5 | 6 | export = toSnakeCase; -------------------------------------------------------------------------------- /packages/helpers/helpers/to-snake-case.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const convertKeys = require('./convert-keys'); 7 | const strToSnakeCase = require('./str-to-snake-case'); 8 | 9 | /** 10 | * Convert object keys to snake case 11 | */ 12 | module.exports = function toSnakeCase(obj, ignored) { 13 | return convertKeys(obj, strToSnakeCase, ignored); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/helpers/helpers/validate-settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const validate = (parent, parentName, childName, childType) => { 4 | if (typeof parent === 'undefined' || typeof parent[childName] === 'undefined') { 5 | return; 6 | } 7 | if (typeof parent[childName] !== childType) { 8 | throw new Error(`${childType} expected for \`${parentName}.${childName}\``) 9 | } 10 | }; 11 | 12 | module.exports = { 13 | validateMailSettings(settings) { 14 | if (typeof settings !== 'object') { 15 | throw new Error('Object expected for `mailSettings`'); 16 | } 17 | const { 18 | bcc, 19 | bypassListManagement, 20 | bypassSpamManagement, 21 | bypassBounceManagement, 22 | bypassUnsubscribeManagement, 23 | footer, 24 | sandboxMode, 25 | spamCheck, 26 | } = settings; 27 | validate(bcc, 'bcc', 'enable', 'boolean'); 28 | validate(bcc, 'bcc', 'email', 'string'); 29 | validate(bypassListManagement, 'bypassListManagement', 'enable', 'boolean'); 30 | validate(bypassSpamManagement, 'bypassSpamManagement', 'enable', 'boolean'); 31 | validate(bypassBounceManagement, 'bypassBounceManagement', 'enable', 'boolean'); 32 | validate(bypassUnsubscribeManagement, 'bypassUnsubscribeManagement', 'enable', 'boolean'); 33 | validate(footer, 'footer', 'enable', 'boolean'); 34 | validate(footer, 'footer', 'text', 'string'); 35 | validate(footer, 'footer', 'html', 'string'); 36 | validate(sandboxMode, 'sandboxMode', 'enable', 'boolean'); 37 | validate(spamCheck, 'spamCheck', 'enable', 'boolean'); 38 | validate(spamCheck, 'spamCheck', 'threshold', 'number'); 39 | validate(spamCheck, 'spamCheck', 'postToUrl', 'string'); 40 | }, 41 | 42 | validateTrackingSettings(settings) { 43 | if (typeof settings !== 'object') { 44 | throw new Error('Object expected for `trackingSettings`'); 45 | } 46 | const { 47 | clickTracking, 48 | openTracking, 49 | subscriptionTracking, 50 | ganalytics, 51 | } = settings; 52 | validate(clickTracking, 'clickTracking', 'enable', 'boolean'); 53 | validate(clickTracking, 'clickTracking', 'enableText', 'boolean'); 54 | validate(openTracking, 'openTracking', 'enable', 'boolean'); 55 | validate(openTracking, 'openTracking', 'substitutionTag', 'string'); 56 | validate(subscriptionTracking, 'subscriptionTracking', 'enable', 'boolean'); 57 | validate(subscriptionTracking, 'subscriptionTracking', 'text', 'string'); 58 | validate(subscriptionTracking, 'subscriptionTracking', 'html', 'string'); 59 | validate(subscriptionTracking, 'subscriptionTracking', 'substitutionTag', 'string'); 60 | validate(ganalytics, 'ganalytics', 'enable', 'boolean'); 61 | validate(ganalytics, 'ganalytics', 'utm_source', 'string'); 62 | validate(ganalytics, 'ganalytics', 'utm_medium', 'string'); 63 | validate(ganalytics, 'ganalytics', 'utm_term', 'string'); 64 | validate(ganalytics, 'ganalytics', 'utm_content', 'string'); 65 | validate(ganalytics, 'ganalytics', 'utm_campaign', 'string'); 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /packages/helpers/helpers/wrap-substitutions.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrap substitutions 3 | */ 4 | declare function wrapSubstitutions(substitutions: { [key: string]: string }, left?: string, right?: string): { [key: string]: string }; 5 | 6 | export = wrapSubstitutions; 7 | -------------------------------------------------------------------------------- /packages/helpers/helpers/wrap-substitutions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Wrap substitutions 5 | */ 6 | module.exports = function wrap(substitutions, left = '{{', right = '}}') { 7 | 8 | //Process arrays 9 | if (Array.isArray(substitutions)) { 10 | return substitutions.map(subs => wrap(subs, left, right)); 11 | } 12 | 13 | //Initialize new wrapped object 14 | const wrapped = {}; 15 | 16 | //Map substitutions and ensure string for value 17 | for (const key in substitutions) { 18 | //istanbul ignore else 19 | if (substitutions.hasOwnProperty(key)) { 20 | wrapped[left + key + right] = String(substitutions[key]); 21 | } 22 | } 23 | 24 | //Return wrapped substitutions 25 | return wrapped; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/helpers/helpers/wrap-substitutions.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const wrapSubstitutions = require('./wrap-substitutions'); 7 | 8 | /** 9 | * Tests 10 | */ 11 | describe('wrapSubstitutions', function() { 12 | 13 | //Test substitutions 14 | const test = { 15 | some: 'string', 16 | someOther: 'string', 17 | 'and-a-number': 3, 18 | }; 19 | 20 | //Test array of substitutions 21 | const arr = [test, test]; 22 | 23 | //Left/right substitutions 24 | const left1 = '{{'; 25 | const right1 = '}}'; 26 | const left2 = '['; 27 | const right2 = ']'; 28 | 29 | //Wrap 30 | const wrap1 = wrapSubstitutions(test, left1, right1); 31 | const wrap2 = wrapSubstitutions(test, left2, right2); 32 | const wrapArr = wrapSubstitutions(arr, left1, right1); 33 | const wrapDefault = wrapSubstitutions(test); 34 | 35 | //Tests 36 | it('should wrap the substitution keys with the relevant wrapper', function() { 37 | expect(wrap1).to.have.property('{{some}}'); 38 | expect(wrap1).to.have.property('{{someOther}}'); 39 | expect(wrap1).to.have.property('{{and-a-number}}'); 40 | expect(wrap2).to.have.property('[some]'); 41 | expect(wrap2).to.have.property('[someOther]'); 42 | expect(wrap2).to.have.property('[and-a-number]'); 43 | }); 44 | it('should not preserve the original keys', function() { 45 | expect(wrap1).not.to.have.property('some'); 46 | expect(wrap1).not.to.have.property('someOther'); 47 | expect(wrap1).not.to.have.property('and-a-number'); 48 | }); 49 | it('should convert substitutions to strings', function() { 50 | expect(wrap1['{{some}}']).to.be.a('string'); 51 | expect(wrap1['{{and-a-number}}']).to.be.a('string'); 52 | }); 53 | it('should convert an array of substitutions', function() { 54 | expect(wrapArr).to.be.an('array'); 55 | expect(wrapArr[0]).to.have.property('{{some}}'); 56 | expect(wrapArr[1]).to.have.property('{{some}}'); 57 | }); 58 | it('should default to the {{}} wrapper', function() { 59 | expect(wrapDefault).to.have.property('{{some}}'); 60 | expect(wrapDefault).to.have.property('{{someOther}}'); 61 | expect(wrapDefault).to.have.property('{{and-a-number}}'); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /packages/helpers/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as helpers from '@sendgrid/helpers/helpers/index'; 2 | import * as classes from '@sendgrid/helpers/classes/index'; 3 | 4 | export { 5 | helpers, 6 | classes, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/helpers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Load support assets 5 | */ 6 | const classes = require('./classes'); 7 | const helpers = require('./helpers'); 8 | 9 | /** 10 | * Export 11 | */ 12 | module.exports = {classes, helpers}; 13 | -------------------------------------------------------------------------------- /packages/helpers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sendgrid/helpers", 3 | "description": "Twilio SendGrid NodeJS internal helpers", 4 | "version": "8.0.0", 5 | "author": "Twilio SendGrid (sendgrid.com)", 6 | "contributors": [ 7 | "Kyle Partridge ", 8 | "David Tomberlin ", 9 | "Swift ", 10 | "Brandon West ", 11 | "Scott Motte ", 12 | "Robert Acosta ", 13 | "Elmer Thomas ", 14 | "Adam Reis " 15 | ], 16 | "license": "MIT", 17 | "homepage": "https://sendgrid.com", 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/sendgrid/sendgrid-nodejs.git" 21 | }, 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "engines": { 26 | "node": ">= 12.0.0" 27 | }, 28 | "tags": [ 29 | "sendgrid", 30 | "helpers" 31 | ], 32 | "dependencies": { 33 | "deepmerge": "^4.2.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/inbound-mail-parser/README.md: -------------------------------------------------------------------------------- 1 | [![BuildStatus](https://travis-ci.com/sendgrid/sendgrid-nodejs.svg?branch=main)](https://travis-ci.com/sendgrid/sendgrid-nodejs) 2 | [![npm version](https://badge.fury.io/js/%40sendgrid%2Fclient.svg)](https://www.npmjs.com/org/sendgrid) 3 | 4 | **This package is part of a monorepo, please see [this README](../../README.md) for details.** 5 | 6 | # Inbound Parse Service for the [SendGrid Inbound Parse API](https://sendgrid.com/docs/API_Reference/Parse_Webhook/inbound_email.html) 7 | This package helps get you started consuming and processing [Inbound Parse](https://sendgrid.com/docs/API_Reference/Parse_Webhook/inbound_email.html) data. 8 | 9 | ## Prerequisites 10 | 11 | - Node.js version 6, 8 or >=10 12 | - A Twilio SendGrid account, [sign up for free](https://sendgrid.com/free?source=sendgrid-nodejs) to send up to 40,000 emails for the first 30 days or check out [our pricing](https://sendgrid.com/pricing?source=sendgrid-nodejs). 13 | 14 | ## Obtain an API Key 15 | 16 | Grab your API Key from the [Twilio SendGrid UI](https://app.sendgrid.com/settings/api_keys). 17 | 18 | # Install Package 19 | 20 | The following recommended installation requires [npm](https://npmjs.org/). If you are unfamiliar with npm, see the [npm docs](https://npmjs.org/doc/). Npm comes installed with Node.js since node version 0.8.x, therefore, you likely already have it. 21 | 22 | ```sh 23 | npm install --save @sendgrid/inbound-mail-parser 24 | ``` 25 | 26 | You may also use [yarn](https://yarnpkg.com/en/) to install. 27 | 28 | ```sh 29 | yarn add @sendgrid/inbound-mail-parser 30 | ``` 31 | 32 | 33 | # How to Contribute 34 | 35 | We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](https://github.com/sendgrid/sendgrid-nodejs/blob/HEAD/CONTRIBUTING.md) guide for details. 36 | 37 | * [Feature Request](../../CONTRIBUTING.md#feature-request) 38 | * [Bug Reports](../../CONTRIBUTING.md#submit-a-bug-report) 39 | * [Improvements to the Codebase](../../CONTRIBUTING.md#improvements-to-the-codebase) 40 | 41 | 42 | # Troubleshooting 43 | 44 | Please see our [troubleshooting guide](https://github.com/sendgrid/sendgrid-nodejs/blob/main/TROUBLESHOOTING.md) for common library issues. 45 | 46 | 47 | # About 48 | 49 | @sendgrid/inbound-mail-parser is maintained and funded by Twilio SendGrid, Inc. The names and logos for @sendgrid/inbound-mail-parser are trademarks of Twilio SendGrid, Inc. 50 | 51 | If you need help installing or using the library, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com). 52 | 53 | If you've instead found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo! 54 | 55 | ![Twilio SendGrid Logo](https://github.com/sendgrid/sendgrid-nodejs/blob/main/twilio_sendgrid_logo.png?raw=true) 56 | -------------------------------------------------------------------------------- /packages/inbound-mail-parser/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as Parser from "./src/parser"; 2 | 3 | export = Parser; -------------------------------------------------------------------------------- /packages/inbound-mail-parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sendgrid/inbound-mail-parser", 3 | "description": "Twilio SendGrid NodeJS inbound mail parser", 4 | "version": "8.0.0", 5 | "author": "Twilio SendGrid (sendgrid.com)", 6 | "contributors": [ 7 | "Kyle Partridge ", 8 | "David Tomberlin ", 9 | "Swift ", 10 | "Brandon West ", 11 | "Scott Motte ", 12 | "Robert Acosta ", 13 | "Elmer Thomas " 14 | ], 15 | "license": "MIT", 16 | "homepage": "https://sendgrid.com", 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/sendgrid/sendgrid-nodejs.git" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "main": "src/parser.js", 25 | "engines": { 26 | "node": ">=12.*" 27 | }, 28 | "dependencies": { 29 | "@sendgrid/helpers": "^8.0.0", 30 | "mailparser": "^2.3.4" 31 | }, 32 | "tags": [ 33 | "sendgrid" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /packages/inbound-mail-parser/src/parser.d.ts: -------------------------------------------------------------------------------- 1 | import {Attachment} from "@sendgrid/helpers/classes" 2 | 3 | declare interface ParseConfig { 4 | keys: string[]; 5 | } 6 | 7 | declare interface ParseRequest { 8 | body?: {}; 9 | payload?: {}; 10 | files?: any[]; 11 | } 12 | 13 | declare class Parse { 14 | constructor(config: ParseConfig, request: ParseRequest); 15 | 16 | /** 17 | * Return an object literal of key/values in the payload received from webhook 18 | * @return {Object} Valid key/values in the webhook payload 19 | */ 20 | keyValues(): { [key: string]: any }; 21 | 22 | /** 23 | * Whether the payload contains the raw email (Only applies to raw payloads) 24 | * @return {Boolean} 25 | */ 26 | hasRawEmail(): boolean; 27 | 28 | /** 29 | * Parses the raw email and returns the mail object in a callback (Only applies to raw payloads) 30 | * @param {Function} callback Function which will receive the parsed email object as the sole argument 31 | */ 32 | getRawEmail(callback: (mail: any) => void): void; 33 | // TODO: Type information for MailParser result in callback 34 | 35 | /** 36 | * Retrieves all attachments received from the webhook payload 37 | * @param {Function} callback Function which will receive an array, of attachments found, as the sole argument 38 | */ 39 | attachments(callback: (attachments: Attachment[]) => void): void; 40 | } 41 | 42 | export = Parse; -------------------------------------------------------------------------------- /packages/inbound-mail-parser/src/parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const { MailParser } = require('mailparser'); 5 | const { 6 | classes: { 7 | Attachment, 8 | }, 9 | } = require('@sendgrid/helpers'); 10 | 11 | /** 12 | * Normalises attachment files retrieved from file system or parsed raw email 13 | * 14 | * @param {Object} file The file object returned by file system or parsed email 15 | * @return {Object} A Twilio SendGrid Attachment object with the file data 16 | */ 17 | const createAttachment = (file) => { 18 | const {originalname, fileName, mimetype, contentType, content} = file; 19 | const attachment = new Attachment(); 20 | 21 | attachment.setFilename(originalname || fileName); 22 | attachment.setType(mimetype || contentType); 23 | attachment.setContent(content.toString('base64')); 24 | 25 | return attachment; 26 | }; 27 | 28 | /** 29 | * Simple class that parses data received from the Twilio SendGrid Inbound Parse Webhook 30 | * 31 | */ 32 | class Parse { 33 | 34 | /** 35 | * @constructor 36 | * @param {Object} config inbound configuration object 37 | * @param {Object} request request object of the parse webhook payload 38 | */ 39 | constructor(config, request) { 40 | this.keys = config.keys; 41 | this.request = request; 42 | this.payload = request.body || request.payload || {}; 43 | this.files = request.files || []; 44 | } 45 | 46 | /** 47 | * Return an object literal of key/values in the payload received from webhook 48 | * @return {Object} Valid key/values in the webhook payload 49 | */ 50 | keyValues() { 51 | return this.keys 52 | .filter(key => this.payload[key]) 53 | .map(key => ({ [key]: this.payload[key] })) 54 | .reduce((keyValues, keyPayload) => Object.assign(keyValues, keyPayload)); 55 | } 56 | 57 | /** 58 | * Whether the payload contains the raw email (Only applies to raw payloads) 59 | * @return {Boolean} 60 | */ 61 | hasRawEmail() { 62 | return !!this.payload.email; 63 | } 64 | 65 | /** 66 | * Parses the raw email and returns the mail object in a callback (Only applies to raw payloads) 67 | * @param {Function} callback Function which will receive the parsed email object as the sole argument 68 | */ 69 | getRawEmail(callback) { 70 | const mailparser = new MailParser(); 71 | const { rawEmail } = this.payload; 72 | 73 | if (!this.hasRawEmail()) { 74 | return callback(null); 75 | } 76 | 77 | mailparser.on('end', callback); 78 | mailparser.write(rawEmail); 79 | mailparser.end(); 80 | } 81 | 82 | /** 83 | * Retrieves all attachments received from the webhook payload 84 | * @param {Function} callback Function which will receive an array, of attachments found, as the sole argument 85 | */ 86 | attachments(callback) { 87 | return this[`_getAttachments${this.hasRawEmail() ? 'Raw' : ''}`](callback); 88 | } 89 | 90 | /** 91 | * Parses raw email to retrieve any encoded attachments (Only applies to raw payloads) 92 | * @private 93 | * @param {Function} callback Function which will receive an array, of attachments found, as the sole argument 94 | */ 95 | _getAttachmentsRaw(callback) { 96 | this.getRawEmail(parsedEmail => { 97 | const attachments = (parsedEmail || {}).attachments || []; 98 | callback(attachments.map(createAttachment)); 99 | }); 100 | } 101 | 102 | /** 103 | * Retrieves webhook payload files from the file system (Only applies to non raw payloads) 104 | * @private 105 | * @param {Function} callback Function which will receive an array, of attachments found, as the sole argument 106 | */ 107 | _getAttachments(callback) { 108 | return callback(this.files 109 | .filter(file => fs.existsSync(file.path)) 110 | .map((exists, idx) => [exists, this.files[idx]]) 111 | .filter(([exists, _]) => exists) 112 | .map(([_, file]) => { 113 | file.content = fs.readFileSync(file.path); 114 | return createAttachment(file); 115 | }) 116 | ); 117 | } 118 | } 119 | 120 | module.exports = Parse; 121 | -------------------------------------------------------------------------------- /packages/inbound-mail-parser/src/parser.spec.js: -------------------------------------------------------------------------------- 1 | const Parse = require('./parser'); 2 | 3 | describe('test_parse', () => { 4 | describe('test_parse_key_values', () => { 5 | it('should return the key values specified in the config from the body', () => { 6 | const config = { 7 | keys: ['to', 'from'], 8 | }; 9 | const request = { 10 | body: { 11 | to: 'inbound@inbound.example.com', 12 | from: 'Test User ', 13 | subject: 'Test Subject', 14 | }, 15 | }; 16 | 17 | const parse = new Parse(config, request); 18 | const keyValues = parse.keyValues(); 19 | const expectedValues = { 20 | to: 'inbound@inbound.example.com', 21 | from: 'Test User ', 22 | }; 23 | 24 | expect(keyValues).to.be.an('object'); 25 | expect(keyValues).to.deep.equal(expectedValues); 26 | }); 27 | 28 | it('should return the key values specified in the config from the payload', () => { 29 | const config = { 30 | keys: ['to', 'from'], 31 | }; 32 | const request = { 33 | payload: { 34 | to: 'inbound@inbound.example.com', 35 | from: 'Test User ', 36 | subject: 'Test Subject', 37 | }, 38 | }; 39 | 40 | const parse = new Parse(config, request); 41 | const keyValues = parse.keyValues(); 42 | const expectedValues = { 43 | to: 'inbound@inbound.example.com', 44 | from: 'Test User ', 45 | }; 46 | 47 | expect(keyValues).to.be.an('object'); 48 | expect(keyValues).to.deep.equal(expectedValues); 49 | }); 50 | }); 51 | 52 | describe('test_parse_get_raw_email', () => { 53 | it('should return null if no raw email property in payload', (done) => { 54 | const parse = new Parse({}, {}); 55 | 56 | function callback(email) { 57 | expect(email).to.be.null(); 58 | done(); 59 | } 60 | 61 | parse.getRawEmail(callback); 62 | }); 63 | 64 | it('should parse raw email from payload and return a mail object', (done) => { 65 | const request = { 66 | body: { 67 | email: 'MIME-Version: 1.0\r\nReceived: by 0.0.0.0 with HTTP; Wed, 10 Aug 2016 14:44:21 -0700 (PDT)\r\nFrom: Example User \r\nDate: Wed, 10 Aug 2016 14:44:21 -0700\r\nSubject: Inbound Parse Test Raw Data\r\nTo: inbound@inbound.inbound.com\r\nContent-Type: multipart/alternative; boundary=001a113ee97c89842f0539be8e7a\r\n\r\n--001a113ee97c89842f0539be8e7a\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\nHello Twilio SendGrid!\r\n\r\n--001a113ee97c89842f0539be8e7a\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nHello Twilio SendGrid!\r\n\r\n--001a113ee97c89842f0539be8e7a--\r\n', 68 | }, 69 | }; 70 | 71 | const parse = new Parse({}, request); 72 | 73 | function callback(email) { 74 | expect(email).to.be.an('object'); 75 | done(); 76 | } 77 | 78 | parse.getRawEmail(callback); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /packages/mail/USE_CASES.md: -------------------------------------------------------------------------------- 1 | Please see the Email Use Cases [here](../../docs/use-cases/README.md#email-use-cases). 2 | -------------------------------------------------------------------------------- /packages/mail/index.d.ts: -------------------------------------------------------------------------------- 1 | import MailService = require("@sendgrid/mail/src/mail"); 2 | 3 | export = MailService; -------------------------------------------------------------------------------- /packages/mail/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mailer = require('./src/mail'); 4 | const MailService = require('./src/classes/mail-service'); 5 | 6 | module.exports = mailer; 7 | module.exports.MailService = MailService; 8 | -------------------------------------------------------------------------------- /packages/mail/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sendgrid/mail", 3 | "description": "Twilio SendGrid NodeJS mail service", 4 | "version": "8.1.5", 5 | "author": "Twilio SendGrid (sendgrid.com)", 6 | "contributors": [ 7 | "Kyle Partridge ", 8 | "David Tomberlin ", 9 | "Swift ", 10 | "Brandon West ", 11 | "Scott Motte ", 12 | "Robert Acosta ", 13 | "Elmer Thomas ", 14 | "Adam Reis " 15 | ], 16 | "license": "MIT", 17 | "homepage": "https://sendgrid.com", 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/sendgrid/sendgrid-nodejs.git" 21 | }, 22 | "main": "index.js", 23 | "engines": { 24 | "node": ">=12.*" 25 | }, 26 | "publishConfig": { 27 | "access": "public" 28 | }, 29 | "dependencies": { 30 | "@sendgrid/client": "^8.1.5", 31 | "@sendgrid/helpers": "^8.0.0" 32 | }, 33 | "tags": [ 34 | "http", 35 | "rest", 36 | "api", 37 | "mail", 38 | "sendgrid" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /packages/mail/src/mail.d.ts: -------------------------------------------------------------------------------- 1 | import {Client} from "@sendgrid/client"; 2 | import {ClientResponse} from "@sendgrid/client/src/response"; 3 | import {ResponseError} from "@sendgrid/helpers/classes"; 4 | import {MailDataRequired} from "@sendgrid/helpers/classes/mail"; 5 | 6 | declare class MailService { 7 | /** 8 | * SendGrid API key passthrough for convenience. 9 | */ 10 | setApiKey(apiKey: string): void; 11 | 12 | /** 13 | * Client to use for invoking underlying API 14 | */ 15 | setClient(client: Client): void; 16 | 17 | /** 18 | * Twilio Email Auth passthrough for convenience. 19 | */ 20 | setTwilioEmailAuth(username: string, password: string): void; 21 | 22 | /** 23 | * Set the default request timeout (in milliseconds). 24 | */ 25 | setTimeout(timeout: number): void; 26 | 27 | /** 28 | * Set substitution wrappers 29 | */ 30 | setSubstitutionWrappers(left: string, right: string): void; 31 | 32 | /** 33 | * Send email 34 | */ 35 | send(data: MailDataRequired | MailDataRequired[], isMultiple?: boolean, cb?: (err: Error | ResponseError, result: [ClientResponse, {}]) => void): Promise<[ClientResponse, {}]>; 36 | 37 | /** 38 | * Send multiple emails (shortcut) 39 | */ 40 | sendMultiple(data: MailDataRequired, cb?: (error: Error | ResponseError, result: [ClientResponse, {}]) => void): Promise<[ClientResponse, {}]>; 41 | } 42 | 43 | declare const mail: MailService; 44 | // @ts-ignore 45 | export = mail; 46 | 47 | export {MailService}; 48 | export {MailDataRequired}; 49 | export {ClientResponse}; 50 | export {ResponseError}; 51 | -------------------------------------------------------------------------------- /packages/mail/src/mail.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const MailService = require('./classes/mail-service'); 7 | 8 | //Export singleton instance 9 | module.exports = new MailService(); 10 | -------------------------------------------------------------------------------- /packages/mail/src/mail.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dependencies 5 | */ 6 | const sgMail = require('./mail'); 7 | const sgClient = sgMail.client; 8 | 9 | /** 10 | * Setup client 11 | */ 12 | before(() => { 13 | sgClient.setApiKey('SendGrid API Key'); 14 | }); 15 | 16 | /** 17 | * Default mock header 18 | */ 19 | beforeEach(() => { 20 | sgClient.setDefaultHeader('X-Mock', 202); 21 | }); 22 | 23 | /** 24 | * Tests 25 | */ 26 | describe('sgMail.send()', () => { 27 | 28 | //Create mail data 29 | const data = { 30 | to: 'recipient@example.org', 31 | from: 'sender@example.org', 32 | subject: 'Hello world', 33 | text: 'Hello plain world!', 34 | html: '

Hello HTML world!

', 35 | }; 36 | 37 | it('should throw an error when no data provided', () => { 38 | return expect(sgMail.send()).to.eventually.be.rejectedWith(Error); 39 | }); 40 | 41 | it('should send a basic email', () => { 42 | sgClient.setDefaultHeader('X-Mock', 202); 43 | return sgMail 44 | .send(data) 45 | .then(([response, body]) => { 46 | expect(response.statusCode).to.equal(202); 47 | }); 48 | }); 49 | 50 | it('should throw an error if callback is not a function', () => { 51 | return expect(function() { 52 | sgMail.send(data, false, {}); 53 | }).to.throw(Error); 54 | }); 55 | 56 | it('should include custom headers to the request', () => { 57 | sgClient.setDefaultHeader('X-Mock', 202); 58 | const clientSpy = sinon.spy(sgClient, "request") 59 | return sgMail 60 | .send(Object.assign(data, { headers: { customHeader: "Custom Header Content" } })) 61 | .then(([response, body]) => { 62 | expect(response.statusCode).to.equal(202); 63 | expect(clientSpy).to.have.been.calledWith(sinon.match({ 64 | url: "/v3/mail/send", 65 | method: "POST", 66 | headers: { customHeader: "Custom Header Content" } 67 | })); 68 | }); 69 | }); 70 | 71 | it('should send email with correct replyToList format', () => { 72 | sgClient.setDefaultHeader('X-Mock', 202); 73 | data["replyToList"] = [ 74 | { 75 | "name": "Test Team", 76 | "email": "test@example.org" 77 | }, 78 | { 79 | "name": "Support Test Team", 80 | "email": "support.test@example.org" 81 | } 82 | ]; 83 | return sgMail 84 | .send(data) 85 | .then(([response, body]) => { 86 | expect(response.statusCode).to.equal(202); 87 | }); 88 | }); 89 | 90 | it('should throw error with wrong replyToList format', () => { 91 | sgClient.setDefaultHeader('X-Mock', 202); 92 | data["replyToList"] = { 93 | "name": "Support Test Team", 94 | "email": "support.test@example.org" 95 | }; 96 | return expect(function() { 97 | sgMail.send(data, false, {}); 98 | }).to.throw(Error); 99 | }); 100 | 101 | it('should throw error if any record in replyToList is without email', () => { 102 | data["replyToList"] = [ 103 | { 104 | "name": "Test Team", 105 | "email": "test@example.org" 106 | }, 107 | { 108 | "name": "Support Test Team" 109 | } 110 | ]; 111 | return expect(function() { 112 | sgMail.send(data, false, {}); 113 | }).to.throw(Error); 114 | }); 115 | 116 | it('should throw error if both replyTo and replyToList are mentioned', () => { 117 | data["replyTo"] = { 118 | "name": "Manual Tester", 119 | "email": "manual.test@example.org" 120 | }; 121 | data["replyToList"] = [ 122 | { 123 | "name": "Test Team", 124 | "email": "test@example.org" 125 | }, 126 | { 127 | "name": "Support Test Team", 128 | "email": "support.test@example.org" 129 | } 130 | ]; 131 | return expect(function() { 132 | sgMail.send(data, false, {}); 133 | }).to.throw(Error); 134 | }); 135 | }); 136 | 137 | -------------------------------------------------------------------------------- /packages/subscription-widget/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /packages/subscription-widget/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2017 SendGrid, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/subscription-widget/Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js -------------------------------------------------------------------------------- /packages/subscription-widget/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Email Double Opt-In", 3 | "description": "Double opt-in email automation", 4 | "repository": "https://github.com/devchas/sendgrid_subscription_widget", 5 | "keywords": ["email", "SendGrid", "Twilio"] 6 | } -------------------------------------------------------------------------------- /packages/subscription-widget/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const http = require('http'); 3 | const bodyParser = require('body-parser'); 4 | const morgan = require('morgan'); 5 | const app = express(); 6 | const router = require('./server/router'); 7 | 8 | // App setup 9 | app.use(morgan('combined')); 10 | app.use(bodyParser.json()); 11 | app.use(bodyParser.urlencoded({ extended: true })); 12 | router(app); 13 | 14 | // Server setup 15 | const port = process.env.PORT || 3090; 16 | const server = http.createServer(app); 17 | server.listen(port); 18 | console.log('Server listening on:', port); 19 | -------------------------------------------------------------------------------- /packages/subscription-widget/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sendgrid/subscription-widget", 3 | "version": "8.0.0", 4 | "description": "Double opt-in email automation", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "nodemon index.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.15.2", 14 | "express": "^4.14.0", 15 | "morgan": "^1.9.1", 16 | "nodemon": "^1.9.2", 17 | "sendgrid": "^4.0.2", 18 | "sendgrid-rest": "^2.5.0" 19 | }, 20 | "publishConfig": { 21 | "access": "public" 22 | }, 23 | "engines": { 24 | "node": ">=12.*" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/subscription-widget/server/router.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ContactList = require('./controllers/contact_list_controller'); 3 | 4 | module.exports = function(app) { 5 | app.get('/', function(req, res) { 6 | res.sendFile(path.join(__dirname, '/static/index.html')); 7 | }); 8 | app.get('/success', function(req, res) { 9 | res.sendFile(path.join(__dirname, '/static/success.html')); 10 | }); 11 | app.post('/confirmEmail', ContactList.sendConfirmation); 12 | app.post('/signup', ContactList.addUser); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/subscription-widget/server/static/check-inbox.html: -------------------------------------------------------------------------------- 1 |

Please check your inbox for an email containing a link to verify your email address.

-------------------------------------------------------------------------------- /packages/subscription-widget/server/static/error.html: -------------------------------------------------------------------------------- 1 |

Looks like something went wrong. Please try again. Please be sure to enter a valid email address.

-------------------------------------------------------------------------------- /packages/subscription-widget/server/static/index.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Enter Your Information 4 | 5 | 6 |
7 | 8 | 9 |
10 | 11 | 12 |
13 | 14 | 15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/subscription-widget/server/static/sample-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sendgrid/sendgrid-nodejs/2bac86884f71be3fb19f96a10c02a1fb616b81fc/packages/subscription-widget/server/static/sample-form.png -------------------------------------------------------------------------------- /packages/subscription-widget/server/static/success.html: -------------------------------------------------------------------------------- 1 |

Thank you for confirming your email address. You've been added to our mailing list.

-------------------------------------------------------------------------------- /packages/subscription-widget/server/static/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sendgrid/sendgrid-nodejs/2bac86884f71be3fb19f96a10c02a1fb616b81fc/packages/subscription-widget/server/static/template.png -------------------------------------------------------------------------------- /packages/subscription-widget/settings.js: -------------------------------------------------------------------------------- 1 | // Change the url to the domain of your app 2 | exports.url = 'http://localhost:3090'; 3 | 4 | exports.senderEmail = 'sender@example.com'; 5 | exports.senderName = 'Sender Name'; 6 | 7 | // set 'exports.listId = null' to add contact to all contacts, but no specific list 8 | // or a string with the listId to add to a specific list 9 | exports.listId = null; 10 | 11 | // set 'exports.templateId = null' to opt out of using a template 12 | // or a string with the templateId to use a template 13 | exports.templateId = null; 14 | 15 | // receive an email when a new signup is confirmed 16 | exports.sendNotification = true; 17 | exports.notificationEmail = 'admin@example.com'; 18 | -------------------------------------------------------------------------------- /static/img/github-fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sendgrid/sendgrid-nodejs/2bac86884f71be3fb19f96a10c02a1fb616b81fc/static/img/github-fork.png -------------------------------------------------------------------------------- /static/img/github-sign-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sendgrid/sendgrid-nodejs/2bac86884f71be3fb19f96a10c02a1fb616b81fc/static/img/github-sign-up.png -------------------------------------------------------------------------------- /test/files.spec.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fs = require('fs'); 3 | 4 | describe('sendgrid-nodejs repo', function() { 5 | /* 6 | it('should have ./Docker or docker/Docker file', function() { 7 | assert(fileExists('Docker') || fileExists('docker/Docker')); 8 | }); 9 | 10 | it('should have ./docker-compose.yml or ./docker/docker-compose.yml file', function() { 11 | assert(fileExists('docker-compose.yml') || fileExists('docker/docker-compose.yml')); 12 | }); 13 | */ 14 | 15 | it('should have ./.env_sample file', function() { 16 | assert(fileExists('.env_sample')); 17 | }); 18 | 19 | it('should have ./.gitignore file', function() { 20 | assert(fileExists('.gitignore')); 21 | }); 22 | 23 | it('should have ./.github/workflows/test-and-deploy file', function() { 24 | assert(fileExists('.github/workflows/test-and-deploy.yml')); 25 | }); 26 | 27 | it('should have ./CHANGELOG.md file', function() { 28 | assert(fileExists('CHANGELOG.md')); 29 | }); 30 | 31 | it('should have ./CODE_OF_CONDUCT.md file', function() { 32 | assert(fileExists('CODE_OF_CONDUCT.md')); 33 | }); 34 | 35 | it('should have ./CONTRIBUTING.md file', function() { 36 | assert(fileExists('CONTRIBUTING.md')); 37 | }); 38 | 39 | it('should have ./LICENSE file', function() { 40 | assert(fileExists('LICENSE')); 41 | }); 42 | 43 | it('should have ./PULL_REQUEST_TEMPLATE.md file', function() { 44 | assert(fileExists('PULL_REQUEST_TEMPLATE.md')); 45 | }); 46 | 47 | it('should have ./README.md file', function() { 48 | assert(fileExists('README.md')); 49 | }); 50 | 51 | it('should have ./TROUBLESHOOTING.md file', function() { 52 | assert(fileExists('TROUBLESHOOTING.md')); 53 | }); 54 | 55 | it('should have ./USAGE.md file', function() { 56 | assert(fileExists('USAGE.md')); 57 | }); 58 | 59 | /* 60 | it('should have ./USE_CASES.md file', function() { 61 | assert(fileExists('USE_CASES.md')); 62 | }); 63 | */ 64 | 65 | function fileExists(filepath) { 66 | try { 67 | return fs.statSync(filepath).isFile(); 68 | } catch(e) { 69 | if (e.code === 'ENOENT') { 70 | console.log('' + filepath + ' doesn\'t exist.'); 71 | return false; 72 | } 73 | throw e; 74 | } 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /test/mocha.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Load dependencies 4 | const fs = require('fs'); 5 | const chai = require('chai'); 6 | const sinon = require('sinon'); 7 | const dirtyChai = require('dirty-chai'); 8 | const sinonChai = require('sinon-chai'); 9 | const chaiAsPromised = require('chai-as-promised'); 10 | 11 | //Enable should assertion style for usage with chai-as-promised 12 | chai.should(); 13 | 14 | //Extend chai 15 | chai.use(dirtyChai); 16 | chai.use(sinonChai); 17 | chai.use(chaiAsPromised); 18 | 19 | //Load sinon extensions 20 | require('mocha-sinon'); 21 | 22 | //Expose globals 23 | global.expect = chai.expect; 24 | global.sinon = sinon; 25 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | --timeout 50000 3 | --require mocha-clean 4 | ./test/mocha.js 5 | -------------------------------------------------------------------------------- /test/typescript/client.ts: -------------------------------------------------------------------------------- 1 | import Client = require("@sendgrid/client"); 2 | import * as https from 'https'; 3 | 4 | // Test setApiKey() method 5 | Client.setApiKey("MY_SENDGRID_API_KEY"); 6 | 7 | // Test setDefaultHeader() method 8 | Client.setDefaultHeader({ 9 | "X-Testing-Type": "TypeScript", 10 | }).setDefaultHeader("X-Testing", "yes"); 11 | 12 | // Test setDefaultRequest() method 13 | Client.setDefaultRequest({ 14 | url: "/test", 15 | }).setDefaultRequest("method", "POST") 16 | .setDefaultRequest('httpsAgent', new https.Agent()) 17 | 18 | // Test createHeaders() method 19 | Client.createHeaders({ 20 | "X-Testing": "yes" 21 | }); 22 | 23 | // Test createRequest() method 24 | const req = Client.createRequest({ 25 | url: "/test" 26 | }); 27 | 28 | // Test request() method 29 | Client.request({ 30 | url: "/test" 31 | }).then(res => { 32 | res[0].statusCode; 33 | }); 34 | -------------------------------------------------------------------------------- /test/typescript/eventwebhook.ts: -------------------------------------------------------------------------------- 1 | import { EventWebhook } from '@sendgrid/eventwebhook'; 2 | 3 | var ew = new EventWebhook(); 4 | const PUBLIC_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybdC+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA=="; 5 | const SIGNATURE = "MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0="; 6 | const TIMESTAMP = "1588788367"; 7 | const PAYLOAD = JSON.stringify({ 8 | event: 'test_event', 9 | category: 'example_payload', 10 | message_id: 'message_id', 11 | }); 12 | var key = ew.convertPublicKeyToECDSA(PUBLIC_KEY); 13 | console.log(ew.verifySignature(key, PAYLOAD, SIGNATURE, TIMESTAMP)); 14 | -------------------------------------------------------------------------------- /test/typescript/helpers.ts: -------------------------------------------------------------------------------- 1 | import * as helpers from "@sendgrid/helpers"; 2 | new helpers.classes.Attachment({ 3 | content: "", 4 | filename: "test.txt", 5 | type: "text/plain" 6 | }); 7 | 8 | new helpers.classes.EmailAddress("someone@example.org"); 9 | new helpers.classes.EmailAddress({ name: "Some One", email: "someone@example.org" }); 10 | 11 | new helpers.classes.Personalization({ 12 | to: "someone@example.org", 13 | from: "somebody@example.org", 14 | subject: "Hello Some One", 15 | dynamicTemplateData: { 16 | translations: { 17 | hello: "Привет!" 18 | }, 19 | count: 1 20 | } 21 | }); 22 | 23 | new helpers.classes.Mail({ 24 | to: "someone@example.org", 25 | from: "someone@example.org", 26 | subject: "Hello Some One", 27 | text: "This is a test message.", 28 | html: "

This is a test message.

" 29 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "module": "commonjs", 5 | "target": "es6", 6 | "baseUrl": ".", 7 | "paths": { 8 | "@sendgrid/*": ["packages/*"] 9 | } 10 | }, 11 | "include": [ 12 | "test/typescript/*.ts" 13 | ] 14 | } -------------------------------------------------------------------------------- /twilio_sendgrid_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sendgrid/sendgrid-nodejs/2bac86884f71be3fb19f96a10c02a1fb616b81fc/twilio_sendgrid_logo.png --------------------------------------------------------------------------------