feat: implement resend rest email adapter (#5916)
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
"build:db-mongodb": "turbo build --filter db-mongodb",
|
||||
"build:db-postgres": "turbo build --filter db-postgres",
|
||||
"build:email-nodemailer": "turbo build --filter email-nodemailer",
|
||||
"build:email-resend-rest": "turbo build --filter email-resend-rest",
|
||||
"build:eslint-config-payload": "turbo build --filter eslint-config-payload",
|
||||
"build:graphql": "turbo build --filter graphql",
|
||||
"build:live-preview": "turbo build --filter live-preview",
|
||||
|
||||
10
packages/email-resend-rest/.eslintignore
Normal file
10
packages/email-resend-rest/.eslintignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
7
packages/email-resend-rest/.eslintrc.cjs
Normal file
7
packages/email-resend-rest/.eslintrc.cjs
Normal file
@@ -0,0 +1,7 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
}
|
||||
10
packages/email-resend-rest/.prettierignore
Normal file
10
packages/email-resend-rest/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
||||
.tmp
|
||||
**/.git
|
||||
**/.hg
|
||||
**/.pnp.*
|
||||
**/.svn
|
||||
**/.yarn/**
|
||||
**/build
|
||||
**/dist/**
|
||||
**/node_modules
|
||||
**/temp
|
||||
15
packages/email-resend-rest/.swcrc
Normal file
15
packages/email-resend-rest/.swcrc
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": true,
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "es6"
|
||||
}
|
||||
}
|
||||
19
packages/email-resend-rest/.swcrc-build
Normal file
19
packages/email-resend-rest/.swcrc-build
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": true,
|
||||
"exclude": [
|
||||
"/**/mocks",
|
||||
"/**/*.spec.ts"
|
||||
],
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "es6"
|
||||
}
|
||||
}
|
||||
22
packages/email-resend-rest/LICENSE.md
Normal file
22
packages/email-resend-rest/LICENSE.md
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2022 Payload CMS, LLC <info@payloadcms.com>
|
||||
Portions Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
30
packages/email-resend-rest/README.md
Normal file
30
packages/email-resend-rest/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Resend REST Email Adapter
|
||||
|
||||
This adapter allows you to send emails using the [Resend](https://resend.com) REST API.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @payloadcms/email-resend-rest`
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
- Sign up for a [Resend](https://resend.com) account
|
||||
- Set up a domain
|
||||
- Create an API key
|
||||
- Set API key as RESEND_API_KEY environment variable
|
||||
- Configure your Payload config
|
||||
|
||||
```ts
|
||||
// payload.config.js
|
||||
import { resendAdapter } from '@payloadcms/email-resend-rest'
|
||||
|
||||
export default buildConfig({
|
||||
email: resendAdapter({
|
||||
defaultFromAddress: 'dev@payloadcms.com',
|
||||
defaultFromName: 'Payload CMS',
|
||||
apiKey: process.env.RESEND_API_KEY || '',
|
||||
}),
|
||||
})
|
||||
```
|
||||
17
packages/email-resend-rest/jest.config.js
Normal file
17
packages/email-resend-rest/jest.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/** @type {import('jest').Config} */
|
||||
const customJestConfig = {
|
||||
rootDir: '.',
|
||||
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
||||
moduleNameMapper: {
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
},
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['<rootDir>/**/*spec.ts'],
|
||||
testTimeout: 10000,
|
||||
transform: {
|
||||
'^.+\\.(t|j)sx?$': ['@swc/jest'],
|
||||
},
|
||||
verbose: true,
|
||||
}
|
||||
|
||||
export default customJestConfig
|
||||
57
packages/email-resend-rest/package.json
Normal file
57
packages/email-resend-rest/package.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend-rest",
|
||||
"version": "0.0.0",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/payloadcms/payload.git",
|
||||
"directory": "packages/email-resend-rest"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "Payload CMS, Inc.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
"require": "./src/index.ts",
|
||||
"types": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "pnpm build:swc && pnpm build:types",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc-build",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build",
|
||||
"test": "jest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "29.5.12",
|
||||
"jest": "^29.7.0",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
}
|
||||
87
packages/email-resend-rest/src/email-resend.spec.ts
Normal file
87
packages/email-resend-rest/src/email-resend.spec.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { resendAdapter } from './index.js'
|
||||
import { Payload } from 'payload/types'
|
||||
|
||||
describe('email-resend', () => {
|
||||
const defaultFromAddress = 'dev@payloadcms.com'
|
||||
const defaultFromName = 'Payload CMS'
|
||||
const apiKey = 'test-api-key'
|
||||
const from = 'dev@payloadcms.com'
|
||||
const to = from
|
||||
const subject = 'This was sent on init'
|
||||
const text = 'This is my message body'
|
||||
|
||||
const mockPayload = {} as unknown as Payload
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should handle sending an email', async () => {
|
||||
global.fetch = jest.spyOn(global, 'fetch').mockImplementation(
|
||||
jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () => {
|
||||
return { id: 'test-id' }
|
||||
},
|
||||
}),
|
||||
) as jest.Mock,
|
||||
) as jest.Mock
|
||||
|
||||
const adapter = resendAdapter({
|
||||
defaultFromAddress,
|
||||
defaultFromName,
|
||||
apiKey,
|
||||
})
|
||||
|
||||
await adapter({ payload: mockPayload }).sendEmail({
|
||||
from,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
})
|
||||
|
||||
// @ts-expect-error
|
||||
expect(global.fetch.mock.calls[0][0]).toStrictEqual('https://api.resend.com/emails')
|
||||
// @ts-expect-error
|
||||
const request = global.fetch.mock.calls[0][1]
|
||||
expect(request.headers.Authorization).toStrictEqual(`Bearer ${apiKey}`)
|
||||
expect(JSON.parse(request.body)).toMatchObject({
|
||||
from,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw an error if the email fails to send', async () => {
|
||||
const errorResponse = {
|
||||
message: 'error information',
|
||||
name: 'validation_error',
|
||||
statusCode: 403,
|
||||
}
|
||||
global.fetch = jest.spyOn(global, 'fetch').mockImplementation(
|
||||
jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () => errorResponse,
|
||||
}),
|
||||
) as jest.Mock,
|
||||
) as jest.Mock
|
||||
|
||||
const adapter = resendAdapter({
|
||||
defaultFromAddress,
|
||||
defaultFromName,
|
||||
apiKey,
|
||||
})
|
||||
|
||||
await expect(() =>
|
||||
adapter({ payload: mockPayload }).sendEmail({
|
||||
from,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
`Error sending email: ${errorResponse.statusCode} ${errorResponse.name} - ${errorResponse.message}`,
|
||||
)
|
||||
})
|
||||
})
|
||||
240
packages/email-resend-rest/src/index.ts
Normal file
240
packages/email-resend-rest/src/index.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import type { EmailAdapter } from 'payload/config'
|
||||
import type { SendEmailOptions } from 'payload/types'
|
||||
|
||||
import { APIError } from 'payload/errors'
|
||||
|
||||
export type ResendAdapterArgs = {
|
||||
apiKey: string
|
||||
defaultFromAddress: string
|
||||
defaultFromName: string
|
||||
}
|
||||
|
||||
type ResendAdapter = EmailAdapter<ResendResponse>
|
||||
|
||||
type ResendError = {
|
||||
message: string
|
||||
name: string
|
||||
statusCode: number
|
||||
}
|
||||
|
||||
type ResendResponse = { id: string } | ResendError
|
||||
|
||||
/**
|
||||
* Email adapter for [Resend](https://resend.com) REST API
|
||||
*/
|
||||
export const resendAdapter = (args: ResendAdapterArgs): ResendAdapter => {
|
||||
const { apiKey, defaultFromAddress, defaultFromName } = args
|
||||
|
||||
const adapter: ResendAdapter = () => ({
|
||||
name: 'resend-rest',
|
||||
defaultFromAddress,
|
||||
defaultFromName,
|
||||
sendEmail: async (message) => {
|
||||
// Map the Payload email options to Resend email options
|
||||
const sendEmailOptions = mapPayloadEmailToResendEmail(
|
||||
message,
|
||||
defaultFromAddress,
|
||||
defaultFromName,
|
||||
)
|
||||
|
||||
const res = await fetch('https://api.resend.com/emails', {
|
||||
body: JSON.stringify(sendEmailOptions),
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
const data = (await res.json()) as ResendResponse
|
||||
|
||||
if ('id' in data) {
|
||||
return data
|
||||
} else {
|
||||
const statusCode = data.statusCode || res.status
|
||||
let formattedError = `Error sending email: ${statusCode}`
|
||||
if (data.name && data.message) {
|
||||
formattedError += ` ${data.name} - ${data.message}`
|
||||
}
|
||||
|
||||
throw new APIError(formattedError, statusCode)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return adapter
|
||||
}
|
||||
|
||||
function mapPayloadEmailToResendEmail(
|
||||
message: SendEmailOptions,
|
||||
defaultFromAddress: string,
|
||||
defaultFromName: string,
|
||||
): ResendSendEmailOptions {
|
||||
return {
|
||||
// Required
|
||||
from: mapFromAddress(message.from, defaultFromName, defaultFromAddress),
|
||||
subject: message.subject ?? '',
|
||||
to: mapAddresses(message.to),
|
||||
|
||||
// Other To fields
|
||||
bcc: mapAddresses(message.bcc),
|
||||
cc: mapAddresses(message.cc),
|
||||
|
||||
// Optional
|
||||
attachments: mapAttachments(message.attachments),
|
||||
html: message.html?.toString() || '',
|
||||
text: message.text?.toString() || '',
|
||||
} as ResendSendEmailOptions
|
||||
}
|
||||
|
||||
function mapFromAddress(
|
||||
address: SendEmailOptions['from'],
|
||||
defaultFromName: string,
|
||||
defaultFromAddress: string,
|
||||
): ResendSendEmailOptions['from'] {
|
||||
if (!address) {
|
||||
return `${defaultFromName} <${defaultFromAddress}>`
|
||||
}
|
||||
|
||||
if (typeof address === 'string') {
|
||||
return address
|
||||
}
|
||||
|
||||
return `${address.name} <${address.address}>`
|
||||
}
|
||||
|
||||
function mapAddresses(addresses: SendEmailOptions['to']): ResendSendEmailOptions['to'] {
|
||||
if (!addresses) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (typeof addresses === 'string') {
|
||||
return addresses
|
||||
}
|
||||
|
||||
if (Array.isArray(addresses)) {
|
||||
return addresses.map((address) => (typeof address === 'string' ? address : address.address))
|
||||
}
|
||||
|
||||
return [addresses.address]
|
||||
}
|
||||
|
||||
function mapAttachments(
|
||||
attachments: SendEmailOptions['attachments'],
|
||||
): ResendSendEmailOptions['attachments'] {
|
||||
if (!attachments) {
|
||||
return []
|
||||
}
|
||||
|
||||
return attachments.map((attachment) => {
|
||||
if (!attachment.filename || !attachment.content) {
|
||||
throw new APIError('Attachment is missing filename or content', 400)
|
||||
}
|
||||
|
||||
if (typeof attachment.content === 'string') {
|
||||
return {
|
||||
content: Buffer.from(attachment.content),
|
||||
filename: attachment.filename,
|
||||
}
|
||||
}
|
||||
|
||||
if (attachment.content instanceof Buffer) {
|
||||
return {
|
||||
content: attachment.content,
|
||||
filename: attachment.filename,
|
||||
}
|
||||
}
|
||||
|
||||
throw new APIError('Attachment content must be a string or a buffer', 400)
|
||||
})
|
||||
}
|
||||
|
||||
type ResendSendEmailOptions = {
|
||||
/**
|
||||
* Filename and content of attachments (max 40mb per email)
|
||||
*
|
||||
* @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
|
||||
*/
|
||||
attachments?: Attachment[]
|
||||
/**
|
||||
* Blind carbon copy recipient email address. For multiple addresses, send as an array of strings.
|
||||
*
|
||||
* @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
|
||||
*/
|
||||
bcc?: string | string[]
|
||||
|
||||
/**
|
||||
* Carbon copy recipient email address. For multiple addresses, send as an array of strings.
|
||||
*
|
||||
* @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
|
||||
*/
|
||||
cc?: string | string[]
|
||||
/**
|
||||
* Sender email address. To include a friendly name, use the format `"Your Name <sender@domain.com>"`
|
||||
*
|
||||
* @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
|
||||
*/
|
||||
from: string
|
||||
/**
|
||||
* Custom headers to add to the email.
|
||||
*
|
||||
* @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
|
||||
*/
|
||||
headers?: Record<string, string>
|
||||
/**
|
||||
* The HTML version of the message.
|
||||
*
|
||||
* @link https://resend.com/api-reference/emails/send-email#body-parameters
|
||||
*/
|
||||
html?: string
|
||||
/**
|
||||
* Reply-to email address. For multiple addresses, send as an array of strings.
|
||||
*
|
||||
* @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
|
||||
*/
|
||||
reply_to?: string | string[]
|
||||
/**
|
||||
* Email subject.
|
||||
*
|
||||
* @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
|
||||
*/
|
||||
subject: string
|
||||
/**
|
||||
* Email tags
|
||||
*
|
||||
* @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
|
||||
*/
|
||||
tags?: Tag[]
|
||||
/**
|
||||
* The plain text version of the message.
|
||||
*
|
||||
* @link https://resend.com/api-reference/emails/send-email#body-parameters
|
||||
*/
|
||||
text?: string
|
||||
/**
|
||||
* Recipient email address. For multiple addresses, send as an array of strings. Max 50.
|
||||
*
|
||||
* @link https://resend.com/docs/api-reference/emails/send-email#body-parameters
|
||||
*/
|
||||
to: string | string[]
|
||||
}
|
||||
|
||||
type Attachment = {
|
||||
/** Content of an attached file. */
|
||||
content?: Buffer | string
|
||||
/** Name of attached file. */
|
||||
filename?: false | string | undefined
|
||||
/** Path where the attachment file is hosted */
|
||||
path?: string
|
||||
}
|
||||
|
||||
export type Tag = {
|
||||
/**
|
||||
* The name of the email tag. It can only contain ASCII letters (a–z, A–Z), numbers (0–9), underscores (_), or dashes (-). It can contain no more than 256 characters.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The value of the email tag. It can only contain ASCII letters (a–z, A–Z), numbers (0–9), underscores (_), or dashes (-). It can contain no more than 256 characters.
|
||||
*/
|
||||
value: string
|
||||
}
|
||||
20
packages/email-resend-rest/tsconfig.json
Normal file
20
packages/email-resend-rest/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true, // Make sure typescript knows that this module depends on their references
|
||||
"noEmit": false /* Do not emit outputs. */,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"strict": true,
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
"src/**/*.spec.ts",
|
||||
],
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"],
|
||||
"references": [
|
||||
{ "path": "../payload" },
|
||||
]
|
||||
}
|
||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@@ -452,6 +452,18 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../payload
|
||||
|
||||
packages/email-resend-rest:
|
||||
devDependencies:
|
||||
'@types/jest':
|
||||
specifier: 29.5.12
|
||||
version: 29.5.12
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@20.12.5)(ts-node@10.9.1)
|
||||
payload:
|
||||
specifier: workspace:*
|
||||
version: link:../payload
|
||||
|
||||
packages/eslint-config-payload:
|
||||
dependencies:
|
||||
'@types/eslint':
|
||||
@@ -1604,6 +1616,9 @@ importers:
|
||||
'@payloadcms/email-nodemailer':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/email-nodemailer
|
||||
'@payloadcms/email-resend-rest':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/email-resend-rest
|
||||
'@payloadcms/eslint-config':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/eslint-config-payload
|
||||
|
||||
8
test/email-resend-rest/.eslintrc.cjs
Normal file
8
test/email-resend-rest/.eslintrc.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
ignorePatterns: ['payload-types.ts'],
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.eslint.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
}
|
||||
2
test/email-resend-rest/.gitignore
vendored
Normal file
2
test/email-resend-rest/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/media
|
||||
/media-gif
|
||||
31
test/email-resend-rest/config.ts
Normal file
31
test/email-resend-rest/config.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { resendAdapter } from '@payloadcms/email-resend-rest'
|
||||
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
// ...extend config here
|
||||
collections: [],
|
||||
email: resendAdapter({
|
||||
defaultFromAddress: 'dev@payloadcms.com',
|
||||
defaultFromName: 'Payload CMS',
|
||||
apiKey: process.env.RESEND_API_KEY || '',
|
||||
}),
|
||||
onInit: async (payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
},
|
||||
})
|
||||
|
||||
const email = await payload.sendEmail({
|
||||
to: 'dev@payloadcms.com',
|
||||
subject: 'This was sent on init',
|
||||
text: 'This is my message body',
|
||||
})
|
||||
|
||||
payload.logger.info({ msg: 'Email sent', email })
|
||||
},
|
||||
})
|
||||
50
test/email-resend-rest/payload-types.ts
Normal file
50
test/email-resend-rest/payload-types.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/* tslint:disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
collections: {
|
||||
posts: Post
|
||||
media: Media
|
||||
users: User
|
||||
}
|
||||
globals: {
|
||||
menu: Menu
|
||||
}
|
||||
}
|
||||
export interface Post {
|
||||
id: string
|
||||
text?: string
|
||||
associatedMedia?: string | Media
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
}
|
||||
export interface Media {
|
||||
id: string
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
url?: string
|
||||
filename?: string
|
||||
mimeType?: string
|
||||
filesize?: number
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
export interface User {
|
||||
id: string
|
||||
updatedAt: string
|
||||
createdAt: string
|
||||
email?: string
|
||||
resetPasswordToken?: string
|
||||
resetPasswordExpiration?: string
|
||||
loginAttempts?: number
|
||||
lockUntil?: string
|
||||
password?: string
|
||||
}
|
||||
export interface Menu {
|
||||
id: string
|
||||
globalText?: string
|
||||
}
|
||||
13
test/email-resend-rest/tsconfig.eslint.json
Normal file
13
test/email-resend-rest/tsconfig.eslint.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
// extend your base config to share compilerOptions, etc
|
||||
//"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// ensure that nobody can accidentally use this config for a build
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
// whatever paths you intend to lint
|
||||
"./**/*.ts",
|
||||
"./**/*.tsx"
|
||||
]
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
"@payloadcms/db-mongodb": "workspace:*",
|
||||
"@payloadcms/db-postgres": "workspace:*",
|
||||
"@payloadcms/email-nodemailer": "workspace:*",
|
||||
"@payloadcms/email-resend-rest": "workspace:*",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/graphql": "workspace:*",
|
||||
"@payloadcms/live-preview": "workspace:*",
|
||||
|
||||
Reference in New Issue
Block a user