├── .github ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── .prettierignore ├── src ├── index.ts ├── lib │ ├── models │ │ ├── Attachment.ts │ │ ├── Options.ts │ │ ├── Message.ts │ │ └── Errors.ts │ ├── types │ │ ├── Callback.ts │ │ └── Responses.ts │ ├── ErrorHandler.spec.ts │ ├── DomainClient.spec.ts │ ├── DomainClient.ts │ ├── ErrorHandler.ts │ └── BaseClient.ts └── index.spec.ts ├── .gitignore ├── docs ├── modules.md ├── README.md └── classes │ └── domainclient.md ├── .vscode ├── extensions.json ├── settings.json └── launch.json ├── tsconfig.module.json ├── .editorconfig ├── CHANGELOG.md ├── .cspell.json ├── LICENSE ├── .circleci └── config.yml ├── .eslintrc.json ├── README.md ├── tsconfig.json └── package.json /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Pull requests are very welcome. 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # package.json is formatted by package managers, so we ignore it here 2 | package.json -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import DomainClient from './lib/DomainClient'; 2 | export { DomainClient as DomainClient }; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | .nyc_output 3 | build 4 | node_modules 5 | test 6 | src/**.js 7 | coverage 8 | *.log 9 | yarn.lock 10 | *DS_Store -------------------------------------------------------------------------------- /src/lib/models/Attachment.ts: -------------------------------------------------------------------------------- 1 | export type Attachment = { 2 | name: string; 3 | content: string; 4 | content_type: string; 5 | cid?: string; 6 | }; 7 | -------------------------------------------------------------------------------- /docs/modules.md: -------------------------------------------------------------------------------- 1 | [@mailpace/mailpace.js](README.md) / Exports 2 | 3 | # @mailpace/mailpace.js 4 | 5 | ## Table of contents 6 | 7 | ### Classes 8 | 9 | - [DomainClient](classes/domainclient.md) 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "eamodio.gitlens", 6 | "streetsidesoftware.code-spell-checker", 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/types/Callback.ts: -------------------------------------------------------------------------------- 1 | import { OmsError } from '../models/Errors'; 2 | 3 | /** 4 | * Node callback, with errors raised under 'OmsError' 5 | */ 6 | export type Callback = (error: OmsError | null, result: T | null) => void; 7 | -------------------------------------------------------------------------------- /src/lib/types/Responses.ts: -------------------------------------------------------------------------------- 1 | export type SendResponse = { 2 | readonly id: number; 3 | readonly status: string; 4 | }; 5 | 6 | export type ErrorResponse = { 7 | readonly error?: string; 8 | readonly errors?: object; 9 | }; 10 | -------------------------------------------------------------------------------- /tsconfig.module.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "outDir": "build/module", 6 | "module": "esnext" 7 | }, 8 | "exclude": [ 9 | "node_modules/**" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.userWords": [], // only use words from .cspell.json 3 | "cSpell.enabled": true, 4 | "editor.formatOnSave": true, 5 | "typescript.tsdk": "node_modules/typescript/lib", 6 | "typescript.enablePromptUseWorkspaceTsdk": true 7 | } 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 2 | 3 | - **What is the current behavior?** (You can also link to an open issue here) 4 | 5 | - **What is the new behavior (if this is a feature change)?** 6 | 7 | - **Other information**: 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /src/lib/models/Options.ts: -------------------------------------------------------------------------------- 1 | export namespace Options { 2 | export class Configuration { 3 | public readonly requestHost: string; 4 | public readonly timeout: number; 5 | } 6 | 7 | export enum HttpMethod { 8 | POST = 'POST', 9 | } 10 | 11 | export enum DefaultHeaderNames { 12 | SERVER_TOKEN = 'MailPace-Server-Token', 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### 0.1.3 (2024-10-12) 6 | 7 | ### [0.1.1](https://github.com/mailpace/mailpace.js/compare/v0.0.13...v0.1.1) (2022-01-24) 8 | 9 | ### 0.0.13 (2022-01-24) 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - **I'm submitting a ...** 2 | [ ] bug report 3 | [ ] feature request 4 | [ ] question about the decisions made in the repository 5 | [ ] question about how to use this project 6 | 7 | - **Summary** 8 | 9 | - **Other information** (e.g. detailed explanation, stack traces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.) 10 | -------------------------------------------------------------------------------- /src/lib/models/Message.ts: -------------------------------------------------------------------------------- 1 | import { Attachment } from './Attachment'; 2 | export type Message = { 3 | from: string; 4 | to: string; 5 | subject?: string; 6 | htmlbody?: string; 7 | textbody?: string; 8 | cc?: string; 9 | bcc?: string; 10 | replyto?: string; 11 | inreplyto?: string; 12 | references?: string; 13 | list_unsubscribe?: string; 14 | attachments?: ReadonlyArray; 15 | tags?: string | ReadonlyArray; 16 | }; 17 | -------------------------------------------------------------------------------- /src/lib/ErrorHandler.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { AxiosError } from 'axios'; 3 | 4 | import { ErrorHandler } from './ErrorHandler'; 5 | import { OmsError } from './models/Errors'; 6 | 7 | export class AxiosErrorObject implements AxiosError { 8 | public readonly name: string; 9 | public readonly message: string; 10 | public readonly isAxiosError: boolean; 11 | public readonly config: object; 12 | public readonly toJSON: () => object; 13 | } 14 | const axiosError: AxiosError = new AxiosErrorObject(); 15 | 16 | const handler = new ErrorHandler(); 17 | 18 | const noMessage = new OmsError(''); 19 | 20 | test('can build a request error', (t) => { 21 | const error = handler.buildRequestError(axiosError); 22 | t.deepEqual(error, noMessage); 23 | }); 24 | -------------------------------------------------------------------------------- /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1", 3 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json", 4 | "language": "en", 5 | "words": [ 6 | "bitjson", 7 | "bitauth", 8 | "cimg", 9 | "circleci", 10 | "codecov", 11 | "commitlint", 12 | "dependabot", 13 | "editorconfig", 14 | "esnext", 15 | "execa", 16 | "exponentiate", 17 | "globby", 18 | "libauth", 19 | "mkdir", 20 | "prettierignore", 21 | "sandboxed", 22 | "transpiled", 23 | "typedoc", 24 | "untracked", 25 | "ohmysmtp", 26 | "mailpace", 27 | "replyto", 28 | "inreplyto", 29 | "htmlbody", 30 | "textbody", 31 | "readonly" 32 | ], 33 | "flagWords": [], 34 | "ignorePaths": [ 35 | "package.json", 36 | "package-lock.json", 37 | "yarn.lock", 38 | "tsconfig.json", 39 | "node_modules/**" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/DomainClient.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import DomainClient from './DomainClient'; 4 | 5 | const token = 'token'; 6 | const client = new DomainClient(token); 7 | const defaultClientOptions = { 8 | requestHost: 'app.mailpace.com/api/v1/', 9 | timeout: 60, 10 | }; 11 | const otherClientOptions = { requestHost: 'another.com', timeout: 600 }; 12 | 13 | const defaultRequestHeaders = { 14 | Accept: 'application/json', 15 | 'Content-Type': 'application/json', 16 | 'MailPace-Server-Token': token, 17 | 'User-Agent': 'mailpace.js - 0.0.13', 18 | }; 19 | 20 | test('can initialize the domain client with default values', (t) => { 21 | t.truthy('sendEmail' in client); 22 | t.deepEqual(client.getOptions(), defaultClientOptions); 23 | }); 24 | 25 | test('token is set', (t) => { 26 | t.deepEqual(client.prepareHeaders(), defaultRequestHeaders); 27 | }); 28 | 29 | test('supports passing in different options', (t) => { 30 | const updatedClient = new DomainClient(token, otherClientOptions); 31 | t.deepEqual(updatedClient.getOptions(), otherClientOptions); 32 | }); 33 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | // To debug, make sure a *.spec.ts file is active in the editor, then run a configuration 5 | { 6 | "type": "node", 7 | "request": "launch", 8 | "name": "Debug Active Spec", 9 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava", 10 | "runtimeArgs": ["debug", "--break", "--serial", "${file}"], 11 | "port": 9229, 12 | "outputCapture": "std", 13 | "skipFiles": ["/**/*.js"], 14 | "preLaunchTask": "npm: build" 15 | // "smartStep": true 16 | }, 17 | { 18 | // Use this one if you're already running `yarn watch` 19 | "type": "node", 20 | "request": "launch", 21 | "name": "Debug Active Spec (no build)", 22 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava", 23 | "runtimeArgs": ["debug", "--break", "--serial", "${file}"], 24 | "port": 9229, 25 | "outputCapture": "std", 26 | "skipFiles": ["/**/*.js"] 27 | // "smartStep": true 28 | }] 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Paul Oms 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 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # https://circleci.com/docs/2.0/language-javascript/ 2 | version: 2 3 | jobs: 4 | 'node-12': 5 | docker: 6 | - image: circleci/node:12 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | keys: 11 | - v1-dependencies-{{ checksum "package.json" }} 12 | - v1-dependencies- 13 | - run: npm install 14 | - save_cache: 15 | paths: 16 | - node_modules 17 | key: v1-dependencies-{{ checksum "package.json" }} 18 | - run: npm test 19 | - run: npm run cov:send 20 | - run: npm run cov:check 21 | 'node-latest': 22 | docker: 23 | - image: circleci/node:latest 24 | steps: 25 | - checkout 26 | - restore_cache: 27 | keys: 28 | - v1-dependencies-{{ checksum "package.json" }} 29 | - v1-dependencies- 30 | - run: npm install 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | - run: npm test 36 | - run: npm run cov:send 37 | - run: npm run cov:check 38 | 39 | workflows: 40 | version: 2 41 | build: 42 | jobs: 43 | - 'node-12' 44 | - 'node-latest' 45 | -------------------------------------------------------------------------------- /src/lib/DomainClient.ts: -------------------------------------------------------------------------------- 1 | import BaseClient from './BaseClient'; 2 | import { Message } from './models/Message'; 3 | import { Options } from './models/Options'; 4 | import { Callback } from './types/Callback'; 5 | import { SendResponse } from './types/Responses'; 6 | 7 | /** 8 | * Client class that can be used to interact with an MailPace Domain 9 | */ 10 | export default class DomainClient extends BaseClient { 11 | /** 12 | * Create a client for sending emails from a domain 13 | * 14 | * @param domainToken - The API token for the domain 15 | * @param configOptions - Configuration options for accessing the API 16 | */ 17 | constructor(domainToken: string, configOptions?: Options.Configuration) { 18 | super(domainToken, Options.DefaultHeaderNames.SERVER_TOKEN, configOptions); 19 | } 20 | 21 | /** Send a single email message through the API 22 | * 23 | * @param send - Email to send 24 | * @param callback - A callback that if provided will be called after sending the email is complete 25 | * @returns A promise that will resolve when the API responds (or an error occurs) 26 | */ 27 | public sendEmail( 28 | email: Message, 29 | callback?: Callback 30 | ): Promise { 31 | return this.processRequestWithBody( 32 | Options.HttpMethod.POST, 33 | '/send', 34 | email, 35 | callback 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "project": "./tsconfig.json" 6 | }, 7 | "env": { 8 | "es6": true 9 | }, 10 | "ignorePatterns": ["node_modules", "build", "coverage"], 11 | "plugins": ["import", "eslint-comments", "functional"], 12 | "extends": [ 13 | "eslint:recommended", 14 | "plugin:eslint-comments/recommended", 15 | "plugin:@typescript-eslint/recommended", 16 | "plugin:import/typescript", 17 | "plugin:functional/lite", 18 | "prettier", 19 | "prettier/@typescript-eslint" 20 | ], 21 | "globals": { 22 | "BigInt": true, 23 | "console": true, 24 | "WebAssembly": true 25 | }, 26 | "rules": { 27 | "functional/no-class": "off", 28 | "functional/no-this-expression": "off", 29 | "functional/prefer-readonly-type": "off", 30 | "functional/immutable-data": "off", 31 | "@typescript-eslint/ban-types": "off", 32 | "functional/no-throw-statement": "off", 33 | "functional/no-return-void": "off", 34 | "@typescript-eslint/no-namespace": "off", 35 | "@typescript-eslint/explicit-module-boundary-types": "off", 36 | "eslint-comments/disable-enable-pair": [ 37 | "error", 38 | { 39 | "allowWholeFile": true 40 | } 41 | ], 42 | "eslint-comments/no-unused-disable": "error", 43 | "import/order": [ 44 | "error", 45 | { 46 | "newlines-between": "always", 47 | "alphabetize": { 48 | "order": "asc" 49 | } 50 | } 51 | ], 52 | "sort-imports": [ 53 | "error", 54 | { 55 | "ignoreDeclarationSort": true, 56 | "ignoreCase": true 57 | } 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MailPace Node.js Library 2 | 3 | [![](https://circleci.com/gh/mailpace/mailpace.js.svg?style=svg)](https://app.circleci.com/pipelines/github/mailpace/mailpace.js) 4 | [![codecov](https://codecov.io/gh/mailpace/mailpace.js/branch/master/graph/badge.svg?token=QDLVU2JGyD)](https://codecov.io/gh/mailpace/mailpace.js) 5 | [![npm version](https://badge.fury.io/js/%40mailpace%2Fmailpace.js.svg)](https://badge.fury.io/js/%40mailpace%2Fmailpace.js) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 7 | 8 | This is the official Node.js library for the https://mailpace.com transactional email API 9 | 10 | ## Quick Start 11 | 12 | ### Pre-requisites 13 | 14 | First you will need to retrieve your API token for your sending domain from [MailPace](https://app.mailpace.com). You can find it under Organization -> Domain -> API Tokens 15 | 16 | Your domain must have completed DKIM authorization and have an active plan to send emails. 17 | 18 | ### Installation 19 | 20 | If using NPM 21 | 22 | `npm install --save @mailpace/mailpace.js` 23 | 24 | If using Yarn 25 | 26 | `yarn add @mailpace/mailpace.js` 27 | 28 | ### Sending an email 29 | 30 | ```javascript 31 | const MailPace = require('@mailpace/mailpace.js'); 32 | const client = new MailPace.DomainClient('API_TOKEN_HERE'); 33 | 34 | client 35 | .sendEmail({ 36 | from: 'test@test.com', 37 | to: 'test@test.com', 38 | subject: 'test', 39 | htmlbody: '

