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-mongodb": "turbo build --filter db-mongodb",
|
||||||
"build:db-postgres": "turbo build --filter db-postgres",
|
"build:db-postgres": "turbo build --filter db-postgres",
|
||||||
"build:email-nodemailer": "turbo build --filter email-nodemailer",
|
"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:eslint-config-payload": "turbo build --filter eslint-config-payload",
|
||||||
"build:graphql": "turbo build --filter graphql",
|
"build:graphql": "turbo build --filter graphql",
|
||||||
"build:live-preview": "turbo build --filter live-preview",
|
"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:*
|
specifier: workspace:*
|
||||||
version: link:../payload
|
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:
|
packages/eslint-config-payload:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/eslint':
|
'@types/eslint':
|
||||||
@@ -1604,6 +1616,9 @@ importers:
|
|||||||
'@payloadcms/email-nodemailer':
|
'@payloadcms/email-nodemailer':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/email-nodemailer
|
version: link:../packages/email-nodemailer
|
||||||
|
'@payloadcms/email-resend-rest':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../packages/email-resend-rest
|
||||||
'@payloadcms/eslint-config':
|
'@payloadcms/eslint-config':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/eslint-config-payload
|
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-mongodb": "workspace:*",
|
||||||
"@payloadcms/db-postgres": "workspace:*",
|
"@payloadcms/db-postgres": "workspace:*",
|
||||||
"@payloadcms/email-nodemailer": "workspace:*",
|
"@payloadcms/email-nodemailer": "workspace:*",
|
||||||
|
"@payloadcms/email-resend-rest": "workspace:*",
|
||||||
"@payloadcms/eslint-config": "workspace:*",
|
"@payloadcms/eslint-config": "workspace:*",
|
||||||
"@payloadcms/graphql": "workspace:*",
|
"@payloadcms/graphql": "workspace:*",
|
||||||
"@payloadcms/live-preview": "workspace:*",
|
"@payloadcms/live-preview": "workspace:*",
|
||||||
|
|||||||
Reference in New Issue
Block a user