example: adds email example
This commit is contained in:
4
examples/email/.env.example
Normal file
4
examples/email/.env.example
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
MONGODB_URI=mongodb://localhost/payload-example-email
|
||||||
|
PAYLOAD_SECRET=
|
||||||
|
NODE_ENV=development
|
||||||
|
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:8000
|
||||||
7
examples/email/.eslintrc.js
Normal file
7
examples/email/.eslintrc.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['@payloadcms'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-unused-vars': 'warn',
|
||||||
|
},
|
||||||
|
}
|
||||||
5
examples/email/.gitignore
vendored
Normal file
5
examples/email/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
build
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
.env
|
||||||
63
examples/email/README.md
Normal file
63
examples/email/README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Payload Email Example
|
||||||
|
|
||||||
|
This example demonstrates how to integrate email functionality into Payload.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
To spin up this example locally, follow these steps:
|
||||||
|
|
||||||
|
1. Clone this repo
|
||||||
|
2. `cd` into this directory and run `yarn` or `npm install`
|
||||||
|
3. `cp .env.example .env` to copy the example environment variables
|
||||||
|
4. `yarn dev` or `npm run dev` to start the server and seed the database
|
||||||
|
5. `open http://localhost:8000/admin` to access the admin panel
|
||||||
|
6. Create your first user
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
Payload utilizes [NodeMailer](https://nodemailer.com/about/) for email functionality. Once you add your email configuration to `payload.init()`, you send email from anywhere in your application just by calling `payload.sendEmail({})`.
|
||||||
|
|
||||||
|
1. Navigate to `src/server.ts` - this is where your email config gets passed to Payload
|
||||||
|
2. Open `src/email/transport.ts` - here we are defining the email config. You can use an env variable to switch between the mock email transport and live email service.
|
||||||
|
|
||||||
|
Now we can start sending email!
|
||||||
|
|
||||||
|
3. Go to `src/collections/Newsletter.ts` - with an `afterChange` hook, we are sending an email when a new user signs up for the newsletter
|
||||||
|
|
||||||
|
Let's not forget our authentication emails...
|
||||||
|
|
||||||
|
4. Auth-enabled collections have built-in options to verify the user and reset the user password. Open `src/collections/Users.ts` and see how we customize these emails.
|
||||||
|
|
||||||
|
Speaking of customization...
|
||||||
|
|
||||||
|
5. Take a look at `src/email/generateEmailHTML` and how it compiles a custom template when sending email. You change this to any HTML template of your choosing.
|
||||||
|
|
||||||
|
That's all you need, now you can go ahead and test out this repo by creating a new `user` or `newsletter-signup` and see the email integration in action.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To spin up this example locally, follow the [Quick Start](#quick-start).
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
To run Payload in production, you need to build and serve the Admin panel. To do so, follow these steps:
|
||||||
|
|
||||||
|
1. First invoke the `payload build` script by running `yarn build` or `npm run build` in your project root. This creates a `./build` directory with a production-ready admin bundle.
|
||||||
|
1. Then run `yarn serve` or `npm run serve` to run Node in production and serve Payload from the `./build` directory.
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
The easiest way to deploy your project is to use [Payload Cloud](https://payloadcms.com/new/import), a one-click hosting solution to deploy production-ready instances of your Payload apps directly from your GitHub repo. You can also deploy your app manually, check out the [deployment documentation](https://payloadcms.com/docs/production/deployment) for full details.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
For more information on integrating email, check out these resources:
|
||||||
|
|
||||||
|
<!-- Update with live blog post URL when published -->
|
||||||
|
|
||||||
|
- [Blog Post - Email 101](https://payloadcms.com/blog)
|
||||||
|
- [Email Documentation](https://payloadcms.com/docs/email/overview#email-functionality)
|
||||||
|
|
||||||
|
## Questions
|
||||||
|
|
||||||
|
If you have any issues or questions, reach out to us on [Discord](https://discord.com/invite/r6sCXqVk3v) or start a [GitHub discussion](https://github.com/payloadcms/payload/discussions).
|
||||||
4
examples/email/nodemon.json
Normal file
4
examples/email/nodemon.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"ext": "ts",
|
||||||
|
"exec": "ts-node src/server.ts"
|
||||||
|
}
|
||||||
35
examples/email/package.json
Normal file
35
examples/email/package.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "payload-example-email",
|
||||||
|
"description": "Payload Email integration example.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "dist/server.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "cross-env PAYLOAD_SEED=true PAYLOAD_DROP_DATABASE=true PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
|
||||||
|
"build:payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload build",
|
||||||
|
"build:server": "tsc",
|
||||||
|
"build": "yarn copyfiles && yarn build:payload && yarn build:server",
|
||||||
|
"serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js",
|
||||||
|
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
|
||||||
|
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
|
||||||
|
"generate:graphQLSchema": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"lint:fix": "eslint --fix --ext .ts,.tsx src"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"payload": "1.6.29",
|
||||||
|
"handlebars": "^4.7.7",
|
||||||
|
"inline-css": "^4.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^4.17.9",
|
||||||
|
"copyfiles": "^2.4.1",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"eslint": "^8.19.0",
|
||||||
|
"nodemon": "^2.0.6",
|
||||||
|
"ts-node": "^9.1.1",
|
||||||
|
"typescript": "^4.8.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
39
examples/email/src/collections/Newsletter.ts
Normal file
39
examples/email/src/collections/Newsletter.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import type { CollectionConfig } from 'payload/types'
|
||||||
|
import generateEmailHTML from '../email/generateEmailHTML'
|
||||||
|
|
||||||
|
const Newsletter: CollectionConfig = {
|
||||||
|
slug: 'newsletter-signups',
|
||||||
|
admin: {
|
||||||
|
defaultColumns: ['name', 'email'],
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
afterChange: [
|
||||||
|
async ({ doc, operation, req }) => {
|
||||||
|
if (operation === 'create') {
|
||||||
|
req.payload.sendEmail({
|
||||||
|
to: doc.email,
|
||||||
|
from: 'sender@example.com',
|
||||||
|
subject: 'Thanks for signing up!',
|
||||||
|
html: await generateEmailHTML({
|
||||||
|
headline: 'Welcome to the newsletter!',
|
||||||
|
content: `<p>${doc.name ? `Hi ${doc.name}!` : 'Hi!'} We'll be in touch soon...</p>`,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Newsletter
|
||||||
29
examples/email/src/collections/Users.ts
Normal file
29
examples/email/src/collections/Users.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type { CollectionConfig } from 'payload/types'
|
||||||
|
|
||||||
|
import generateForgotPasswordEmail from '../email/generateForgotPasswordEmail'
|
||||||
|
import generateVerificationEmail from '../email/generateVerificationEmail'
|
||||||
|
|
||||||
|
const Users: CollectionConfig = {
|
||||||
|
slug: 'users',
|
||||||
|
auth: {
|
||||||
|
verify: {
|
||||||
|
generateEmailSubject: () => 'Verify your email',
|
||||||
|
generateEmailHTML: generateVerificationEmail,
|
||||||
|
},
|
||||||
|
forgotPassword: {
|
||||||
|
generateEmailSubject: () => 'Reset your password',
|
||||||
|
generateEmailHTML: generateForgotPasswordEmail,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
useAsTitle: 'email',
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Users
|
||||||
24
examples/email/src/email/generateEmailHTML.ts
Normal file
24
examples/email/src/email/generateEmailHTML.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import Handlebars from 'handlebars'
|
||||||
|
import inlineCSS from 'inline-css'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
const template = fs.readFileSync
|
||||||
|
? fs.readFileSync(path.join(__dirname, './template.html'), 'utf8')
|
||||||
|
: ''
|
||||||
|
|
||||||
|
// Compile the template
|
||||||
|
const getHTML = Handlebars.compile(template)
|
||||||
|
|
||||||
|
const generateEmailHTML = async (data): Promise<string> => {
|
||||||
|
const preInlinedCSS = getHTML(data)
|
||||||
|
|
||||||
|
const html = await inlineCSS(preInlinedCSS, {
|
||||||
|
url: ' ',
|
||||||
|
removeStyleTags: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
export default generateEmailHTML
|
||||||
14
examples/email/src/email/generateForgotPasswordEmail.ts
Normal file
14
examples/email/src/email/generateForgotPasswordEmail.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import generateEmailHTML from './generateEmailHTML'
|
||||||
|
|
||||||
|
const generateForgotPasswordEmail = async ({ token }): Promise<string> =>
|
||||||
|
generateEmailHTML({
|
||||||
|
headline: 'Locked out?',
|
||||||
|
content:
|
||||||
|
'<p>Let's get you back in.</p>',
|
||||||
|
cta: {
|
||||||
|
buttonLabel: 'Reset your password',
|
||||||
|
url: `${process.env.PAYLOAD_PUBLIC_SERVER_URL}/reset-password?token=${token}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default generateForgotPasswordEmail
|
||||||
16
examples/email/src/email/generateVerificationEmail.ts
Normal file
16
examples/email/src/email/generateVerificationEmail.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import generateEmailHTML from './generateEmailHTML'
|
||||||
|
|
||||||
|
const generateVerificationEmail = async (args): Promise<string> => {
|
||||||
|
const { user, token } = args
|
||||||
|
|
||||||
|
return generateEmailHTML({
|
||||||
|
headline: 'Verify your account',
|
||||||
|
content: `<p>Hi${user.name ? ' ' + user.name : ''}! Validate your account by clicking the button below.</p>`,
|
||||||
|
cta: {
|
||||||
|
buttonLabel: 'Verify',
|
||||||
|
url: `${process.env.PAYLOAD_PUBLIC_SERVER_URL}/verify?token=${token}&email=${user.email}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default generateVerificationEmail
|
||||||
317
examples/email/src/email/template.html
Normal file
317
examples/email/src/email/template.html
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<style type="text/css">
|
||||||
|
body,
|
||||||
|
html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
html,
|
||||||
|
.bg {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
p,
|
||||||
|
em,
|
||||||
|
strong {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #333333;
|
||||||
|
outline: 0;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a img {
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5 {
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 40px;
|
||||||
|
color: #333333;
|
||||||
|
margin: 0 0 25px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #333333;
|
||||||
|
margin: 0 0 25px 0;
|
||||||
|
font-size: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 25px;
|
||||||
|
color: #333333;
|
||||||
|
margin: 0 0 25px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #333333;
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
color: #333333;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 900;
|
||||||
|
margin: 0 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
td {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 25px;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 15px;
|
||||||
|
margin-left: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 25px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 25px;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.hr td {
|
||||||
|
font-size: 0;
|
||||||
|
line-height: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************************
|
||||||
|
MAIN
|
||||||
|
********************************/
|
||||||
|
|
||||||
|
.main {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************************
|
||||||
|
MAX WIDTHS
|
||||||
|
********************************/
|
||||||
|
|
||||||
|
.max-width {
|
||||||
|
max-width: 800px;
|
||||||
|
width: 94%;
|
||||||
|
margin: 0 3%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************************
|
||||||
|
REUSABLES
|
||||||
|
********************************/
|
||||||
|
|
||||||
|
.padding {
|
||||||
|
padding: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-border {
|
||||||
|
border: 0;
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-margin {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
line-height: 45px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************************
|
||||||
|
PANELS
|
||||||
|
********************************/
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width : 800px) {
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 24px !important;
|
||||||
|
margin: 0 0 20px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 20px !important;
|
||||||
|
margin: 0 0 20px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 20px !important;
|
||||||
|
margin: 0 0 20px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 18px !important;
|
||||||
|
margin: 0 0 15px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 15px !important;
|
||||||
|
margin: 0 0 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-width {
|
||||||
|
width: 90% !important;
|
||||||
|
margin: 0 5% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.padding {
|
||||||
|
padding: 30px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.padding-vert {
|
||||||
|
padding-top: 20px !important;
|
||||||
|
padding-bottom: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.padding-horiz {
|
||||||
|
padding-left: 20px !important;
|
||||||
|
padding-right: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
line-height: 20px !important;
|
||||||
|
height: 20px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="background-color:#F3F3F3; height: 100%;">
|
||||||
|
<table height="100%" width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#f3f3f3"
|
||||||
|
style="background-color: #f3f3f3;">
|
||||||
|
<tr>
|
||||||
|
<td valign="top" align="left">
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top">
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<table class="max-width" cellpadding="0" cellspacing="0" border="0" width="100%"
|
||||||
|
style="width: 100%;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="spacer"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="padding main">
|
||||||
|
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- LOGO -->
|
||||||
|
<a href="https://payloadcms.com/" target="_blank">
|
||||||
|
<img src="https://payloadcms.com/images/logo-dark.png" width="150"
|
||||||
|
height="auto" />
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="spacer"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- HEADLINE -->
|
||||||
|
<h1 style="margin: 0 0 30px;">{{headline}}</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- CONTENT -->
|
||||||
|
{{{content}}}
|
||||||
|
|
||||||
|
<!-- CTA -->
|
||||||
|
{{#if cta}}
|
||||||
|
<div>
|
||||||
|
<a href="{{cta.url}}"
|
||||||
|
style="background-color:#222222;border-radius:4px;color:#ffffff;display:inline-block;font-family:sans-serif;font-size:13px;font-weight:bold;line-height:60px;text-align:center;text-decoration:none;width:200px;-webkit-text-size-adjust:none;">
|
||||||
|
{{cta.buttonLabel}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
20
examples/email/src/email/transport.ts
Normal file
20
examples/email/src/email/transport.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
let email
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
email = {
|
||||||
|
fromName: 'Payload',
|
||||||
|
fromAddress: 'info@payloadcms.com',
|
||||||
|
transportOptions: {
|
||||||
|
// Configure a custom transport here
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
email = {
|
||||||
|
fromName: 'Ethereal Email',
|
||||||
|
fromAddress: 'example@ethereal.com',
|
||||||
|
logMockCredentials: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default email
|
||||||
1
examples/email/src/emptyModule.js
Normal file
1
examples/email/src/emptyModule.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {};
|
||||||
35
examples/email/src/payload-types.ts
Normal file
35
examples/email/src/payload-types.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/**
|
||||||
|
* This file was automatically generated by Payload CMS.
|
||||||
|
* 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: {
|
||||||
|
'newsletter-signups': NewsletterSignup;
|
||||||
|
users: User;
|
||||||
|
};
|
||||||
|
globals: {};
|
||||||
|
}
|
||||||
|
export interface NewsletterSignup {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
email: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
email?: string;
|
||||||
|
resetPasswordToken?: string;
|
||||||
|
resetPasswordExpiration?: string;
|
||||||
|
_verified?: boolean;
|
||||||
|
_verificationToken?: string;
|
||||||
|
loginAttempts?: number;
|
||||||
|
lockUntil?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
46
examples/email/src/payload.config.ts
Normal file
46
examples/email/src/payload.config.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
import dotenv from 'dotenv'
|
||||||
|
import path from 'path'
|
||||||
|
import { buildConfig } from 'payload/config'
|
||||||
|
|
||||||
|
import Users from './collections/Users'
|
||||||
|
import Newsletter from './collections/Newsletter'
|
||||||
|
|
||||||
|
dotenv.config({
|
||||||
|
path: path.resolve(__dirname, '../.env'),
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockModulePath = path.resolve(__dirname, './emptyModule.js')
|
||||||
|
|
||||||
|
export default buildConfig({
|
||||||
|
admin: {
|
||||||
|
webpack: config => ({
|
||||||
|
...config,
|
||||||
|
resolve: {
|
||||||
|
...config?.resolve,
|
||||||
|
alias: [
|
||||||
|
'fs',
|
||||||
|
'handlebars',
|
||||||
|
'inline-css',
|
||||||
|
path.resolve(__dirname, './email/transport'),
|
||||||
|
path.resolve(__dirname, './email/generateEmailHTML'),
|
||||||
|
path.resolve(__dirname, './email/generateForgotPasswordEmail'),
|
||||||
|
path.resolve(__dirname, './email/generateVerificationEmail'),
|
||||||
|
].reduce(
|
||||||
|
(aliases, importPath) => ({
|
||||||
|
...aliases,
|
||||||
|
[importPath]: mockModulePath,
|
||||||
|
}),
|
||||||
|
config.resolve.alias,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
collections: [
|
||||||
|
Newsletter,
|
||||||
|
Users,
|
||||||
|
],
|
||||||
|
typescript: {
|
||||||
|
outputFile: path.resolve(__dirname, 'payload-types.ts'),
|
||||||
|
},
|
||||||
|
})
|
||||||
30
examples/email/src/server.ts
Normal file
30
examples/email/src/server.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import path from 'path'
|
||||||
|
import payload from 'payload'
|
||||||
|
import email from './email/transport'
|
||||||
|
|
||||||
|
require('dotenv').config({
|
||||||
|
path: path.resolve(__dirname, '../.env'),
|
||||||
|
})
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
app.get('/', (_, res) => {
|
||||||
|
res.redirect('/admin')
|
||||||
|
})
|
||||||
|
|
||||||
|
const start = async (): Promise<void> => {
|
||||||
|
await payload.init({
|
||||||
|
secret: process.env.PAYLOAD_SECRET,
|
||||||
|
mongoURL: process.env.MONGODB_URI,
|
||||||
|
express: app,
|
||||||
|
email,
|
||||||
|
onInit: () => {
|
||||||
|
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(8000)
|
||||||
|
}
|
||||||
|
|
||||||
|
start()
|
||||||
38
examples/email/tsconfig.json
Normal file
38
examples/email/tsconfig.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"strict": false,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"jsx": "react",
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"paths": {
|
||||||
|
"payload/generated-types": [
|
||||||
|
"./src/payload-types.ts"
|
||||||
|
],
|
||||||
|
"node_modules/*": [
|
||||||
|
"./node_modules/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"build",
|
||||||
|
],
|
||||||
|
"ts-node": {
|
||||||
|
"transpileOnly": true
|
||||||
|
}
|
||||||
|
}
|
||||||
6919
examples/email/yarn.lock
Normal file
6919
examples/email/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user