HTML Email

', 40 | }) 41 | .then((r) => { 42 | console.log(r); 43 | }); 44 | ``` 45 | 46 | ## Documentation 47 | 48 | See [the ./docs folder](https://github.com/mailpace/mailpace.js/tree/master/docs) for documentation and options 49 | 50 | To regenerate the documentation, run `yarn docs:md` 51 | 52 | ## Issues, Support & Contributions 53 | 54 | If you have any difficulties please contact support@mailpace.com or open an issue on github. 55 | 56 | Contributions are always welcome 57 | 58 | ## License 59 | 60 | MIT 61 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | @mailpace/mailpace.js / [Exports](modules.md) 2 | 3 | # MailPace Node.js Library 4 | 5 | [![](https://circleci.com/gh/mailpace/mailpace.js.svg?style=svg)](https://app.circleci.com/pipelines/github/mailpace/mailpace.js) 6 | [![codecov](https://codecov.io/gh/mailpace/mailpace.js/branch/master/graph/badge.svg?token=QDLVU2JGyD)](https://codecov.io/gh/mailpace/mailpace.js) 7 | [![npm version](https://badge.fury.io/js/%40mailpace%2Fmailpace.js.svg)](https://badge.fury.io/js/%40mailpace%2Fmailpace.js) 8 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 9 | 10 | This is the official Node.js library for the https://mailpace.com transactional email API 11 | 12 | ## Quick Start 13 | 14 | ### Pre-requisites 15 | 16 | First you will need to retrieve your API token for your sending domain from [MailPace](https://app.mailpace.com). You can find it under Organization -> Domain -> API Tokens 17 | 18 | Your domain must have completed DKIM authorization and have an active plan to send emails. 19 | 20 | ### Installation 21 | 22 | If using NPM 23 | 24 | `npm install --save @mailpace/mailpace.js` 25 | 26 | If using Yarn 27 | 28 | `yarn add @mailpace/mailpace.js` 29 | 30 | ### Sending an email 31 | 32 | ```javascript 33 | const MailPace = require('@mailpace/mailpace.js'); 34 | const client = new MailPace.DomainClient('API_TOKEN_HERE'); 35 | 36 | client 37 | .sendEmail({ 38 | from: 'test@test.com', 39 | to: 'test@test.com', 40 | subject: 'test', 41 | htmlbody: '

