-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #480 from evershopcommerce/dev
Dev
- Loading branch information
Showing
11 changed files
with
2,100 additions
and
106 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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
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,117 @@ | ||
# Resend extension for EverShop | ||
|
||
A Resend extension for EverShop. This extension is used to send email to customers. | ||
|
||
> **Note**: This extension requires EverShop version 1.0.0 or higher. | ||
## Installation | ||
|
||
### Step 1: Install the extension package | ||
|
||
```javascript | ||
npm install @evershop/resend | ||
``` | ||
### Step 2: Register the extension by adding it to the config file | ||
|
||
```javascript | ||
// config/default.json | ||
{ | ||
..., | ||
"system": [ | ||
..., | ||
"extensions": [ | ||
..., | ||
{ | ||
"name": "resend", | ||
"resolve": "node_modules/@evershop/resend", | ||
"enabled": true, | ||
"priority": 10 | ||
} | ||
] | ||
] | ||
} | ||
``` | ||
|
||
### Step 3: Configure the extension | ||
|
||
#### 1. Add your Resend API key to the `.env` file | ||
|
||
```env | ||
RESEND_API_KEY=your_api_key | ||
``` | ||
|
||
#### 2. Add the following configuration to the `config/config.json` file | ||
|
||
```javascript | ||
// config/config.json | ||
{ | ||
..., | ||
"resend": { | ||
"from": "Customer Service <Your email>", | ||
"events": { | ||
"order_placed": { | ||
"subject": "Order Confirmation", | ||
"enabled": true, | ||
"templatePath": "config/emails/order_confirmation.html" // The path to your email template. Starting from the root of your project | ||
}, | ||
"reset_password": { | ||
"subject": "Reset Password", | ||
"enabled": true, | ||
"templatePath": "config/emails/reset_password.html" // The path to your email template. Starting from the root of your project | ||
}, | ||
"customer_registered": { | ||
"subject": "Welcome to EverShop", | ||
"enabled": true, | ||
"templatePath": "config/emails/welcome.html" // The path to your email template. Starting from the root of your project | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
This extension now supports 3 events: | ||
1. order_placed: This event is fired when a customer places an order. Send an email to confirm the order. | ||
2. reset_password: This event is fired when a customer requests to reset password. Send an email with a link to reset password. | ||
3. customer_registered: This event is fired when a customer registers an account. Send a customer welcome email. | ||
|
||
## Email templates | ||
|
||
You can customize the email templates by creating your own HTML files. You can keep the HTML files anywhere in your project. Just make sure to provide the correct path in the `config/config.json` file. | ||
|
||
For example if you store your email templates in the `config/emails` directory, you can provide the path as `config/emails/order_confirmation.html`. | ||
|
||
```javascript | ||
// The folder structure | ||
config | ||
└── emails | ||
├── order_confirmation.html | ||
├── reset_password.html | ||
└── welcome.html | ||
|
||
// The config file | ||
{ | ||
..., | ||
"resend": { | ||
"from": "Customer Service <Your email>", | ||
"events": { | ||
"order_placed": { | ||
"subject": "Order Confirmation", | ||
"enabled": true, | ||
"templatePath": "config/emails/order_confirmation.html" | ||
}, | ||
"reset_password": { | ||
"subject": "Reset Password", | ||
"enabled": true, | ||
"templatePath": "config/emails/reset_password.html" | ||
}, | ||
"customer_registered": { | ||
"subject": "Welcome to EverShop", | ||
"enabled": true, | ||
"templatePath": "config/emails/welcome.html" | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
> **Note**: You can check the example email templates [here](https://github.com/evershopcommerce/evershop/tree/main/packages/resend/email_template_examples). |
86 changes: 86 additions & 0 deletions
86
packages/resend/api/resetPassword/[resetPassword]sendMail.js
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,86 @@ | ||
const path = require('path'); | ||
const fs = require('fs').promises; | ||
const { | ||
INTERNAL_SERVER_ERROR | ||
} = require('@evershop/evershop/src/lib/util/httpStatus'); | ||
const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl'); | ||
const { debug } = require('@evershop/evershop/src/lib/log/debuger'); | ||
const { | ||
getContextValue | ||
} = require('@evershop/evershop/src/modules/graphql/services/contextHelper'); | ||
const { getConfig } = require('@evershop/evershop/src/lib/util/getConfig'); | ||
const { Resend } = require('resend'); | ||
const Handlebars = require('handlebars'); | ||
const { getEnv } = require('@evershop/evershop/src/lib/util/getEnv'); | ||
const { getValue } = require('@evershop/evershop/src/lib/util/registry'); | ||
|
||
// eslint-disable-next-line no-unused-vars | ||
module.exports = async (request, response, delegate, next) => { | ||
try { | ||
const { | ||
$body: { email, token } | ||
} = response; | ||
|
||
// Check if the API key is set | ||
const apiKey = getEnv('RESEND_API_KEY', ''); | ||
const from = getConfig('resend.from', ''); | ||
|
||
if (!apiKey || !from) { | ||
return; | ||
} | ||
const resend = new Resend(apiKey); | ||
const resetPassword = getConfig('resend.events.reset_password', {}); | ||
|
||
// Check if the we need to send the email on order placed event | ||
if (resetPassword.enabled === false) { | ||
return; | ||
} | ||
|
||
// Generate the url to reset password page | ||
const url = buildUrl('updatePasswordPage'); | ||
// Add the token to the url | ||
const resetPasswordUrl = `${getContextValue( | ||
request, | ||
'homeUrl' | ||
)}${url}?token=${token}`; | ||
|
||
// Build the email data | ||
const emailDataFinal = await getValue( | ||
'resend_reset_password_email_data', | ||
{ | ||
resetPasswordUrl | ||
}, | ||
{} | ||
); | ||
|
||
// Send email to customer | ||
const msg = { | ||
name: 'Reset Password', | ||
to: email, | ||
subject: resetPassword.subject || 'Reset Password', | ||
from | ||
}; | ||
|
||
// Read the template if it's set | ||
if (resetPassword.templatePath) { | ||
// So we need to get the full path to the file | ||
const filePath = path.join(process.cwd(), resetPassword.templatePath); | ||
const templateContent = await fs.readFile(filePath, 'utf8'); | ||
msg.html = Handlebars.compile(templateContent)(emailDataFinal); | ||
} else { | ||
msg.text = `This is your reset password link: ${resetPasswordUrl}`; | ||
} | ||
|
||
await resend.emails.send(msg); | ||
next(); | ||
} catch (e) { | ||
debug('critical', e); | ||
response.status(INTERNAL_SERVER_ERROR); | ||
response.json({ | ||
error: { | ||
status: INTERNAL_SERVER_ERROR, | ||
message: e.message | ||
} | ||
}); | ||
} | ||
}; |
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,48 @@ | ||
const { getConfig } = require('@evershop/evershop/src/lib/util/getConfig'); | ||
const { addProcessor } = require('@evershop/evershop/src/lib/util/registry'); | ||
const config = require('config'); | ||
|
||
module.exports = () => { | ||
const resendConfig = { | ||
from: 'Customer Service <[email protected]>', | ||
events: { | ||
order_placed: { | ||
subject: 'Order Confirmation', | ||
enabled: true, | ||
templatePath: undefined // This is the path to the email template. Starting from the root of the project. | ||
}, | ||
reset_password: { | ||
subject: 'Reset Password', | ||
enabled: true, | ||
templatePath: undefined // This is the path to the email template. Starting from the root of the project. | ||
}, | ||
customer_registered: { | ||
subject: 'Welcome to Evershop', | ||
enabled: true, | ||
templatePath: undefined // This is the path to the email template. Starting from the root of the project. | ||
} | ||
} | ||
}; | ||
config.util.setModuleDefaults('resend', resendConfig); | ||
// Add a processor to proceed the email data before sending | ||
addProcessor('resend_order_confirmation_email_data', (order) => { | ||
// Convert the order.created_at to a human readable date | ||
const locale = getConfig('shop.language', 'en'); | ||
const options = { year: 'numeric', month: 'long', day: 'numeric' }; | ||
|
||
// eslint-disable-next-line no-param-reassign | ||
order.created_at = new Date(order.created_at).toLocaleDateString( | ||
locale, | ||
options | ||
); | ||
|
||
// Add the order total text including the currency | ||
// eslint-disable-next-line no-param-reassign | ||
order.grand_total_text = Number(order.grand_total).toLocaleString(locale, { | ||
style: 'currency', | ||
currency: order.currency | ||
}); | ||
|
||
return order; | ||
}); | ||
}; |
Oops, something went wrong.