-
-
Notifications
You must be signed in to change notification settings - Fork 459
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(connector): added postmark connector
- Loading branch information
Showing
10 changed files
with
868 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@logto/connector-postmark": major | ||
--- | ||
|
||
add postmark connector |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# Postmark connector | ||
|
||
Logto connector for Postmark email service. | ||
|
||
## Get started | ||
|
||
Postmark is a mail platform for transactional and marketing email. We can use its email sending function to send a _verification code_. | ||
|
||
## Register Postmark account | ||
|
||
Create a new account at [Postmark website](https://postmark.com/). You may skip this step if you've already got an account. | ||
|
||
## Configure your connector | ||
|
||
Fill out the `serverToken` field with the Server Token you find under settings for your | ||
server in Postmark. | ||
|
||
Fill out the `fromEmail` field with the senders' _From Address_. | ||
|
||
In order to enable full user flows, templates with usageType `Register`, `SignIn`, `ForgotPassword` and `Generic` are required | ||
|
||
Here is an example of Postmark connector template JSON. | ||
|
||
```jsonc | ||
[ | ||
{ | ||
"usageType": "Register", | ||
"templateAlias": "logto-register" | ||
}, | ||
{ | ||
"usageType": "SignIn", | ||
"templateAlias": "logto-sign-in" | ||
}, | ||
{ | ||
"usageType": "ForgotPassword", | ||
"templateAlias": "logto-forgot-password" | ||
}, | ||
{ | ||
"usageType": "Generic", | ||
"templateAlias": "logto-generic" | ||
}, | ||
] | ||
``` | ||
|
||
## Test Postmark email connector | ||
|
||
You can type in an email address and click on "Send" to see whether the settings can work before "Save and Done". | ||
|
||
That's it. Don't forget to [Enable connector in sign-in experience](https://docs.logto.io/docs/tutorials/get-started/passwordless-sign-in-by-adding-connectors#enable-sms-or-email-passwordless-sign-in) | ||
|
||
## Config types | ||
|
||
| Name | Type | | ||
|-------------|-------------------| | ||
| serverToken | string | | ||
| fromEmail | string | | ||
|
||
| Template Properties | Type | Enum values | | ||
|---------------------|-------------|------------------------------------------------------| | ||
| usageType | enum string | 'Register' \| 'SignIn' \| 'ForgotPassword' \| 'Generic' | | ||
| templateAlias | string | N/A | |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
{ | ||
"name": "@logto/connector-postmark", | ||
"version": "1.0.0", | ||
"description": "Postmark connector implementation.", | ||
"author": "Sten Sandvik <stenrs@gmail.com>", | ||
"dependencies": { | ||
"@logto/connector-kit": "workspace:^4.0.0", | ||
"@silverhand/essentials": "^2.9.0", | ||
"postmark": "^4.0.2", | ||
"zod": "^3.22.4" | ||
}, | ||
"devDependencies": { | ||
"@rollup/plugin-commonjs": "^25.0.7", | ||
"@rollup/plugin-json": "^6.1.0", | ||
"@rollup/plugin-node-resolve": "^15.2.3", | ||
"@rollup/plugin-typescript": "^11.1.6", | ||
"@silverhand/eslint-config": "6.0.1", | ||
"@silverhand/ts-config": "6.0.0", | ||
"@types/node": "^20.11.20", | ||
"@types/nodemailer": "^6.4.7", | ||
"@types/supertest": "^6.0.2", | ||
"@vitest/coverage-v8": "^1.4.0", | ||
"eslint": "^8.56.0", | ||
"lint-staged": "^15.0.2", | ||
"nock": "^13.3.1", | ||
"prettier": "^3.0.0", | ||
"rollup": "^4.12.0", | ||
"rollup-plugin-output-size": "^1.3.0", | ||
"supertest": "^7.0.0", | ||
"typescript": "^5.3.3", | ||
"vitest": "^1.4.0" | ||
}, | ||
"main": "./lib/index.js", | ||
"module": "./lib/index.js", | ||
"exports": "./lib/index.js", | ||
"license": "MPL-2.0", | ||
"type": "module", | ||
"files": [ | ||
"lib", | ||
"docs", | ||
"logo.svg", | ||
"logo-dark.svg" | ||
], | ||
"scripts": { | ||
"precommit": "lint-staged", | ||
"check": "tsc --noEmit", | ||
"build": "tsup", | ||
"dev": "tsup --watch", | ||
"lint": "eslint --ext .ts src", | ||
"lint:report": "pnpm lint --format json --output-file report.json", | ||
"test": "vitest src", | ||
"test:ci": "pnpm run test --silent --coverage", | ||
"prepublishOnly": "pnpm build" | ||
}, | ||
"engines": { | ||
"node": "^20.9.0" | ||
}, | ||
"eslintConfig": { | ||
"extends": "@silverhand", | ||
"settings": { | ||
"import/core-modules": [ | ||
"@silverhand/essentials", | ||
"got", | ||
"nock", | ||
"snakecase-keys", | ||
"zod" | ||
] | ||
} | ||
}, | ||
"prettier": "@silverhand/eslint-config/.prettierrc", | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import type { ConnectorMetadata } from '@logto/connector-kit'; | ||
import { ConnectorConfigFormItemType } from '@logto/connector-kit'; | ||
|
||
export const defaultMetadata: ConnectorMetadata = { | ||
id: 'postmark-mail', | ||
target: 'postmark-mail', | ||
platform: null, | ||
name: { | ||
en: 'Postmark Mail', | ||
}, | ||
logo: './logo.svg', | ||
logoDark: null, | ||
description: { | ||
en: 'Postmark is a mail sending platform.', | ||
}, | ||
readme: './README.md', | ||
formItems: [ | ||
{ | ||
key: 'serverToken', | ||
label: 'Server Token', | ||
type: ConnectorConfigFormItemType.Text, | ||
required: true, | ||
placeholder: '<your-server-token>', | ||
}, | ||
{ | ||
key: 'fromEmail', | ||
label: 'From Email', | ||
type: ConnectorConfigFormItemType.Text, | ||
required: true, | ||
placeholder: '<from_email_address@your.domain>', | ||
}, | ||
{ | ||
key: 'templates', | ||
label: 'Templates', | ||
type: ConnectorConfigFormItemType.Json, | ||
required: true, | ||
defaultValue: [ | ||
{ | ||
usageType: 'SignIn', | ||
templateAlias: 'logto-sign-in', | ||
}, | ||
{ | ||
usageType: 'Register', | ||
templateAlias: 'logto-register', | ||
}, | ||
{ | ||
usageType: 'ForgotPassword', | ||
templateAlias: 'logto-forgot-password', | ||
}, | ||
{ | ||
usageType: 'Generic', | ||
templateAlias: 'logto-generic', | ||
}, | ||
], | ||
}, | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { TemplateType } from '@logto/connector-kit'; | ||
|
||
import { mockedConfig } from './mock.js'; | ||
|
||
const getConfig = vi.fn().mockResolvedValue(mockedConfig); | ||
const sendEmailWithTemplate = vi.fn(); | ||
vi.mock('postmark', () => ({ | ||
ServerClient: vi.fn(() => ({ | ||
sendEmailWithTemplate, | ||
})), | ||
})); | ||
|
||
const { default: createConnector } = await import('./index.js'); | ||
|
||
describe('Postmark connector', () => { | ||
it('init without throwing errors', async () => { | ||
await expect(createConnector({ getConfig })).resolves.not.toThrow(); | ||
}); | ||
|
||
describe('sendMessage()', () => { | ||
afterEach(() => { | ||
vi.clearAllMocks(); | ||
}); | ||
|
||
it('should call sendEmailWithTemplate() with correct template and content', async () => { | ||
const connector = await createConnector({ getConfig }); | ||
await connector.sendMessage({ | ||
to: 'to@email.com', | ||
type: TemplateType.SignIn, | ||
payload: { code: '1234' }, | ||
}); | ||
expect(sendEmailWithTemplate).toHaveBeenCalledWith( | ||
expect.objectContaining({ | ||
From: mockedConfig.fromEmail, | ||
TemplateAlias: 'logto-sign-in', | ||
To: 'to@email.com', | ||
TemplateModel: { code: '1234' }, | ||
}) | ||
); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { assert } from '@silverhand/essentials'; | ||
|
||
import type { | ||
GetConnectorConfig, | ||
CreateConnector, | ||
EmailConnector, | ||
SendMessageFunction, | ||
} from '@logto/connector-kit'; | ||
import { | ||
ConnectorError, | ||
ConnectorErrorCodes, | ||
validateConfig, | ||
ConnectorType, | ||
} from '@logto/connector-kit'; | ||
import { ServerClient } from 'postmark'; | ||
|
||
import { defaultMetadata } from './constant.js'; | ||
import { postmarkConfigGuard } from './types.js'; | ||
|
||
const sendMessage = | ||
(getConfig: GetConnectorConfig): SendMessageFunction => | ||
async (data, inputConfig) => { | ||
const { to, type, payload } = data; | ||
|
||
const config = inputConfig ?? (await getConfig(defaultMetadata.id)); | ||
validateConfig(config, postmarkConfigGuard); | ||
|
||
const { serverToken, fromEmail, templates } = config; | ||
const template = templates.find((template) => template.usageType === type); | ||
|
||
assert( | ||
template, | ||
new ConnectorError( | ||
ConnectorErrorCodes.TemplateNotFound, | ||
`Template not found for type: ${type}` | ||
) | ||
); | ||
|
||
const client = new ServerClient(serverToken); | ||
|
||
try { | ||
await client.sendEmailWithTemplate({ | ||
From: fromEmail, | ||
TemplateAlias: template.templateAlias, | ||
To: to, | ||
TemplateModel: payload, | ||
}); | ||
} catch (error: unknown) { | ||
throw new ConnectorError( | ||
ConnectorErrorCodes.General, | ||
error instanceof Error ? error.message : '' | ||
); | ||
} | ||
}; | ||
|
||
const createPostmarkConnector: CreateConnector<EmailConnector> = async ({ getConfig }) => { | ||
return { | ||
metadata: defaultMetadata, | ||
type: ConnectorType.Email, | ||
configGuard: postmarkConfigGuard, | ||
sendMessage: sendMessage(getConfig), | ||
}; | ||
}; | ||
|
||
export default createPostmarkConnector; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import type { PostmarkConfig } from './types.js'; | ||
|
||
export const mockedServerToken = 'serverToken'; | ||
|
||
export const mockedConfig: PostmarkConfig = { | ||
serverToken: mockedServerToken, | ||
fromEmail: 'noreply@logto.test.io', | ||
templates: [ | ||
{ | ||
usageType: 'SignIn', | ||
templateAlias: 'logto-sign-in', | ||
}, | ||
{ | ||
usageType: 'Register', | ||
templateAlias: 'logto-register', | ||
}, | ||
{ | ||
usageType: 'ForgotPassword', | ||
templateAlias: 'logto-forgot-password', | ||
}, | ||
{ | ||
usageType: 'Generic', | ||
templateAlias: 'logto-generic', | ||
}, | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { z } from 'zod'; | ||
|
||
/** | ||
* UsageType here is used to specify the use case of the template, can be either | ||
* 'Register', 'SignIn', 'ForgotPassword', 'Generic'. | ||
*/ | ||
const requiredTemplateUsageTypes = ['Register', 'SignIn', 'ForgotPassword', 'Generic']; | ||
|
||
const templateGuard = z.object({ | ||
usageType: z.string(), | ||
templateAlias: z.string(), | ||
}); | ||
|
||
export const postmarkConfigGuard = z.object({ | ||
serverToken: z.string(), | ||
fromEmail: z.string(), | ||
templates: z.array(templateGuard).refine( | ||
(templates) => | ||
requiredTemplateUsageTypes.every((requiredType) => | ||
templates.map((template) => template.usageType).includes(requiredType) | ||
), | ||
(templates) => ({ | ||
message: `Template with UsageType (${requiredTemplateUsageTypes | ||
.filter( | ||
(requiredType) => !templates.map((template) => template.usageType).includes(requiredType) | ||
) | ||
.join(', ')}) should be provided!`, | ||
}) | ||
), | ||
}); | ||
|
||
export type PostmarkConfig = z.infer<typeof postmarkConfigGuard>; |
Oops, something went wrong.