HTML Email

', 42 | }) 43 | .then((r) => { 44 | console.log(r); 45 | }); 46 | ``` 47 | 48 | ## Documentation 49 | 50 | See [the ./docs folder](https://github.com/mailpace/mailpace.js/tree/master/docs) for documentation and options 51 | 52 | To regenerate the documentation, run `yarn docs:md` 53 | 54 | ## Issues, Support & Contributions 55 | 56 | If you have any difficulties please contact support@mailpace.com or open an issue on github. 57 | 58 | Contributions are always welcome 59 | 60 | ## License 61 | 62 | MIT 63 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2017", 5 | "outDir": "build/main", 6 | "rootDir": "src", 7 | "moduleResolution": "node", 8 | "module": "commonjs", 9 | "declaration": true, 10 | "inlineSourceMap": true, 11 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 12 | "resolveJsonModule": true /* Include modules imported with .json extension. */, 13 | 14 | // "strict": true /* Enable all strict type-checking options. */, 15 | 16 | /* Strict Type-Checking Options */ 17 | // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 18 | // "strictNullChecks": true /* Enable strict null checks. */, 19 | // "strictFunctionTypes": true /* Enable strict checking of function types. */, 20 | // "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 21 | // "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 22 | // "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 23 | 24 | /* Additional Checks */ 25 | "noUnusedLocals": true /* Report errors on unused locals. */, 26 | "noUnusedParameters": true /* Report errors on unused parameters. */, 27 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 28 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 29 | 30 | /* Debugging Options */ 31 | "traceResolution": false /* Report module resolution log messages. */, 32 | "listEmittedFiles": false /* Print names of generated files part of the compilation. */, 33 | "listFiles": false /* Print names of files part of the compilation. */, 34 | "pretty": true /* Stylize errors and messages using color and context. */, 35 | 36 | /* Experimental Options */ 37 | // "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 38 | // "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 39 | 40 | "lib": ["es2017"], 41 | "types": ["node"], 42 | "typeRoots": ["node_modules/@types", "src/types"] 43 | }, 44 | "include": ["src/**/*.ts"], 45 | "exclude": ["node_modules/**"], 46 | "compileOnSave": false 47 | } 48 | -------------------------------------------------------------------------------- /src/lib/models/Errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Standard Oms error on which all sub-errors are based. 3 | */ 4 | export class OmsError extends Error { 5 | public readonly statusCode: number; 6 | 7 | constructor(message: string, statusCode = 0) { 8 | super(message); 9 | this.statusCode = statusCode; 10 | 11 | // this is mandatory due: 12 | // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work 13 | Object.setPrototypeOf(this, OmsError.prototype); 14 | this.setUpStackTrace(); 15 | } 16 | 17 | protected setUpStackTrace() { 18 | this.name = this.constructor.name; 19 | Error.captureStackTrace(this, this.constructor); 20 | } 21 | } 22 | 23 | export class HttpError extends OmsError { 24 | constructor(message: string, statusCode: number) { 25 | super(message, statusCode); 26 | Object.setPrototypeOf(this, HttpError.prototype); 27 | this.setUpStackTrace(); 28 | } 29 | } 30 | 31 | export class InvalidAPIKey extends HttpError { 32 | constructor(message: string, statusCode: number) { 33 | super(message, statusCode); 34 | Object.setPrototypeOf(this, InvalidAPIKey.prototype); 35 | this.setUpStackTrace(); 36 | } 37 | } 38 | 39 | export class UnauthorizedDomain extends HttpError { 40 | constructor(message: string, statusCode: number) { 41 | super(message, statusCode); 42 | Object.setPrototypeOf(this, UnauthorizedDomain.prototype); 43 | this.setUpStackTrace(); 44 | } 45 | } 46 | 47 | export class InvalidRequest extends HttpError { 48 | constructor(message: string, statusCode: number) { 49 | super(message, statusCode); 50 | Object.setPrototypeOf(this, InvalidRequest.prototype); 51 | this.setUpStackTrace(); 52 | } 53 | } 54 | 55 | export class InternalServerError extends HttpError { 56 | constructor(message: string, statusCode: number) { 57 | super(message, statusCode); 58 | Object.setPrototypeOf(this, InternalServerError.prototype); 59 | this.setUpStackTrace(); 60 | } 61 | } 62 | 63 | export class ServiceUnavailableError extends HttpError { 64 | constructor(message: string, statusCode: number) { 65 | super(message, statusCode); 66 | Object.setPrototypeOf(this, ServiceUnavailableError.prototype); 67 | this.setUpStackTrace(); 68 | } 69 | } 70 | 71 | export class UnknownError extends HttpError { 72 | constructor(message: string, statusCode: number) { 73 | super(message, statusCode); 74 | Object.setPrototypeOf(this, UnknownError.prototype); 75 | this.setUpStackTrace(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/lib/ErrorHandler.ts: -------------------------------------------------------------------------------- 1 | import { AxiosError, AxiosResponse } from 'axios'; 2 | 3 | import * as Errors from './models/Errors'; 4 | import { ErrorResponse } from './types/Responses'; 5 | 6 | /** 7 | * Handles three different error types: 8 | * - Unknown 9 | * - Single error (from API) 10 | * - Array of errors (from API) 11 | */ 12 | export class ErrorHandler { 13 | /** 14 | * Handle generic and Axios errors 15 | * 16 | * @param error - Axios error 17 | * 18 | * @return {OmsError} - Oms error 19 | */ 20 | public buildRequestError(error: AxiosError): Errors.OmsError { 21 | const response: AxiosResponse | undefined = error.response; 22 | 23 | if (response !== undefined) { 24 | return this.buildAxiosError(response, error.message); 25 | } else { 26 | return new Errors.OmsError(error.message); 27 | } 28 | } 29 | 30 | /** 31 | * Prepare the axios based error 32 | * 33 | * @param {AxiosResponse} response - Error from Axios library 34 | * @return {OmsError} - cleaned up error 35 | */ 36 | private buildAxiosError( 37 | response: AxiosResponse, 38 | errorMessage: string 39 | ): Errors.OmsError { 40 | const data: ErrorResponse = response.data || {}; 41 | const status = this.retrieveDefaultOrValue(0, response.status); 42 | const message = this.retrieveDefaultOrValue( 43 | errorMessage, 44 | data.error || JSON.stringify(data.errors) 45 | ); 46 | 47 | return this.mapErrorToStatus(message, status); 48 | } 49 | 50 | private retrieveDefaultOrValue(defaultValue: T, data: T): T { 51 | return data === undefined ? defaultValue : data; 52 | } 53 | 54 | /** 55 | * Map the error based on status code to the correct error type 56 | * 57 | * @param error - Axios error 58 | * 59 | * @returns correct error type based on the status code 60 | */ 61 | private mapErrorToStatus( 62 | errorMessage: string, 63 | errorStatusCode: number 64 | ): Errors.HttpError { 65 | switch (errorStatusCode) { 66 | case 400: 67 | return new Errors.OmsError(errorMessage, errorStatusCode); 68 | 69 | case 401: 70 | return new Errors.InvalidAPIKey(errorMessage, errorStatusCode); 71 | 72 | case 403: 73 | return new Errors.UnauthorizedDomain(errorMessage, errorStatusCode); 74 | 75 | case 406: 76 | return new Errors.InvalidRequest(errorMessage, errorStatusCode); 77 | 78 | case 500: 79 | return new Errors.InternalServerError(errorMessage, errorStatusCode); 80 | 81 | case 503: 82 | return new Errors.ServiceUnavailableError( 83 | errorMessage, 84 | errorStatusCode 85 | ); 86 | 87 | default: 88 | return new Errors.UnknownError(errorMessage, errorStatusCode); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mailpace/mailpace.js", 3 | "version": "0.1.3", 4 | "description": "The official Node.js library for the https://mailpace.com API", 5 | "main": "build/main/index.js", 6 | "typings": "build/main/index.d.ts", 7 | "module": "build/module/index.js", 8 | "repository": "https://github.com/mailpace/mailpace.js", 9 | "license": "MIT", 10 | "keywords": [], 11 | "scripts": { 12 | "build": "run-p build:*", 13 | "build:main": "tsc -p tsconfig.json", 14 | "build:module": "tsc -p tsconfig.module.json", 15 | "fix": "run-s fix:*", 16 | "fix:prettier": "prettier \"src/**/*.ts\" --write", 17 | "fix:lint": "eslint src --ext .ts --fix", 18 | "test": "run-s build test:*", 19 | "test:lint": "eslint src --ext .ts", 20 | "test:prettier": "prettier \"src/**/*.ts\" --list-different", 21 | "test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"", 22 | "test:unit": "nyc --silent ava", 23 | "check-cli": "run-s test diff-integration-tests check-integration-tests", 24 | "check-integration-tests": "run-s check-integration-test:*", 25 | "diff-integration-tests": "mkdir -p diff && rm -rf diff/test && cp -r test diff/test && rm -rf diff/test/test-*/.git && cd diff && git init --quiet && git add -A && git commit --quiet --no-verify --allow-empty -m 'WIP' && echo '\\n\\nCommitted most recent integration test output in the \"diff\" directory. Review the changes with \"cd diff && git diff HEAD\" or your preferred git diff viewer.'", 26 | "watch:build": "tsc -p tsconfig.json -w", 27 | "watch:test": "nyc --silent ava --watch", 28 | "cov": "run-s build test:unit cov:html cov:lcov && open-cli coverage/index.html", 29 | "cov:html": "nyc report --reporter=html", 30 | "cov:lcov": "nyc report --reporter=lcov", 31 | "cov:send": "run-s cov:lcov && codecov", 32 | "cov:check": "nyc report && nyc check-coverage --lines 100 --functions 100 --branches 100", 33 | "doc": "run-s doc:html && open-cli build/docs/index.html", 34 | "doc:html": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --out build/docs", 35 | "doc:json": "typedoc src/ --exclude **/*.spec.ts --target ES6 --mode file --json build/docs/typedoc.json", 36 | "doc:md": "typedoc --out docs src/index.ts", 37 | "doc:publish": "gh-pages -m \"[ci skip] Updates\" -d build/docs", 38 | "version": "standard-version", 39 | "reset-hard": "git clean -dfx && git reset --hard && npm i", 40 | "prepare-release": "run-s reset-hard test cov:check doc:html version doc:publish" 41 | }, 42 | "engines": { 43 | "node": ">=10" 44 | }, 45 | "dependencies": { 46 | "@types/node": "^14.14.16", 47 | "axios": "^0.21.1", 48 | "ts-node": "^9.0.0" 49 | }, 50 | "devDependencies": { 51 | "@ava/typescript": "^1.1.1", 52 | "@bitauth/libauth": "^1.17.1", 53 | "@istanbuljs/nyc-config-typescript": "^1.0.1", 54 | "@typescript-eslint/eslint-plugin": "^4.0.1", 55 | "@typescript-eslint/parser": "^4.0.1", 56 | "ava": "^3.12.1", 57 | "axios-mock-adapter": "^1.19.0", 58 | "codecov": "^3.5.0", 59 | "cspell": "^4.1.0", 60 | "cz-conventional-changelog": "^3.3.0", 61 | "eslint": "^7.8.0", 62 | "eslint-config-prettier": "^6.11.0", 63 | "eslint-plugin-eslint-comments": "^3.2.0", 64 | "eslint-plugin-functional": "^3.0.2", 65 | "eslint-plugin-import": "^2.22.0", 66 | "gh-pages": "^3.1.0", 67 | "npm-run-all": "^4.1.5", 68 | "nyc": "^15.1.0", 69 | "open-cli": "^6.0.1", 70 | "prettier": "^2.1.1", 71 | "standard-version": "^9.0.0", 72 | "typedoc": "0.21.0", 73 | "typedoc-plugin-markdown": "3.10.0", 74 | "typescript": "^4.2.0" 75 | }, 76 | "files": [ 77 | "build/main", 78 | "build/module", 79 | "!**/*.spec.*", 80 | "!**/*.json", 81 | "CHANGELOG.md", 82 | "LICENSE", 83 | "README.md" 84 | ], 85 | "ava": { 86 | "failFast": true, 87 | "timeout": "60s", 88 | "typescript": { 89 | "rewritePaths": { 90 | "src/": "build/main/" 91 | } 92 | }, 93 | "files": [ 94 | "!build/module/**" 95 | ] 96 | }, 97 | "config": { 98 | "commitizen": { 99 | "path": "cz-conventional-changelog" 100 | } 101 | }, 102 | "prettier": { 103 | "singleQuote": true 104 | }, 105 | "nyc": { 106 | "extends": "@istanbuljs/nyc-config-typescript", 107 | "exclude": [ 108 | "**/*.spec.js" 109 | ] 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /docs/classes/domainclient.md: -------------------------------------------------------------------------------- 1 | [@mailpace/mailpace.js](../README.md) / [Exports](../modules.md) / DomainClient 2 | 3 | # Class: DomainClient 4 | 5 | Client class that can be used to interact with an MailPace Domain 6 | 7 | ## Hierarchy 8 | 9 | - `BaseClient` 10 | 11 | ↳ **DomainClient** 12 | 13 | ## Table of contents 14 | 15 | ### Constructors 16 | 17 | - [constructor](domainclient.md#constructor) 18 | 19 | ### Properties 20 | 21 | - [clientVersion](domainclient.md#clientversion) 22 | - [errorHandler](domainclient.md#errorhandler) 23 | - [httpClient](domainclient.md#httpclient) 24 | - [DefaultOptions](domainclient.md#defaultoptions) 25 | 26 | ### Methods 27 | 28 | - [getOptions](domainclient.md#getoptions) 29 | - [prepareHeaders](domainclient.md#prepareheaders) 30 | - [processRequestWithBody](domainclient.md#processrequestwithbody) 31 | - [sendEmail](domainclient.md#sendemail) 32 | 33 | ## Constructors 34 | 35 | ### constructor 36 | 37 | • **new DomainClient**(`domainToken`, `configOptions?`) 38 | 39 | Create a client for sending emails from a domain 40 | 41 | #### Parameters 42 | 43 | | Name | Type | Description | 44 | | :------ | :------ | :------ | 45 | | `domainToken` | `string` | The API token for the domain | 46 | | `configOptions?` | `Configuration` | Configuration options for accessing the API | 47 | 48 | #### Overrides 49 | 50 | BaseClient.constructor 51 | 52 | #### Defined in 53 | 54 | [lib/DomainClient.ts:10](https://github.com/mailpace/mailpace.js/blob/0ed46d8/src/lib/DomainClient.ts#L10) 55 | 56 | ## Properties 57 | 58 | ### clientVersion 59 | 60 | • `Readonly` **clientVersion**: `string` 61 | 62 | #### Inherited from 63 | 64 | BaseClient.clientVersion 65 | 66 | #### Defined in 67 | 68 | [lib/BaseClient.ts:22](https://github.com/mailpace/mailpace.js/blob/0ed46d8/src/lib/BaseClient.ts#L22) 69 | 70 | ___ 71 | 72 | ### errorHandler 73 | 74 | • `Protected` `Readonly` **errorHandler**: `ErrorHandler` 75 | 76 | #### Inherited from 77 | 78 | BaseClient.errorHandler 79 | 80 | #### Defined in 81 | 82 | [lib/BaseClient.ts:24](https://github.com/mailpace/mailpace.js/blob/0ed46d8/src/lib/BaseClient.ts#L24) 83 | 84 | ___ 85 | 86 | ### httpClient 87 | 88 | • `Readonly` **httpClient**: `AxiosInstance` 89 | 90 | #### Inherited from 91 | 92 | BaseClient.httpClient 93 | 94 | #### Defined in 95 | 96 | [lib/BaseClient.ts:23](https://github.com/mailpace/mailpace.js/blob/0ed46d8/src/lib/BaseClient.ts#L23) 97 | 98 | ___ 99 | 100 | ### DefaultOptions 101 | 102 | ▪ `Static` `Readonly` **DefaultOptions**: `Configuration` 103 | 104 | Default options 105 | 106 | #### Inherited from 107 | 108 | BaseClient.DefaultOptions 109 | 110 | #### Defined in 111 | 112 | [lib/BaseClient.ts:17](https://github.com/mailpace/mailpace.js/blob/0ed46d8/src/lib/BaseClient.ts#L17) 113 | 114 | ## Methods 115 | 116 | ### getOptions 117 | 118 | ▸ **getOptions**(): `Configuration` 119 | 120 | #### Returns 121 | 122 | `Configuration` 123 | 124 | #### Inherited from 125 | 126 | BaseClient.getOptions 127 | 128 | #### Defined in 129 | 130 | [lib/BaseClient.ts:42](https://github.com/mailpace/mailpace.js/blob/0ed46d8/src/lib/BaseClient.ts#L42) 131 | 132 | ___ 133 | 134 | ### prepareHeaders 135 | 136 | ▸ **prepareHeaders**(): `object` 137 | 138 | Prepare the default HTTP Request Headers 139 | 140 | #### Returns 141 | 142 | `object` 143 | 144 | #### Inherited from 145 | 146 | BaseClient.prepareHeaders 147 | 148 | #### Defined in 149 | 150 | [lib/BaseClient.ts:49](https://github.com/mailpace/mailpace.js/blob/0ed46d8/src/lib/BaseClient.ts#L49) 151 | 152 | ___ 153 | 154 | ### processRequestWithBody 155 | 156 | ▸ `Protected` **processRequestWithBody**(`method`, `path`, `body`, `callback?`): `Promise` 157 | 158 | Prepare the request and send on 159 | 160 | **`see`** processRequest for more details. 161 | 162 | #### Type parameters 163 | 164 | | Name | 165 | | :------ | 166 | | `T` | 167 | 168 | #### Parameters 169 | 170 | | Name | Type | 171 | | :------ | :------ | 172 | | `method` | `POST` | 173 | | `path` | `string` | 174 | | `body` | `object` | 175 | | `callback?` | `Callback` | 176 | 177 | #### Returns 178 | 179 | `Promise` 180 | 181 | #### Inherited from 182 | 183 | BaseClient.processRequestWithBody 184 | 185 | #### Defined in 186 | 187 | [lib/BaseClient.ts:63](https://github.com/mailpace/mailpace.js/blob/0ed46d8/src/lib/BaseClient.ts#L63) 188 | 189 | ___ 190 | 191 | ### sendEmail 192 | 193 | ▸ **sendEmail**(`email`, `callback?`): `Promise` 194 | 195 | Send a single email message through the API 196 | 197 | #### Parameters 198 | 199 | | Name | Type | Description | 200 | | :------ | :------ | :------ | 201 | | `email` | `Message` | - | 202 | | `callback?` | `Callback` | A callback that if provided will be called after sending the email is complete | 203 | 204 | #### Returns 205 | 206 | `Promise` 207 | 208 | A promise that will resolve when the API responds (or an error occurs) 209 | 210 | #### Defined in 211 | 212 | [lib/DomainClient.ts:27](https://github.com/mailpace/mailpace.js/blob/0ed46d8/src/lib/DomainClient.ts#L27) 213 | -------------------------------------------------------------------------------- /src/lib/BaseClient.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosError, AxiosInstance } from 'axios'; 2 | 3 | import { ErrorHandler } from './ErrorHandler'; 4 | import { Options } from './models/Options'; 5 | import { Callback } from './types/Callback'; 6 | 7 | const CLIENT_VERSION = '0.0.13'; // TODO: use library version in package.json 8 | const API_VERSION = 'v1'; // TODO: make this a client option 9 | 10 | /** 11 | * Base client 12 | */ 13 | export default abstract class BaseClient { 14 | /** 15 | * Default options 16 | */ 17 | public static readonly DefaultOptions: Options.Configuration = { 18 | requestHost: `app.mailpace.com/api/${API_VERSION}/`, 19 | timeout: 60, 20 | }; 21 | 22 | public readonly clientVersion: string; 23 | public readonly httpClient: AxiosInstance; 24 | protected readonly errorHandler: ErrorHandler; 25 | private readonly Options: Options.Configuration; 26 | private readonly authHeader: string; 27 | private readonly token: string; 28 | 29 | protected constructor( 30 | token: string, 31 | authHeader: string, 32 | configOptions?: Options.Configuration 33 | ) { 34 | this.clientVersion = CLIENT_VERSION; 35 | this.token = token.trim(); 36 | this.authHeader = authHeader; 37 | this.Options = { ...BaseClient.DefaultOptions, ...configOptions }; 38 | this.httpClient = this.buildHttpClient(); 39 | this.errorHandler = new ErrorHandler(); 40 | } 41 | 42 | public getOptions(): Options.Configuration { 43 | return this.Options; 44 | } 45 | 46 | /** 47 | * Prepare the default HTTP Request Headers 48 | */ 49 | public prepareHeaders(): object { 50 | return { 51 | [this.authHeader]: this.token, 52 | Accept: 'application/json', 53 | 'Content-Type': 'application/json', 54 | 'User-Agent': `mailpace.js - ${this.clientVersion}`, 55 | }; 56 | } 57 | 58 | /** 59 | * Prepare the request and send on 60 | * 61 | * @see processRequest for more details. 62 | */ 63 | protected processRequestWithBody( 64 | method: Options.HttpMethod, 65 | path: string, 66 | body: null | object, 67 | callback?: Callback 68 | ): Promise { 69 | return this.processRequest(method, path, {}, body, callback); 70 | } 71 | 72 | /** 73 | * Send HTTP request via Axios 74 | * 75 | * @param method - see processHttpRequest for details 76 | * @param path - see processHttpRequest for details 77 | * @param queryParameters - see processHttpRequest for details 78 | * @param body - see processHttpRequest for details 79 | * @param callback - callback function to be executed. 80 | * 81 | * @returns A promise that will complete when the API responds (or an error occurs). 82 | */ 83 | private processRequest( 84 | method: Options.HttpMethod, 85 | path: string, 86 | queryParameters: object, 87 | body: null | object, 88 | callback?: Callback 89 | ): Promise { 90 | const httpRequest: Promise = this.processHttpRequest( 91 | method, 92 | path, 93 | queryParameters, 94 | body 95 | ); 96 | this.processCallbackRequest(httpRequest, callback); 97 | return httpRequest; 98 | } 99 | 100 | /** 101 | * Process HTTP request 102 | * 103 | * @param method - Which type of http request will be executed. 104 | * @param path - API URL endpoint. 105 | * @param queryParameters - Querystring parameters used for http request. 106 | * @param body - Data sent with http request. 107 | * 108 | * @returns A promise that will complete when the API responds (or an error occurs). 109 | */ 110 | private processHttpRequest( 111 | method: Options.HttpMethod, 112 | path: string, 113 | queryParameters: object, 114 | body: null | object 115 | ): Promise { 116 | return this.httpRequest(method, path, queryParameters, body) 117 | .then((response) => response) 118 | .catch((error: AxiosError) => { 119 | throw this.errorHandler.buildRequestError(error); 120 | }); 121 | } 122 | 123 | /** 124 | * Process callback function for HTTP request. 125 | * 126 | * @param httpRequest - HTTP request for which callback will be executed 127 | * @param callback - callback function to be executed. 128 | */ 129 | private processCallbackRequest( 130 | httpRequest: Promise, 131 | callback?: Callback 132 | ): void { 133 | if (callback) { 134 | httpRequest 135 | .then((response) => callback(null, response)) 136 | .catch((error) => callback(error, null)); 137 | } 138 | } 139 | 140 | /** 141 | * Send request 142 | * 143 | * @param method - Which type of http request will be executed. 144 | * @param path - API URL endpoint. 145 | * @param queryParameters - Querystring parameters used for http request. 146 | * @param body - Data sent with http request. 147 | */ 148 | private httpRequest( 149 | method: Options.HttpMethod, 150 | path: string, 151 | queryParameters: {} | object, 152 | body: null | object 153 | ): Promise { 154 | return this.httpClient.request({ 155 | method, 156 | url: path, 157 | data: body, 158 | headers: this.prepareHeaders(), 159 | params: queryParameters, 160 | }); 161 | } 162 | 163 | /** 164 | * Create http client for making requests 165 | * 166 | * @return {AxiosInstance} 167 | */ 168 | private buildHttpClient(): AxiosInstance { 169 | const httpClient = axios.create({ 170 | baseURL: `https://${this.Options.requestHost}`, 171 | timeout: this.Options.timeout * 1000, 172 | responseType: 'json', 173 | maxContentLength: Infinity, 174 | maxBodyLength: 30 * 1024 * 1024, 175 | validateStatus(status) { 176 | return status >= 200 && status < 300; 177 | }, 178 | }); 179 | 180 | httpClient.interceptors.response.use((response) => response.data); 181 | return httpClient; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | // These are basically integration tests 2 | import test from 'ava'; 3 | import axios from 'axios'; 4 | import MockAdapter from 'axios-mock-adapter'; 5 | 6 | // Mock the send endpoint 7 | const mock = new MockAdapter(axios); 8 | const successResponse = { id: 0, status: 'pending' }; 9 | 10 | const exampleEmail = { 11 | from: 'test@test.com', 12 | to: 'test@test.com', 13 | subject: 'test', 14 | htmlbody: '

Hi

', 15 | }; 16 | 17 | import * as mailpace from './index'; 18 | const client = new mailpace.DomainClient('token'); 19 | 20 | test.afterEach.always(() => { 21 | mock.reset(); 22 | }); 23 | 24 | test.serial('calling sendEmail responds to 200', async (t) => { 25 | mock.onPost('/send').reply(200, successResponse); 26 | const response = await client.sendEmail(exampleEmail); 27 | t.deepEqual(response, successResponse); 28 | }); 29 | 30 | test.serial( 31 | 'calling sendEmail when the server is down throws an error', 32 | async (t) => { 33 | mock.onPost('/send').reply(500, {}); 34 | const error = await t.throwsAsync(() => client.sendEmail(exampleEmail)); 35 | t.is(error.message, 'Request failed with status code 500'); 36 | } 37 | ); 38 | 39 | test.serial('the callback is called on success', async (t) => { 40 | mock.onPost('/send').reply(200, successResponse); 41 | await client.sendEmail(exampleEmail, function () { 42 | t.pass(); 43 | }); 44 | }); 45 | 46 | test.serial('the callback is called when there is an error', async (t) => { 47 | mock.onPost('/send').timeout(); 48 | const error = await t.throwsAsync(() => 49 | client.sendEmail(exampleEmail, () => { 50 | t.pass(); 51 | }) 52 | ); 53 | t.is(error.message, 'timeout of 60000ms exceeded'); 54 | }); 55 | 56 | test.serial('handles 400 errors', async (t) => { 57 | mock.onPost('/send').reply(400, {}); 58 | const error = await t.throwsAsync(() => client.sendEmail(exampleEmail)); 59 | t.is(error.message, 'Request failed with status code 400'); 60 | }); 61 | 62 | test.serial('handles 401 errors', async (t) => { 63 | mock.onPost('/send').reply(401, {}); 64 | const error = await t.throwsAsync(() => client.sendEmail(exampleEmail)); 65 | t.is(error.message, 'Request failed with status code 401'); 66 | }); 67 | 68 | test.serial('handles 403 errors', async (t) => { 69 | mock.onPost('/send').reply(403, {}); 70 | const error = await t.throwsAsync(() => client.sendEmail(exampleEmail)); 71 | t.is(error.message, 'Request failed with status code 403'); 72 | }); 73 | 74 | test.serial('handles 404 errors', async (t) => { 75 | mock.onPost('/send').reply(404, {}); 76 | const error = await t.throwsAsync(() => client.sendEmail(exampleEmail)); 77 | t.is(error.message, 'Request failed with status code 404'); 78 | }); 79 | 80 | test.serial('handles 406 errors', async (t) => { 81 | mock.onPost('/send').reply(406, {}); 82 | const error = await t.throwsAsync(() => client.sendEmail(exampleEmail)); 83 | t.is(error.message, 'Request failed with status code 406'); 84 | }); 85 | 86 | test.serial('handles 503 errors', async (t) => { 87 | mock.onPost('/send').reply(503, {}); 88 | const error = await t.throwsAsync(() => client.sendEmail(exampleEmail)); 89 | t.is(error.message, 'Request failed with status code 503'); 90 | }); 91 | 92 | test.serial('handles other errors', async (t) => { 93 | mock.onPost('/send').reply(418); // I'm a teapot 94 | const error = await t.throwsAsync(() => client.sendEmail(exampleEmail)); 95 | t.is(error.message, 'Request failed with status code 418'); 96 | }); 97 | 98 | test.serial('can handle attachments', async (t) => { 99 | const exampleAttachment = { 100 | name: 'hello.pdf', 101 | cid: 'hello', 102 | content: 'test', 103 | content_type: 'test/test', 104 | }; 105 | const emailWithAttachments = Object.assign( 106 | { attachments: [exampleAttachment, exampleAttachment] }, 107 | exampleEmail 108 | ); 109 | mock.onPost('/send').reply(200, successResponse); 110 | const response = await client.sendEmail(emailWithAttachments); 111 | t.deepEqual(response, successResponse); 112 | }); 113 | 114 | test.serial('can handle single tags', async (t) => { 115 | const emailWithTags = Object.assign({ tags: 'a single tag' }, exampleEmail); 116 | mock.onPost('/send').reply(200, successResponse); 117 | const response = await client.sendEmail(emailWithTags); 118 | const request = JSON.parse(mock.history.post[0].data); 119 | t.deepEqual(response, successResponse); 120 | t.deepEqual(request.tags, 'a single tag'); 121 | }); 122 | 123 | test.serial('can handle array of tags', async (t) => { 124 | const emailWithTags = Object.assign( 125 | { tags: ['array', 'of', 'tags'] }, 126 | exampleEmail 127 | ); 128 | mock.onPost('/send').reply(200, successResponse); 129 | const response = await client.sendEmail(emailWithTags); 130 | const request = JSON.parse(mock.history.post[0].data); 131 | t.deepEqual(response, successResponse); 132 | t.deepEqual(request.tags, ['array', 'of', 'tags']); 133 | }); 134 | 135 | test.serial('can handle list-unsubscribe headers', async (t) => { 136 | const emailWithListUnsubscribe = Object.assign( 137 | { 138 | list_unsubscribe: 139 | ', ', 140 | }, 141 | exampleEmail 142 | ); 143 | mock.onPost('/send').reply(200, successResponse); 144 | const response = await client.sendEmail(emailWithListUnsubscribe); 145 | t.deepEqual(response, successResponse); 146 | }); 147 | 148 | test.serial('can handle in-reply-to', async (t) => { 149 | const emailWithInReplyTo = Object.assign( 150 | { 151 | inreplyto: '', 152 | }, 153 | exampleEmail 154 | ); 155 | mock.onPost('/send').reply(200, successResponse); 156 | const response = await client.sendEmail(emailWithInReplyTo); 157 | t.deepEqual(response, successResponse); 158 | }); 159 | 160 | test.serial('can handle references', async (t) => { 161 | const emailWithReferences = Object.assign( 162 | { 163 | references: ', ', 164 | }, 165 | exampleEmail 166 | ); 167 | mock.onPost('/send').reply(200, successResponse); 168 | const response = await client.sendEmail(emailWithReferences); 169 | t.deepEqual(response, successResponse); 170 | }); 171 | --------------------------------------------------------------------------------