strapi-plugin-magic-mail 3.0.4 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +192 -0
- package/README.md +625 -1336
- package/dist/admin/App-Bi6mzEff.mjs +10973 -0
- package/dist/admin/App-aaEU9cpu.js +10976 -0
- package/dist/admin/PluginSettings-BR2-PVHx.mjs +705 -0
- package/dist/admin/PluginSettings-DbTkOzCh.js +707 -0
- package/dist/admin/analyticsLogs-BCY0Zv3Y.mjs +685 -0
- package/dist/admin/analyticsLogs-DNU09hoB.js +686 -0
- package/dist/admin/de-B5bPRK6l.js +717 -0
- package/dist/admin/de-BqYqMhOn.mjs +717 -0
- package/dist/admin/en-8xANOQdO.mjs +717 -0
- package/dist/admin/en-Bk5eVrtc.js +717 -0
- package/dist/admin/es-BfPsCsam.js +717 -0
- package/dist/admin/es-DgpTufJ_.mjs +717 -0
- package/dist/admin/fr-BuotP0Gc.mjs +717 -0
- package/dist/admin/fr-IQ3KPUoS.js +717 -0
- package/dist/admin/index-QGqbGw6k.js +102 -0
- package/dist/admin/index-qvFJ-a0Q.mjs +103 -0
- package/dist/admin/index.js +2 -99
- package/dist/admin/index.mjs +2 -99
- package/dist/admin/pt-BI63Lncs.js +717 -0
- package/dist/admin/pt-DBZu2cDY.mjs +717 -0
- package/dist/server/index.js +4655 -3374
- package/dist/server/index.mjs +4655 -3374
- package/package.json +54 -48
- package/strapi-admin.js +3 -2
- package/strapi-server.js +3 -2
- package/dist/admin/App-D3TchJYB.js +0 -8028
- package/dist/admin/App-DkyWmEgv.mjs +0 -8007
- package/dist/admin/PluginSettings-B6TdA1qd.js +0 -388
- package/dist/admin/PluginSettings-DTqdZ-nW.mjs +0 -386
- package/dist/admin/StyledButtons-aq1EWrQ-.mjs +0 -433
- package/dist/admin/StyledButtons-jHF7TXal.js +0 -434
- package/dist/admin/de-DKEFnBGC.js +0 -63
- package/dist/admin/de-SKsLY2de.mjs +0 -63
- package/dist/admin/en-CvtZE1HH.js +0 -63
- package/dist/admin/en-Ds2Q7fue.mjs +0 -63
- package/dist/admin/es-BEvzLtTg.mjs +0 -63
- package/dist/admin/es-BbLLes3D.js +0 -63
- package/dist/admin/fr-CRpwImX6.mjs +0 -63
- package/dist/admin/fr-nH9uN6s9.js +0 -63
- package/dist/admin/pt-CjOe2N_n.mjs +0 -63
- package/dist/admin/pt-qt_48NxL.js +0 -63
- package/pics/EmailAccountDashboard.png +0 -0
- package/pics/Emailaddccount1.png +0 -0
- package/pics/RoutingEmailTemplates.png +0 -0
- package/pics/Templates.png +0 -0
- package/pics/codesnippet.png +0 -0
- package/pics/emailAnalytics.png +0 -0
- package/pics/emailDesigner.png +0 -0
- package/pics/emailTemplateSendTest.png +0 -0
- package/pics/emailVersionHistory.png +0 -0
- package/pics/emailaddaccount2.png +0 -0
- package/pics/emailaddaccount3.png +0 -0
package/README.md
CHANGED
|
@@ -1,1641 +1,930 @@
|
|
|
1
|
-
#
|
|
1
|
+
# MagicMail: email accounts, routing and templates for Strapi 5
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Project site: https://schero94.github.io/Magic-Mail/
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/strapi-plugin-magic-mail)
|
|
6
6
|
[](LICENSE)
|
|
7
|
-
[-blue.svg>)](https://strapi.io)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
MagicMail adds a mail desk to the Strapi admin. You connect one or more sending accounts (Gmail, Microsoft 365, Yahoo, any SMTP server, SendGrid or Mailgun), decide with routing rules which account sends which email, design templates, and see what was delivered, opened and clicked.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
## 🌟 Why MagicMail?
|
|
14
|
-
|
|
15
|
-
**Stop fighting with .env files and email configuration!** MagicMail brings professional email management to Strapi v5 with:
|
|
16
|
-
|
|
17
|
-
- **100% Free & Open Source** - Every feature included, no license key, tiers, or limits (MIT)
|
|
18
|
-
- **6 Email Providers** - Gmail, Microsoft 365, Yahoo, SMTP, SendGrid, Mailgun
|
|
19
|
-
- **WhatsApp Messaging** - Send messages via WhatsApp (FREE!)
|
|
20
|
-
- **OAuth 2.0 Authentication** - No passwords needed for Gmail, Microsoft, Yahoo
|
|
21
|
-
- **Smart Routing Rules** - Route emails by type, recipient, subject, or custom conditions
|
|
22
|
-
- **WhatsApp Fallback** - Auto-send via WhatsApp if email fails
|
|
23
|
-
- **Automatic Failover** - Never lose an email when rate limits hit
|
|
24
|
-
- **Beautiful Admin UI** - Manage everything from Strapi Admin Panel
|
|
25
|
-
- **Zero Configuration** - No .env files, everything in the database
|
|
26
|
-
- **Email Designer Compatible** - Works seamlessly with strapi-plugin-email-designer-5
|
|
27
|
-
- **GDPR/CAN-SPAM Compliant** - Built-in List-Unsubscribe headers
|
|
28
|
-
- **Professional Security** - TLS 1.2+, DKIM, SPF, DMARC validation
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## 📸 Screenshots
|
|
33
|
-
|
|
34
|
-
### Email Accounts Dashboard
|
|
35
|
-

|
|
36
|
-
*Manage unlimited email accounts with live stats and real-time monitoring*
|
|
37
|
-
|
|
38
|
-
### Add Email Account - Step 1
|
|
39
|
-

|
|
40
|
-
*Choose from 6 different email providers with OAuth 2.0 support*
|
|
41
|
-
|
|
42
|
-
### Add Email Account - Step 2
|
|
43
|
-

|
|
44
|
-
*Configure SMTP settings or connect with OAuth*
|
|
45
|
-
|
|
46
|
-
### Add Email Account - Step 3
|
|
47
|
-

|
|
48
|
-
*Complete setup with rate limits and test connection*
|
|
49
|
-
|
|
50
|
-
### Email Templates
|
|
51
|
-

|
|
52
|
-
*Create and manage email templates with the visual designer*
|
|
11
|
+
It plugs into the Strapi email service you already use. Code that calls `strapi.plugin('email').service('email').send()`, Strapi's own password-reset mail and every Users & Permissions mail go through MagicMail without changes.
|
|
53
12
|
|
|
54
|
-
|
|
55
|
-

|
|
56
|
-
*Drag-and-drop email editor with real-time preview*
|
|
13
|
+
MagicMail sends **email only**. It is MIT-licensed, has no license key, tiers or usage limits.
|
|
57
14
|
|
|
58
|
-
|
|
59
|
-

|
|
60
|
-
*Track changes and restore previous template versions*
|
|
15
|
+
**At a glance**
|
|
61
16
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
17
|
+
- 6 providers: Gmail, Microsoft 365 and Yahoo with OAuth 2.0, SMTP (optional DKIM), SendGrid, Mailgun
|
|
18
|
+
- Routing rules by email type, recipient, subject, template or a custom value, with a fallback account
|
|
19
|
+
- Hourly and daily quotas per account, reserved atomically, with safe failover
|
|
20
|
+
- Template designer: visual editor (Unlayer) or plain HTML/text, Mustache variables, versions, import/export
|
|
21
|
+
- Delivery log with privacy-friendly open and click tracking and configurable retention
|
|
22
|
+
- Credentials encrypted with AES-256-GCM; secrets never reach the browser
|
|
23
|
+
- Admin UI in English, German, French, Spanish and Portuguese, light and dark mode
|
|
65
24
|
|
|
66
|
-
|
|
67
|
-

|
|
68
|
-
*Define intelligent routing rules based on email type, recipient, subject*
|
|
25
|
+
## Contents
|
|
69
26
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
27
|
+
- [Screenshots](#screenshots)
|
|
28
|
+
- [Quick start](#quick-start)
|
|
29
|
+
- [Sending email](#sending-email)
|
|
30
|
+
- [Provider setup](#provider-setup)
|
|
31
|
+
- [Routing rules and quotas](#routing-rules-and-quotas)
|
|
32
|
+
- [Templates and the visual editor](#templates-and-the-visual-editor)
|
|
33
|
+
- [Tracking, privacy and retention](#tracking-privacy-and-retention)
|
|
34
|
+
- [Content API](#content-api)
|
|
35
|
+
- [Admin API](#admin-api)
|
|
36
|
+
- [Permissions](#permissions)
|
|
37
|
+
- [Configuration reference](#configuration-reference)
|
|
38
|
+
- [Troubleshooting](#troubleshooting)
|
|
39
|
+
- [Upgrading to 4.0.0](#upgrading-to-400)
|
|
40
|
+
- [Development](#development)
|
|
77
41
|
|
|
78
42
|
---
|
|
79
43
|
|
|
80
|
-
##
|
|
44
|
+
## Screenshots
|
|
81
45
|
|
|
82
|
-
|
|
46
|
+
The screenshots show synthetic demo data (`example.test` addresses) on Strapi 5.61.
|
|
83
47
|
|
|
84
|
-
###
|
|
48
|
+
### Email accounts
|
|
85
49
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
| **Open Rate** | ~20% | ~98% |
|
|
89
|
-
| **Delivery Speed** | Minutes | Instant |
|
|
90
|
-
| **Spam Folder Risk** | High | None |
|
|
91
|
-
| **User Preference** | Declining | Growing |
|
|
92
|
-
| **Cost** | Varies | FREE |
|
|
50
|
+

|
|
51
|
+
_Accounts with status, provider, priority, today's usage against the daily limit, and test/edit/delete actions._
|
|
93
52
|
|
|
94
|
-
###
|
|
53
|
+
### Account wizard
|
|
95
54
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
3. **Verify** - Check connection status shows "Connected"
|
|
99
|
-
4. **Send** - Start sending messages programmatically!
|
|
55
|
+

|
|
56
|
+
_Four steps: provider, details, credentials, settings. OAuth providers connect in a popup._
|
|
100
57
|
|
|
101
|
-
###
|
|
58
|
+
### Routing rules
|
|
102
59
|
|
|
103
|
-
|
|
60
|
+

|
|
61
|
+
_Rules in evaluation order. Rules that point to a missing or inactive account are flagged._
|
|
104
62
|
|
|
105
|
-
|
|
106
|
-
// Send WhatsApp message directly
|
|
107
|
-
await strapi.plugin('magic-mail').service('whatsapp').sendMessage(
|
|
108
|
-
'+491234567890', // Phone number with country code
|
|
109
|
-
'Hello! This is a test message from MagicMail.'
|
|
110
|
-
);
|
|
111
|
-
```
|
|
63
|
+
### Templates
|
|
112
64
|
|
|
113
|
-
|
|
65
|
+

|
|
66
|
+
_Templates with Reference ID, category and status; edit, download as HTML or JSON, duplicate, code snippets, test send and delete._
|
|
114
67
|
|
|
115
|
-
|
|
116
|
-
// Send via unified API - auto-detects channel
|
|
117
|
-
await strapi.plugin('magic-mail').service('email-router').sendMessage({
|
|
118
|
-
channel: 'whatsapp', // or 'email' or 'auto'
|
|
119
|
-
phoneNumber: '+491234567890',
|
|
120
|
-
message: 'Your order #12345 has been shipped!'
|
|
121
|
-
});
|
|
68
|
+
### Template editor
|
|
122
69
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
channel: 'auto',
|
|
126
|
-
phoneNumber: '+491234567890', // Has phone -> WhatsApp
|
|
127
|
-
message: 'Shipping notification'
|
|
128
|
-
});
|
|
70
|
+

|
|
71
|
+
_Template editor. For this screenshot the Unlayer canvas was replaced by a local stand-in, so no request went to Unlayer; the real editor looks different inside the canvas._
|
|
129
72
|
|
|
130
|
-
|
|
131
|
-
channel: 'auto',
|
|
132
|
-
to: 'user@example.com', // Has email -> Email
|
|
133
|
-
subject: 'Shipping Notification',
|
|
134
|
-
message: 'Your order has been shipped!'
|
|
135
|
-
});
|
|
136
|
-
```
|
|
73
|
+
### Analytics
|
|
137
74
|
|
|
138
|
-
|
|
75
|
+

|
|
76
|
+
_Totals, delivery log with server-side search, opens and clicks per email._
|
|
139
77
|
|
|
140
|
-
|
|
141
|
-
// POST /api/magic-mail/send-whatsapp
|
|
142
|
-
await fetch('/api/magic-mail/send-whatsapp', {
|
|
143
|
-
method: 'POST',
|
|
144
|
-
headers: {
|
|
145
|
-
'Content-Type': 'application/json',
|
|
146
|
-
'Authorization': 'Bearer YOUR_STRAPI_API_TOKEN'
|
|
147
|
-
},
|
|
148
|
-
body: JSON.stringify({
|
|
149
|
-
phoneNumber: '+491234567890',
|
|
150
|
-
message: 'Hello from MagicMail!'
|
|
151
|
-
})
|
|
152
|
-
});
|
|
78
|
+
### Plugin settings
|
|
153
79
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
method: 'POST',
|
|
157
|
-
headers: {
|
|
158
|
-
'Content-Type': 'application/json',
|
|
159
|
-
'Authorization': 'Bearer YOUR_STRAPI_API_TOKEN'
|
|
160
|
-
},
|
|
161
|
-
body: JSON.stringify({
|
|
162
|
-
channel: 'whatsapp',
|
|
163
|
-
phoneNumber: '+491234567890',
|
|
164
|
-
message: 'Order confirmed!'
|
|
165
|
-
})
|
|
166
|
-
});
|
|
167
|
-
```
|
|
80
|
+

|
|
81
|
+
_Settings → MagicMail → Plugin Settings: link and open tracking, tracking base URL, unsubscribe header, default sender name._
|
|
168
82
|
|
|
169
|
-
###
|
|
83
|
+
### Dark mode and mobile
|
|
170
84
|
|
|
171
|
-
|
|
172
|
-
If email delivery fails, automatically send via WhatsApp:
|
|
85
|
+

|
|
173
86
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
87
|
+
| Accounts at 390 px | Routing rules at 390 px |
|
|
88
|
+
| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
89
|
+
|  |  |
|
|
177
90
|
|
|
178
|
-
|
|
179
|
-
to: 'user@example.com',
|
|
180
|
-
phoneNumber: '+491234567890', // Fallback phone
|
|
181
|
-
subject: 'Important Update',
|
|
182
|
-
html: '<h1>Update</h1>',
|
|
183
|
-
type: 'transactional'
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// If email fails -> auto-sends via WhatsApp!
|
|
187
|
-
```
|
|
91
|
+
---
|
|
188
92
|
|
|
189
|
-
|
|
190
|
-
Get instant WhatsApp alerts for critical events:
|
|
93
|
+
## Quick start
|
|
191
94
|
|
|
192
|
-
|
|
193
|
-
// Example: Notify admin when email quota exceeded
|
|
194
|
-
const adminPhone = '+4917612345678';
|
|
95
|
+
### Requirements
|
|
195
96
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
)
|
|
200
|
-
```
|
|
97
|
+
- Node.js `>=22.12.0 <=26.x.x`
|
|
98
|
+
- Strapi `^5.57.0` (developed and tested against 5.61)
|
|
99
|
+
- `@strapi/plugin-users-permissions` `^5.57.0` (part of every default Strapi app; the email log links to its users)
|
|
100
|
+
- Outside development and test: the environment variable `MAGIC_MAIL_ENCRYPTION_KEY` (see [Environment variables](#environment-variables))
|
|
201
101
|
|
|
202
|
-
|
|
203
|
-
Send time-sensitive messages instantly:
|
|
102
|
+
### 1. Install
|
|
204
103
|
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
await strapi.plugin('magic-mail').service('whatsapp').sendMessage(
|
|
208
|
-
userPhone,
|
|
209
|
-
`Your verification code: 123456\n\nValid for 5 minutes.`
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
// Order updates
|
|
213
|
-
await strapi.plugin('magic-mail').service('whatsapp').sendMessage(
|
|
214
|
-
customerPhone,
|
|
215
|
-
`Order #${orderId} is out for delivery!\nTrack: ${trackingUrl}`
|
|
216
|
-
);
|
|
104
|
+
```bash
|
|
105
|
+
npm install strapi-plugin-magic-mail
|
|
217
106
|
```
|
|
218
107
|
|
|
219
|
-
###
|
|
108
|
+
### 2. Enable
|
|
220
109
|
|
|
221
110
|
```javascript
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// { exists: true, jid: '491234567890@s.whatsapp.net' }
|
|
111
|
+
// config/plugins.js (or config/plugins.ts with export default)
|
|
112
|
+
module.exports = () => ({
|
|
113
|
+
'magic-mail': {
|
|
114
|
+
enabled: true,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
229
117
|
```
|
|
230
118
|
|
|
231
|
-
###
|
|
232
|
-
|
|
233
|
-
When both **MagicMail** and **Magic-Link** are installed:
|
|
234
|
-
|
|
235
|
-
- Magic-Link automatically uses MagicMail's WhatsApp service
|
|
236
|
-
- Only ONE WhatsApp connection needed (managed here in MagicMail)
|
|
237
|
-
- Send magic links via WhatsApp from Magic-Link settings
|
|
238
|
-
- Unified WhatsApp management for all plugins
|
|
239
|
-
|
|
240
|
-
---
|
|
241
|
-
|
|
242
|
-
## Quick Start
|
|
243
|
-
|
|
244
|
-
### Installation
|
|
245
|
-
|
|
246
|
-
Requires Node.js 20.19 or newer and Strapi 5.50 or newer.
|
|
119
|
+
### 3. Set the encryption key
|
|
247
120
|
|
|
248
121
|
```bash
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
yarn add strapi-plugin-magic-mail
|
|
122
|
+
# Generate once and keep it with your other secrets
|
|
123
|
+
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
252
124
|
```
|
|
253
125
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
Create or update `config/plugins.ts`:
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
export default () => ({
|
|
260
|
-
'magic-mail': {
|
|
261
|
-
enabled: true,
|
|
262
|
-
},
|
|
263
|
-
});
|
|
126
|
+
```env
|
|
127
|
+
MAGIC_MAIL_ENCRYPTION_KEY=<the generated value>
|
|
264
128
|
```
|
|
265
129
|
|
|
266
|
-
|
|
130
|
+
Keep the key stable. Losing it makes the stored account credentials unreadable.
|
|
131
|
+
|
|
132
|
+
### 4. Build and start
|
|
267
133
|
|
|
268
134
|
```bash
|
|
269
135
|
npm run build
|
|
270
136
|
npm run develop
|
|
271
137
|
```
|
|
272
138
|
|
|
273
|
-
### Add
|
|
274
|
-
|
|
275
|
-
1. Navigate to **Admin Panel → MagicMail → Email Accounts**
|
|
276
|
-
2. Click **"Add Account"**
|
|
277
|
-
3. Choose your provider (Gmail OAuth, SMTP, etc.)
|
|
278
|
-
4. Fill in credentials
|
|
279
|
-
5. Click **"Test"** to verify
|
|
280
|
-
6. Done! 🎉
|
|
139
|
+
### 5. Add an account and send a test
|
|
281
140
|
|
|
282
|
-
|
|
141
|
+
1. Open **MagicMail → Email Accounts** in the admin and click **Add account**.
|
|
142
|
+
2. Pick a provider and enter the details (see [Provider setup](#provider-setup)).
|
|
143
|
+
3. Click the **Test** action on the account, enter your own address and choose **Direct test**.
|
|
283
144
|
|
|
284
|
-
|
|
145
|
+
Every existing `strapi.plugin('email')` call now goes through MagicMail.
|
|
285
146
|
|
|
286
|
-
|
|
287
|
-
|----------|------|----------------|----------|
|
|
288
|
-
| **Gmail** | OAuth 2.0 | Google OAuth | Gmail API, Attachments, Auto DKIM |
|
|
289
|
-
| **Microsoft 365** | OAuth 2.0 | Azure AD | Graph API, Attachments, Tenant Support |
|
|
290
|
-
| **Yahoo Mail** | OAuth 2.0 | Yahoo OAuth | SMTP OAuth2, Attachments |
|
|
291
|
-
| **SMTP** | Credentials | Username/Password | Universal, DKIM Optional, Custom Servers |
|
|
292
|
-
| **SendGrid** | API Key | SendGrid API | Transactional, Marketing, Templates |
|
|
293
|
-
| **Mailgun** | API Key | Mailgun API | Bulk Sending, Analytics |
|
|
147
|
+
Upgrading from 3.x? Read [Upgrading to 4.0.0](#upgrading-to-400) first.
|
|
294
148
|
|
|
295
149
|
---
|
|
296
150
|
|
|
297
|
-
##
|
|
151
|
+
## Sending email
|
|
298
152
|
|
|
299
|
-
###
|
|
153
|
+
### Option 1: the Strapi email service (recommended)
|
|
300
154
|
|
|
301
|
-
MagicMail
|
|
155
|
+
MagicMail wraps the provider of `@strapi/email` at startup, so existing code keeps working:
|
|
302
156
|
|
|
303
157
|
```javascript
|
|
304
|
-
// This works in ANY Strapi plugin or controller:
|
|
305
158
|
await strapi.plugin('email').service('email').send({
|
|
306
159
|
to: 'user@example.com',
|
|
307
|
-
subject: 'Welcome to
|
|
308
|
-
text: '
|
|
160
|
+
subject: 'Welcome to Example Shop',
|
|
161
|
+
text: 'Thanks for signing up!',
|
|
309
162
|
html: '<h1>Welcome!</h1><p>Thanks for signing up!</p>',
|
|
310
163
|
});
|
|
311
|
-
|
|
312
|
-
// ✅ MagicMail automatically:
|
|
313
|
-
// - Selects best account (via routing rules or priority)
|
|
314
|
-
// - Checks rate limits
|
|
315
|
-
// - Handles failover if needed
|
|
316
|
-
// - Logs the email
|
|
317
|
-
// - Updates statistics
|
|
318
164
|
```
|
|
319
165
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
- ✅ **Fallback safe** - Falls back to original email service if MagicMail fails
|
|
324
|
-
- ✅ **Drop-in replacement** - Just enable and go!
|
|
166
|
+
Both `send()` and `sendTemplatedEmail()` are routed, including Strapi's admin password-reset email and the Users & Permissions emails. MagicMail picks the account (routing rules, then priority), reserves quota, fails over when that is safe, logs the email and updates the statistics.
|
|
167
|
+
|
|
168
|
+
**Fallback to the original provider.** When no MagicMail account is active, or every eligible account is over its quota, the email is sent through the Strapi email provider you configured before (logged with the account name `strapi-email-provider`). Once a MagicMail account has attempted delivery, an error is returned to the caller and the email is **not** sent again through the original provider, so nothing goes out twice.
|
|
325
169
|
|
|
326
|
-
###
|
|
170
|
+
### Option 2: the MagicMail router service
|
|
327
171
|
|
|
328
|
-
|
|
172
|
+
Use the router directly when you need its options, for example to force an account:
|
|
329
173
|
|
|
330
174
|
```javascript
|
|
331
|
-
// Force a specific account
|
|
332
175
|
await strapi.plugin('magic-mail').service('email-router').send({
|
|
333
176
|
to: 'customer@example.com',
|
|
334
|
-
subject: 'Order
|
|
335
|
-
html: '<h1>Your
|
|
336
|
-
accountName: '
|
|
177
|
+
subject: 'Order confirmation',
|
|
178
|
+
html: '<h1>Your order #12345</h1>',
|
|
179
|
+
accountName: 'Transactional SMTP', // case-insensitive; never replaced by another account
|
|
337
180
|
type: 'transactional',
|
|
338
181
|
priority: 'high',
|
|
339
182
|
});
|
|
183
|
+
// → { success: true, accountUsed: 'Transactional SMTP', messageId: '<…>', emailId: '…' }
|
|
340
184
|
```
|
|
341
185
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
```javascript
|
|
345
|
-
await strapi.plugin('email').service('email').send({
|
|
346
|
-
to: 'customer@example.com',
|
|
347
|
-
subject: 'Invoice #12345',
|
|
348
|
-
html: '<h1>Invoice Attached</h1>',
|
|
349
|
-
attachments: [
|
|
350
|
-
{
|
|
351
|
-
filename: 'invoice.pdf',
|
|
352
|
-
path: './uploads/invoice-12345.pdf',
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
filename: 'receipt.pdf',
|
|
356
|
-
content: Buffer.from(pdfData),
|
|
357
|
-
contentType: 'application/pdf',
|
|
358
|
-
},
|
|
359
|
-
],
|
|
360
|
-
});
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
**Supported attachment formats:**
|
|
364
|
-
- ✅ PDF, Word, Excel, PowerPoint
|
|
365
|
-
- ✅ Images (PNG, JPG, GIF, WebP)
|
|
366
|
-
- ✅ Text files (TXT, CSV, JSON)
|
|
367
|
-
- ✅ Archives (ZIP, RAR)
|
|
368
|
-
- ✅ Any file type!
|
|
369
|
-
|
|
370
|
-
### Advanced: Priority & Marketing Emails
|
|
371
|
-
|
|
372
|
-
```javascript
|
|
373
|
-
// High priority email
|
|
374
|
-
await strapi.plugin('magic-mail').service('email-router').send({
|
|
375
|
-
to: 'vip@example.com',
|
|
376
|
-
subject: 'Urgent: Action Required',
|
|
377
|
-
html: '<h1>Important Update</h1>',
|
|
378
|
-
priority: 'high', // Adds X-Priority and Importance headers
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
// Marketing email with unsubscribe (GDPR/CAN-SPAM compliant)
|
|
382
|
-
await strapi.plugin('magic-mail').service('email-router').send({
|
|
383
|
-
to: 'subscriber@example.com',
|
|
384
|
-
subject: 'Weekly Newsletter',
|
|
385
|
-
html: '<h1>This Week's Updates</h1>',
|
|
386
|
-
type: 'marketing',
|
|
387
|
-
unsubscribeUrl: 'https://yoursite.com/unsubscribe?id=123',
|
|
388
|
-
// Automatically adds List-Unsubscribe header!
|
|
389
|
-
});
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
---
|
|
393
|
-
|
|
394
|
-
## 🎨 Email Designer Suite (NEW!)
|
|
395
|
-
|
|
396
|
-
**Design beautiful emails visually with the integrated email designer!**
|
|
397
|
-
|
|
398
|
-
### ✨ Features
|
|
399
|
-
|
|
400
|
-
Every Email Designer feature is included for free:
|
|
186
|
+
An explicit `accountName` is never swapped for another account: if that account is over quota or fails, the send fails. Router calls never use the Strapi-provider fallback; they get the error.
|
|
401
187
|
|
|
402
|
-
|
|
403
|
-
|---------|----------|
|
|
404
|
-
| **Visual Designer** | ✅ |
|
|
405
|
-
| **Unlimited Templates** | ✅ |
|
|
406
|
-
| **Drag & Drop + Mustache** | ✅ |
|
|
407
|
-
| **Import / Export** | ✅ |
|
|
408
|
-
| **Template Versioning** | ✅ |
|
|
409
|
-
| **Email Analytics** | ✅ |
|
|
188
|
+
### Option 3: HTTP
|
|
410
189
|
|
|
411
|
-
|
|
190
|
+
See [Content API](#content-api) for `POST /api/magic-mail/send` and `/send-message`.
|
|
412
191
|
|
|
413
|
-
|
|
192
|
+
### Send options
|
|
414
193
|
|
|
415
|
-
|
|
194
|
+
The same fields work for the router, the Strapi email service and `POST /api/magic-mail/send`.
|
|
416
195
|
|
|
417
|
-
|
|
196
|
+
| Field | Description |
|
|
197
|
+
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
198
|
+
| `to`, `cc`, `bcc` | Address, array or comma-separated list (max. 100 `to`, 50 `cc`/`bcc`). Display names allowed |
|
|
199
|
+
| `subject`, `text`, `html` | `subject` is required unless a template provides it; `text` or `html` is required. HTML is sanitised (no scripts, event handlers or `javascript:` URLs; email layout is kept) |
|
|
200
|
+
| `from` | Optional. Must be the sending account's address; only its display name is used |
|
|
201
|
+
| `replyTo` | A valid address |
|
|
202
|
+
| `type` | `transactional` (default), `marketing` or `notification`. Without `type`, a template's category is used |
|
|
203
|
+
| `priority` | `normal` (default), `high` or `low`. `high` adds `X-Priority` and `Importance` headers |
|
|
204
|
+
| `accountName` | Force one account |
|
|
205
|
+
| `templateId` / `templateReferenceId`, `templateData` | Render a MagicMail template by its Reference ID with Mustache data (`data` is accepted as an alias of `templateData`) |
|
|
206
|
+
| `unsubscribeUrl` | Per-email unsubscribe URL |
|
|
207
|
+
| `enableTracking: false`, `skipLinkTracking: true` | Disable open and click tracking, or only click tracking, for this email |
|
|
208
|
+
| `customField` | Value matched by routing rules of type _Custom_ |
|
|
209
|
+
| `headers` | Up to 20 custom headers: `X-*` (except MagicMail's own) plus `List-Id`, `Precedence`, `Auto-Submitted`, `In-Reply-To`, `References`. Others are dropped |
|
|
210
|
+
| `attachments` | Up to 20, 25 MB each, with `filename`, `content`, `contentType`, `encoding`, `cid` |
|
|
211
|
+
| `userId`, `recipientName` | Stored on the email log |
|
|
418
212
|
|
|
419
|
-
|
|
213
|
+
### Attachments
|
|
420
214
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
215
|
+
```javascript
|
|
216
|
+
await strapi
|
|
217
|
+
.plugin('email')
|
|
218
|
+
.service('email')
|
|
219
|
+
.send({
|
|
220
|
+
to: 'customer@example.com',
|
|
221
|
+
subject: 'Invoice 12345',
|
|
222
|
+
html: '<h1>Your invoice is attached</h1>',
|
|
223
|
+
attachments: [
|
|
224
|
+
{
|
|
225
|
+
filename: 'invoice.pdf',
|
|
226
|
+
content: await fs.promises.readFile(invoicePath), // read the file yourself
|
|
227
|
+
contentType: 'application/pdf',
|
|
228
|
+
},
|
|
229
|
+
{ filename: 'receipt.pdf', content: pdfBase64, encoding: 'base64' },
|
|
230
|
+
],
|
|
231
|
+
});
|
|
428
232
|
```
|
|
429
233
|
|
|
430
|
-
|
|
234
|
+
Attachments must carry their `content` (a Buffer, or a string with `encoding`). Attachments with `path` or `href` are rejected for every provider, so MagicMail never reads server files or fetches URLs for a caller. Inline images use `cid`.
|
|
431
235
|
|
|
432
|
-
|
|
236
|
+
### Unsubscribe and bulk headers
|
|
433
237
|
|
|
434
|
-
|
|
238
|
+
- `List-Unsubscribe` is sent for marketing emails that have an unsubscribe URL, and for every email type while **Settings → Unsubscribe header** is on and a URL is available.
|
|
239
|
+
- A per-email `unsubscribeUrl` wins. The plugin-wide URL from the settings is used only while the toggle is on; `{{email}}` in it is replaced with the URL-encoded recipient (single-recipient emails only).
|
|
240
|
+
- `List-Unsubscribe-Post: List-Unsubscribe=One-Click` (RFC 8058) is only added for `https` URLs.
|
|
241
|
+
- Marketing emails get `Precedence: bulk`; notification emails get `Auto-Submitted: auto-generated`.
|
|
435
242
|
|
|
436
|
-
|
|
243
|
+
---
|
|
437
244
|
|
|
438
|
-
|
|
439
|
-
// Send email using template
|
|
440
|
-
await strapi.plugin('magic-mail').service('email-router').send({
|
|
441
|
-
to: 'user@example.com',
|
|
442
|
-
templateId: 100, // Your template reference ID
|
|
443
|
-
templateData: {
|
|
444
|
-
user: {
|
|
445
|
-
firstName: 'John',
|
|
446
|
-
lastName: 'Doe',
|
|
447
|
-
email: 'john@example.com',
|
|
448
|
-
},
|
|
449
|
-
company: {
|
|
450
|
-
name: 'ACME Corp',
|
|
451
|
-
url: 'https://acme.com',
|
|
452
|
-
},
|
|
453
|
-
orderNumber: '12345',
|
|
454
|
-
orderTotal: '$199.99',
|
|
455
|
-
},
|
|
456
|
-
});
|
|
245
|
+
## Provider setup
|
|
457
246
|
|
|
458
|
-
|
|
459
|
-
// - Load template #100
|
|
460
|
-
// - Render it with your data (Mustache)
|
|
461
|
-
// - Route via smart routing
|
|
462
|
-
// - Send via best account
|
|
463
|
-
```
|
|
247
|
+
### OAuth redirect URI
|
|
464
248
|
|
|
465
|
-
|
|
249
|
+
MagicMail builds the redirect URI from the `URL` environment variable if it is set (legacy override), otherwise from Strapi's `server.url` (or `server.absoluteUrl`):
|
|
466
250
|
|
|
467
|
-
```javascript
|
|
468
|
-
// Also works with standard Strapi email service!
|
|
469
|
-
await strapi.plugin('email').service('email').send({
|
|
470
|
-
to: 'user@example.com',
|
|
471
|
-
templateId: 100,
|
|
472
|
-
templateData: {
|
|
473
|
-
user: { firstName: 'Jane' },
|
|
474
|
-
resetUrl: 'https://yoursite.com/reset?token=xyz',
|
|
475
|
-
},
|
|
476
|
-
});
|
|
477
251
|
```
|
|
478
|
-
|
|
479
|
-
### 🎯 Template Categories
|
|
480
|
-
|
|
481
|
-
Templates can be categorized for automatic routing:
|
|
482
|
-
|
|
483
|
-
- **transactional** - Order confirmations, receipts
|
|
484
|
-
- **marketing** - Newsletters, promotions
|
|
485
|
-
- **notification** - System alerts, updates
|
|
486
|
-
- **custom** - Anything else
|
|
487
|
-
|
|
488
|
-
**Automatic Routing by Category:**
|
|
489
|
-
|
|
490
|
-
```javascript
|
|
491
|
-
// Create routing rule: All marketing templates → SendGrid
|
|
492
|
-
// Then just send:
|
|
493
|
-
await strapi.plugin('magic-mail').service('email-router').send({
|
|
494
|
-
to: 'subscriber@example.com',
|
|
495
|
-
templateId: 200, // Marketing category template
|
|
496
|
-
templateData: { offer: '50% OFF' },
|
|
497
|
-
});
|
|
498
|
-
// ✅ Automatically routes via SendGrid (based on category)
|
|
252
|
+
<public URL>/magic-mail/oauth/<gmail|microsoft|yahoo>/callback
|
|
499
253
|
```
|
|
500
254
|
|
|
501
|
-
|
|
255
|
+
The account wizard shows the exact value the server uses; register exactly that value with the provider. In production the URL must be public `https`; there is no `localhost` fallback. The `localhost:1337` URIs below are for local development.
|
|
502
256
|
|
|
503
|
-
|
|
257
|
+
The authorization popup reports back to the admin origin (`admin.url`). If the admin runs on its own host, set `admin.url` to that absolute URL. OAuth flows are bound to the admin who started them and kept in process memory, so several Strapi instances behind a load balancer need sticky admin sessions.
|
|
504
258
|
|
|
505
|
-
|
|
506
|
-
// Get all versions
|
|
507
|
-
const versions = await strapi
|
|
508
|
-
.plugin('magic-mail')
|
|
509
|
-
.service('email-designer')
|
|
510
|
-
.getVersions(templateId);
|
|
259
|
+
**Reconnecting an account** (revoked consent, expired refresh token): edit the account and use **Reconnect with <provider>** in the wizard. The provider must return the account's own mailbox. Access tokens are refreshed 60 seconds before they expire and once more if the provider rejects them; rotated refresh tokens are stored.
|
|
511
260
|
|
|
512
|
-
|
|
513
|
-
await strapi
|
|
514
|
-
.plugin('magic-mail')
|
|
515
|
-
.service('email-designer')
|
|
516
|
-
.restoreVersion(templateId, versionId);
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
### 📦 Import/Export Templates
|
|
261
|
+
### Gmail (OAuth 2.0)
|
|
520
262
|
|
|
521
|
-
**
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
.plugin('magic-mail')
|
|
526
|
-
.service('email-designer')
|
|
527
|
-
.exportTemplates();
|
|
263
|
+
1. In the [Google Cloud Console](https://console.cloud.google.com) create or select a project and enable the **Gmail API**.
|
|
264
|
+
2. Create an **OAuth client ID** of type _Web application_.
|
|
265
|
+
3. Add the redirect URI, for example `http://localhost:1337/magic-mail/oauth/gmail/callback`.
|
|
266
|
+
4. In MagicMail choose **Gmail**, paste the client ID and secret, click **Connect with Google** and sign in.
|
|
528
267
|
|
|
529
|
-
|
|
530
|
-
fs.writeFileSync('templates.json', JSON.stringify(templates, null, 2));
|
|
531
|
-
```
|
|
268
|
+
Scopes: `https://www.googleapis.com/auth/gmail.send`, `https://www.googleapis.com/auth/userinfo.email`, `openid`. Mail is sent through the Gmail API.
|
|
532
269
|
|
|
533
|
-
|
|
270
|
+
### Microsoft 365 (OAuth 2.0)
|
|
534
271
|
|
|
535
|
-
|
|
536
|
-
|
|
272
|
+
1. In the [Azure portal](https://portal.azure.com) open **Microsoft Entra ID → App registrations → New registration**.
|
|
273
|
+
2. Copy the **Application (client) ID** and the **Directory (tenant) ID**.
|
|
274
|
+
3. Add a _Web_ redirect URI, for example `http://localhost:1337/magic-mail/oauth/microsoft/callback`.
|
|
275
|
+
4. API permissions (Microsoft Graph, _Delegated_): `Mail.Send`, `User.Read`, `offline_access`, `openid`, `email`. Grant admin consent.
|
|
276
|
+
5. Under **Certificates & secrets** create a client secret and copy its **Value** (not the secret ID).
|
|
277
|
+
6. In MagicMail choose **Microsoft 365**, enter tenant ID, client ID and secret, click **Connect with Microsoft**.
|
|
537
278
|
|
|
538
|
-
|
|
539
|
-
.plugin('magic-mail')
|
|
540
|
-
.service('email-designer')
|
|
541
|
-
.importTemplates(templates);
|
|
279
|
+
The tenant ID must be a GUID, a domain, `common`, `organizations` or `consumers`. Mail is sent through Microsoft Graph `sendMail`.
|
|
542
280
|
|
|
543
|
-
|
|
544
|
-
```
|
|
281
|
+
### Yahoo Mail (OAuth 2.0)
|
|
545
282
|
|
|
546
|
-
|
|
283
|
+
1. Create an app at [developer.yahoo.com/apps](https://developer.yahoo.com/apps).
|
|
284
|
+
2. Redirect URI, for example `http://localhost:1337/magic-mail/oauth/yahoo/callback`.
|
|
285
|
+
3. Permissions: **Mail (read/write)** and **OpenID Connect** (scopes `openid email mail-w`).
|
|
286
|
+
4. In MagicMail choose **Yahoo Mail**, paste client ID and secret and click **Connect with Yahoo**.
|
|
547
287
|
|
|
548
|
-
|
|
288
|
+
Mail is sent over SMTP with XOAUTH2.
|
|
549
289
|
|
|
550
|
-
|
|
551
|
-
- ✅ Pre-built Content Blocks
|
|
552
|
-
- ✅ Image Upload & Management
|
|
553
|
-
- ✅ Custom Fonts & Colors
|
|
554
|
-
- ✅ Responsive Design
|
|
555
|
-
- ✅ Mobile Preview
|
|
556
|
-
- ✅ HTML/Text Editor
|
|
557
|
-
- ✅ Merge Tags Support
|
|
558
|
-
- ✅ Save/Load Designs
|
|
290
|
+
### SMTP
|
|
559
291
|
|
|
560
|
-
|
|
292
|
+
| Field | Example |
|
|
293
|
+
| ----------------- | ------------------------------------------------------------ |
|
|
294
|
+
| Host | `smtp.example.com` |
|
|
295
|
+
| Port | `587` (STARTTLS) or `465` (turn on _Use SSL/TLS encryption_) |
|
|
296
|
+
| Username/password | Mailbox credentials or an app password |
|
|
561
297
|
|
|
562
|
-
|
|
563
|
-
const stats = await strapi
|
|
564
|
-
.plugin('magic-mail')
|
|
565
|
-
.service('email-designer')
|
|
566
|
-
.getStats();
|
|
567
|
-
|
|
568
|
-
console.log(stats);
|
|
569
|
-
// {
|
|
570
|
-
// total: 15,
|
|
571
|
-
// active: 12,
|
|
572
|
-
// inactive: 3,
|
|
573
|
-
// byCategory: [
|
|
574
|
-
// { category: 'transactional', count: 8 },
|
|
575
|
-
// { category: 'marketing', count: 4 },
|
|
576
|
-
// { category: 'notification', count: 3 }
|
|
577
|
-
// ],
|
|
578
|
-
// maxTemplates: -1, // unlimited (all features are free)
|
|
579
|
-
// remaining: -1 // unlimited
|
|
580
|
-
// }
|
|
581
|
-
```
|
|
298
|
+
Common hosts: Gmail `smtp.gmail.com:587` (app password), Outlook.com `smtp-mail.outlook.com:587`, Microsoft 365 `smtp.office365.com:587`.
|
|
582
299
|
|
|
583
|
-
|
|
300
|
+
TLS 1.2 or newer is required and certificates are verified. For a local mail catcher (Mailpit, MailHog) set `"allowInsecure": true` in the account config; it is honoured outside `NODE_ENV=production`, or for loopback hosts.
|
|
584
301
|
|
|
585
|
-
|
|
586
|
-
```javascript
|
|
587
|
-
// Set category when creating template
|
|
588
|
-
category: 'marketing' // Auto-routes to marketing account
|
|
589
|
-
```
|
|
302
|
+
Optional DKIM signing (account config):
|
|
590
303
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
console.log(rendered.html); // Preview HTML
|
|
304
|
+
```json
|
|
305
|
+
{
|
|
306
|
+
"dkim": {
|
|
307
|
+
"domainName": "example.com",
|
|
308
|
+
"keySelector": "default",
|
|
309
|
+
"privateKey": "-----BEGIN PRIVATE KEY-----\n..."
|
|
310
|
+
}
|
|
311
|
+
}
|
|
600
312
|
```
|
|
601
313
|
|
|
602
|
-
|
|
603
|
-
```javascript
|
|
604
|
-
// Template version history is always enabled (free for everyone).
|
|
605
|
-
```
|
|
314
|
+
Generate a key with `openssl genrsa -out dkim_private.pem 2048` and publish the public key as a TXT record at `default._domainkey.example.com`.
|
|
606
315
|
|
|
607
|
-
|
|
608
|
-
```javascript
|
|
609
|
-
// Add tags to templates
|
|
610
|
-
tags: ['onboarding', 'welcome-series', 'v2']
|
|
611
|
-
```
|
|
316
|
+
The provider value `nodemailer` is an alias of `smtp`.
|
|
612
317
|
|
|
613
|
-
|
|
318
|
+
### SendGrid
|
|
614
319
|
|
|
615
|
-
|
|
320
|
+
1. In SendGrid go to **Settings → API Keys** and create a key with the **Mail Send** permission.
|
|
321
|
+
2. In MagicMail choose **SendGrid** and paste the key. The sender address must be verified in SendGrid.
|
|
616
322
|
|
|
617
|
-
|
|
323
|
+
### Mailgun
|
|
618
324
|
|
|
619
|
-
|
|
325
|
+
1. In Mailgun copy your **Private API key** and your verified sending domain. Note the region the domain was created in (US or EU).
|
|
326
|
+
2. In MagicMail choose **Mailgun**, enter the domain (for example `mg.example.com`), pick the region and paste the key.
|
|
620
327
|
|
|
621
|
-
|
|
622
|
-
```
|
|
623
|
-
Match Type: Email Type
|
|
624
|
-
Match Value: marketing
|
|
625
|
-
Target Account: SendGrid Marketing
|
|
626
|
-
Priority: 10
|
|
627
|
-
```
|
|
328
|
+
A Mailgun domain can only be used through the API host of its region. MagicMail stores the region as `config.region` and calls:
|
|
628
329
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
Target Account: Priority SMTP
|
|
634
|
-
Fallback Account: Gmail OAuth
|
|
635
|
-
Priority: 9
|
|
636
|
-
```
|
|
330
|
+
| `config.region` | API host |
|
|
331
|
+
| --------------- | ---------------------------- |
|
|
332
|
+
| `us` (default) | `https://api.mailgun.net` |
|
|
333
|
+
| `eu` | `https://api.eu.mailgun.net` |
|
|
637
334
|
|
|
638
|
-
|
|
639
|
-
```
|
|
640
|
-
Match Type: Subject
|
|
641
|
-
Match Value: Password Reset
|
|
642
|
-
Target Account: Transactional SMTP
|
|
643
|
-
Priority: 8
|
|
644
|
-
```
|
|
335
|
+
Any other value is rejected with a validation error when the account is created or updated. Accounts saved without a region keep using the US host.
|
|
645
336
|
|
|
646
|
-
###
|
|
337
|
+
### Provider comparison
|
|
647
338
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
339
|
+
| Provider | Authentication | Transport | DKIM |
|
|
340
|
+
| ------------- | --------------------- | --------------------------- | -------------------- |
|
|
341
|
+
| Gmail | OAuth 2.0 | Gmail API | by Google |
|
|
342
|
+
| Microsoft 365 | OAuth 2.0 (Entra ID) | Microsoft Graph `sendMail` | by Microsoft |
|
|
343
|
+
| Yahoo Mail | OAuth 2.0 | SMTP with XOAUTH2 | by Yahoo |
|
|
344
|
+
| SMTP | Username and password | Nodemailer, any SMTP server | optional (see above) |
|
|
345
|
+
| SendGrid | API key | HTTPS API (v3) | by SendGrid |
|
|
346
|
+
| Mailgun | API key + domain | HTTPS API (US or EU region) | by Mailgun |
|
|
651
347
|
|
|
652
|
-
|
|
348
|
+
All providers support attachments, inline images (`cid`) and custom headers.
|
|
653
349
|
|
|
654
350
|
---
|
|
655
351
|
|
|
656
|
-
##
|
|
657
|
-
|
|
658
|
-
### Gmail OAuth Setup
|
|
352
|
+
## Routing rules and quotas
|
|
659
353
|
|
|
660
|
-
|
|
661
|
-
1. Go to [console.cloud.google.com](https://console.cloud.google.com)
|
|
662
|
-
2. Create a new project or select existing
|
|
663
|
-
3. Enable Gmail API
|
|
664
|
-
4. Create OAuth 2.0 Client ID
|
|
665
|
-
5. Add redirect URI: `http://localhost:1337/magic-mail/oauth/gmail/callback`
|
|
666
|
-
6. Copy Client ID and Client Secret
|
|
354
|
+
Routing rules send an email through a specific account when it matches a condition. Manage them under **MagicMail → Routing Rules**.
|
|
667
355
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
356
|
+
| Match type | Matches when |
|
|
357
|
+
| -------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
358
|
+
| **Email Type** | the email type equals the value (`transactional`, `marketing`, `notification`) |
|
|
359
|
+
| **Recipient** | any recipient matches: `@domain.com` = domain, `user@domain.com` = exact address, anything else = substring |
|
|
360
|
+
| **Subject** | the subject contains the value (case-insensitive) |
|
|
361
|
+
| **Template** | the template name or Reference ID equals the value |
|
|
362
|
+
| **Custom** | the email's `customField` equals the value |
|
|
674
363
|
|
|
675
|
-
|
|
676
|
-
- `https://www.googleapis.com/auth/gmail.send`
|
|
677
|
-
- `https://www.googleapis.com/auth/userinfo.email`
|
|
364
|
+
Examples:
|
|
678
365
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
1. Go to [portal.azure.com](https://portal.azure.com)
|
|
685
|
-
2. Navigate to **Azure Active Directory → App registrations**
|
|
686
|
-
3. Click **"New registration"**
|
|
687
|
-
4. Name: "MagicMail"
|
|
688
|
-
5. Account type: **"Accounts in this organizational directory only"**
|
|
689
|
-
6. Copy **Application (client) ID** AND **Directory (tenant) ID**
|
|
690
|
-
|
|
691
|
-
**2. Add Redirect URI**
|
|
692
|
-
```
|
|
693
|
-
http://localhost:1337/magic-mail/oauth/microsoft/callback
|
|
694
|
-
```
|
|
366
|
+
| Rule | Match | Account | Fallback | Priority |
|
|
367
|
+
| --------------------- | ---------------------------------- | ------------------ | ------------------ | -------- |
|
|
368
|
+
| Password resets first | Subject contains `Password Reset` | Transactional SMTP | Gmail Support | 1000000 |
|
|
369
|
+
| Marketing campaigns | Email type `marketing` | Marketing SendGrid | | 50 |
|
|
370
|
+
| VIP customers | Recipient `@vip-customers.example` | Gmail Support | Transactional SMTP | 10 |
|
|
695
371
|
|
|
696
|
-
**
|
|
372
|
+
**Order.** Rules are checked from the highest to the lowest priority (1 to 1,000,000); rules with the same priority are checked oldest first. The first match wins. The admin list and `GET /magic-mail/routing-rules` show them in this order.
|
|
697
373
|
|
|
698
|
-
|
|
699
|
-
- ✅ `Mail.Send` - Send emails as the signed-in user
|
|
700
|
-
- ✅ `User.Read` - Read user profile
|
|
701
|
-
- ✅ `offline_access` - Maintain access (refresh tokens)
|
|
702
|
-
- ✅ `openid` - Sign users in
|
|
703
|
-
- ✅ `email` - View users' email address
|
|
374
|
+
**Account order for one email.** The account of the first matching rule, then that rule's fallback account, then the accounts (and fallbacks) of the next matching rules, then the primary account, then the remaining active accounts by account priority (1 to 10, higher first). Accounts over their quota are skipped.
|
|
704
375
|
|
|
705
|
-
**
|
|
376
|
+
**Failover.** Errors that guarantee the provider did not accept the message (connection or authentication failures, missing credentials, HTTP 401/403/429/500/502/503, SMTP envelope rejection) move on to the next account. Ambiguous errors such as timeouts are returned to the caller instead, so an email is never delivered twice.
|
|
706
377
|
|
|
707
|
-
**
|
|
708
|
-
1. **Certificates & secrets** → **New client secret**
|
|
709
|
-
2. Copy the **VALUE** (not the Secret ID)
|
|
710
|
-
3. Store it securely!
|
|
378
|
+
**Account references.** Rules reference accounts by name. Creating or editing a rule with an unknown account name returns 400. Renaming an account updates its rules; deleting an account deactivates the rules that target it and clears it where it is the fallback.
|
|
711
379
|
|
|
712
|
-
**
|
|
713
|
-
1. Choose "Microsoft OAuth" provider
|
|
714
|
-
2. Paste **Tenant ID**, **Client ID**, and **Client Secret**
|
|
715
|
-
3. Click "Connect with Microsoft"
|
|
716
|
-
4. Authorize
|
|
717
|
-
5. Done! ✅
|
|
380
|
+
**Quotas.** Each account can have an hourly and a daily limit (0 = unlimited). Quota is reserved atomically before dispatch, so concurrent sends cannot exceed it. Windows follow the clock hour and the calendar day in UTC; set `quotaTimezone` to use another IANA time zone.
|
|
718
381
|
|
|
719
382
|
---
|
|
720
383
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
**1. Yahoo Developer Console**
|
|
724
|
-
1. Go to [developer.yahoo.com/apps](https://developer.yahoo.com/apps)
|
|
725
|
-
2. Click **"Create an App"**
|
|
726
|
-
3. Fill in app details
|
|
727
|
-
|
|
728
|
-
**2. Add Redirect URI**
|
|
729
|
-
```
|
|
730
|
-
http://localhost:1337/magic-mail/oauth/yahoo/callback
|
|
731
|
-
```
|
|
384
|
+
## Templates and the visual editor
|
|
732
385
|
|
|
733
|
-
|
|
734
|
-
- ✅ `Mail` - Send and manage emails
|
|
735
|
-
- ✅ `OpenID Connect` - User authentication
|
|
386
|
+
Everything in the designer is included, with no tiers:
|
|
736
387
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
388
|
+
| Feature | Included |
|
|
389
|
+
| --------------------------------- | -------- |
|
|
390
|
+
| **Visual Designer (Unlayer)** | ✅ |
|
|
391
|
+
| **Plain HTML/Text editing** | ✅ |
|
|
392
|
+
| **Unlimited Templates** | ✅ |
|
|
393
|
+
| **Mustache Variables** | ✅ |
|
|
394
|
+
| **Import / Export** | ✅ |
|
|
395
|
+
| **Template Versioning** | ✅ |
|
|
396
|
+
| **Users & Permissions templates** | ✅ |
|
|
742
397
|
|
|
743
|
-
|
|
398
|
+
### Creating templates
|
|
744
399
|
|
|
745
|
-
|
|
400
|
+
1. Go to **MagicMail → Email Templates** and click **Create template**.
|
|
401
|
+
2. Build the email in the **Visual Designer** tab, or write HTML and text in the **Plain Text** tab.
|
|
402
|
+
3. Use Mustache variables for dynamic content:
|
|
746
403
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
Port: 587 (STARTTLS) or 465 (SSL/TLS)
|
|
752
|
-
Username: your-email@domain.com
|
|
753
|
-
Password: your-password (or App Password)
|
|
754
|
-
```
|
|
404
|
+
```html
|
|
405
|
+
<h1>Welcome {{user.firstName}}!</h1>
|
|
406
|
+
<p>Thanks for joining {{company.name}}</p>
|
|
407
|
+
```
|
|
755
408
|
|
|
756
|
-
**
|
|
757
|
-
- **Gmail:** `smtp.gmail.com:587` (use App Password!)
|
|
758
|
-
- **Outlook:** `smtp-mail.outlook.com:587`
|
|
759
|
-
- **Office365:** `smtp.office365.com:587`
|
|
760
|
-
- **Custom:** Your own mail server
|
|
409
|
+
4. Save. Each template has a numeric **Reference ID** (for example `100`) that you use when sending. It cannot be changed after creation.
|
|
761
410
|
|
|
762
|
-
|
|
763
|
-
1. Google Account → Security → 2-Step Verification
|
|
764
|
-
2. App passwords → Generate
|
|
765
|
-
3. Use the 16-character password in MagicMail
|
|
411
|
+
The server fills `{{system.date}}`, `{{system.year}}` and `{{system.unsubscribe}}` (the email's unsubscribe URL); callers cannot override them.
|
|
766
412
|
|
|
767
|
-
|
|
413
|
+
### Sending a template
|
|
768
414
|
|
|
769
|
-
For custom domains, you can add DKIM:
|
|
770
415
|
```javascript
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
416
|
+
await strapi
|
|
417
|
+
.plugin('magic-mail')
|
|
418
|
+
.service('email-router')
|
|
419
|
+
.send({
|
|
420
|
+
to: 'user@example.com',
|
|
421
|
+
templateId: 100, // Reference ID
|
|
422
|
+
templateData: {
|
|
423
|
+
user: { firstName: 'Ada' },
|
|
424
|
+
company: { name: 'Example Shop' },
|
|
425
|
+
},
|
|
426
|
+
});
|
|
779
427
|
```
|
|
780
428
|
|
|
781
|
-
|
|
429
|
+
`strapi.plugin('email').service('email').send()` accepts the same `templateId` and `templateData` fields. The template list also shows a ready-made code snippet per template.
|
|
782
430
|
|
|
783
|
-
###
|
|
431
|
+
### Categories
|
|
784
432
|
|
|
785
|
-
|
|
786
|
-
1. Go to [app.sendgrid.com](https://app.sendgrid.com)
|
|
787
|
-
2. Settings → API Keys → Create API Key
|
|
788
|
-
3. Scope: **Mail Send** (Full Access)
|
|
789
|
-
4. Copy the API key
|
|
433
|
+
Templates have a category: `transactional`, `marketing`, `notification` or `custom`. The first three become the email `type` when the caller passes none, so a rule _Email Type = marketing_ also catches marketing templates.
|
|
790
434
|
|
|
791
|
-
|
|
792
|
-
1. Choose "SendGrid" provider
|
|
793
|
-
2. Paste API Key
|
|
794
|
-
3. Done! ✅
|
|
435
|
+
### Versions, import and export
|
|
795
436
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
### Mailgun Setup
|
|
799
|
-
|
|
800
|
-
**1. Get API Key & Domain**
|
|
801
|
-
1. Go to [app.mailgun.com](https://app.mailgun.com)
|
|
802
|
-
2. Settings → API Security → Copy **Private API Key**
|
|
803
|
-
3. Sending → Domains → Copy your verified domain
|
|
437
|
+
- Version 1 is the created template. Every save that changes the content (name, subject, design, HTML, text or tags) adds a version with the saved content. Saves that only toggle `isActive` or the category add none. Restoring a version saves its content as a new version.
|
|
438
|
+
- Export writes JSON (all templates, or the ones you select); import accepts MagicMail exports and exports of `strapi-plugin-email-designer-5`. The `isActive` state is kept.
|
|
804
439
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
---
|
|
812
|
-
|
|
813
|
-
## 🛡️ Security Features
|
|
814
|
-
|
|
815
|
-
### Encryption
|
|
816
|
-
- ✅ **AES-256-GCM** encryption for all credentials
|
|
817
|
-
- ✅ **No plain text passwords** in database
|
|
818
|
-
- ✅ **OAuth tokens encrypted** at rest
|
|
819
|
-
- ✅ **Secure token refresh** mechanism
|
|
820
|
-
|
|
821
|
-
### Transport Security
|
|
822
|
-
- ✅ **TLS 1.2+ enforced** (SMTP)
|
|
823
|
-
- ✅ **Certificate verification** enabled
|
|
824
|
-
- ✅ **HTTPS only** for API providers
|
|
825
|
-
|
|
826
|
-
### Email Authentication
|
|
827
|
-
- ✅ **DKIM signing** (optional for SMTP, automatic for OAuth)
|
|
828
|
-
- ✅ **SPF validation** support
|
|
829
|
-
- ✅ **DMARC compliance** - All providers pass DMARC checks
|
|
830
|
-
- ✅ **Proper Message-ID** generation (RFC 5322)
|
|
831
|
-
|
|
832
|
-
### Content Security
|
|
833
|
-
- ✅ **XSS prevention** - No `<script>` tags allowed
|
|
834
|
-
- ✅ **Input validation** - Email format, subject length
|
|
835
|
-
- ✅ **Spam trigger detection** - Warns about suspicious patterns
|
|
836
|
-
- ✅ **MIME type validation**
|
|
837
|
-
|
|
838
|
-
### Compliance Headers
|
|
839
|
-
- ✅ **List-Unsubscribe** (RFC 8058) - GDPR/CAN-SPAM compliant
|
|
840
|
-
- ✅ **Auto-Submitted** (RFC 3834) - Prevents auto-responder loops
|
|
841
|
-
- ✅ **Precedence: bulk** for marketing emails
|
|
842
|
-
- ✅ **X-Priority** and **Importance** headers
|
|
843
|
-
|
|
844
|
-
---
|
|
845
|
-
|
|
846
|
-
## 📊 Features Overview
|
|
847
|
-
|
|
848
|
-
### Multi-Account Management
|
|
849
|
-
- Unlimited email accounts
|
|
850
|
-
- Provider-specific configuration
|
|
851
|
-
- Active/Inactive status
|
|
852
|
-
- Primary account designation
|
|
853
|
-
- Per-account priority (1-10)
|
|
854
|
-
- Daily and hourly rate limits
|
|
855
|
-
|
|
856
|
-
### Smart Routing Engine
|
|
857
|
-
- **Email Type Routing** - transactional, marketing, notification
|
|
858
|
-
- **Recipient Matching** - Route by email domain or address
|
|
859
|
-
- **Subject Matching** - Route by subject keywords
|
|
860
|
-
- **Template Matching** - Route by template name
|
|
861
|
-
- **Custom Field Matching** - Flexible custom conditions
|
|
862
|
-
- **Priority-based evaluation** - Higher priority rules match first
|
|
863
|
-
- **Fallback accounts** - Automatic failover per rule
|
|
864
|
-
|
|
865
|
-
### Rate Limiting & Failover
|
|
866
|
-
- Per-account daily limits
|
|
867
|
-
- Per-account hourly limits
|
|
868
|
-
- Automatic counter reset (hourly/daily)
|
|
869
|
-
- Smart failover to backup accounts
|
|
870
|
-
- Rate limit hit detection
|
|
871
|
-
- Account health monitoring
|
|
872
|
-
|
|
873
|
-
### Analytics & Monitoring
|
|
874
|
-
- Real-time statistics dashboard
|
|
875
|
-
- Emails sent today (per account and total)
|
|
876
|
-
- Total emails sent (lifetime)
|
|
877
|
-
- Active/Inactive account count
|
|
878
|
-
- Success/Failure tracking
|
|
879
|
-
- Last used timestamps
|
|
880
|
-
- Visual progress bars for rate limits
|
|
881
|
-
|
|
882
|
-
### Testing Features
|
|
883
|
-
- **Test Direct** - Test specific account directly
|
|
884
|
-
- **Test Strapi Service** - Verify Email Designer compatibility
|
|
885
|
-
- **Priority Testing** - Test high/normal priority headers
|
|
886
|
-
- **Marketing Email Testing** - Test List-Unsubscribe headers
|
|
887
|
-
- **Detailed test results** - Shows all security features applied
|
|
888
|
-
|
|
889
|
-
---
|
|
890
|
-
|
|
891
|
-
## 🔧 Configuration
|
|
892
|
-
|
|
893
|
-
### Basic Setup (No .env Required!)
|
|
894
|
-
|
|
895
|
-
All configuration is done via the Admin Panel. However, for production deployments, you can set:
|
|
896
|
-
|
|
897
|
-
```env
|
|
898
|
-
# Optional - Base URL for OAuth callbacks
|
|
899
|
-
URL=https://yourdomain.com
|
|
900
|
-
|
|
901
|
-
# Optional - Encryption key (auto-generated if not provided)
|
|
902
|
-
ENCRYPTION_KEY=your-32-character-secret-key
|
|
440
|
+
```javascript
|
|
441
|
+
const designer = strapi.plugin('magic-mail').service('email-designer');
|
|
442
|
+
const versions = await designer.getVersions(templateDocumentId);
|
|
443
|
+
await designer.restoreVersion(templateDocumentId, versionDocumentId);
|
|
444
|
+
const exported = await designer.exportTemplates(); // or exportTemplates([documentId, ...])
|
|
445
|
+
const results = await designer.importTemplates(exported);
|
|
903
446
|
```
|
|
904
447
|
|
|
905
|
-
###
|
|
906
|
-
|
|
907
|
-
Each account can have:
|
|
908
|
-
|
|
909
|
-
**Basic Settings:**
|
|
910
|
-
- Name (unique identifier)
|
|
911
|
-
- Description
|
|
912
|
-
- Provider type
|
|
913
|
-
- From Email
|
|
914
|
-
- From Name
|
|
915
|
-
- Reply-To Email
|
|
448
|
+
### Users & Permissions templates
|
|
916
449
|
|
|
917
|
-
**
|
|
918
|
-
- OAuth credentials (encrypted)
|
|
919
|
-
- SMTP credentials (encrypted)
|
|
920
|
-
- API keys (encrypted)
|
|
921
|
-
- DKIM configuration (optional)
|
|
450
|
+
The **Core emails** tab edits the _Reset password_ and _Email address confirmation_ templates of Users & Permissions. This needs the _Users & Permissions → Email templates_ read/update permissions in addition to MagicMail's. Only `{{ URL }}`, `{{ SERVER_URL }}`, `{{ ADMIN_URL }}`, `{{ CODE }}`, `{{ TOKEN }}`, `{{ USER }}`, `{{ USER.email }}` and `{{ USER.username }}` are accepted; they are stored as `<%= KEY %>`, the syntax Users & Permissions fills in.
|
|
922
451
|
|
|
923
|
-
|
|
924
|
-
- Daily email limit (0 = unlimited)
|
|
925
|
-
- Hourly email limit (0 = unlimited)
|
|
926
|
-
- Priority (1-10, higher = preferred)
|
|
452
|
+
### The visual editor (Unlayer) and privacy
|
|
927
453
|
|
|
928
|
-
|
|
929
|
-
- Active/Inactive
|
|
930
|
-
- Primary account designation
|
|
454
|
+
The visual designer embeds the [Unlayer](https://unlayer.com) editor through `react-email-editor`. What that means for you:
|
|
931
455
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
456
|
+
- The editor script loads from `https://editor.unlayer.com/embed.js?2` and the editor build is pinned (`1.524.0`). Unlayer serves the editor, so the browser of each editor user contacts Unlayer's servers.
|
|
457
|
+
- No Unlayer `projectId` is sent unless you configure one.
|
|
458
|
+
- Disabled: Unlayer image hosting, stock images, the AI assistant, the hosted image editor, "preview in browser" and third-party placeholder images. New image blocks use an inline placeholder.
|
|
459
|
+
- Images you add are uploaded to the **Strapi Media Library** (`POST /upload`), so editors also need _Media Library → Create (upload)_.
|
|
460
|
+
- If the editor cannot load (CSP, offline, blocked), the page explains why and the **Plain Text** tab keeps working. You never need Unlayer to send templates.
|
|
935
461
|
|
|
936
|
-
|
|
462
|
+
Optional overrides (`config/plugins.js`):
|
|
937
463
|
|
|
938
464
|
```javascript
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
replyTo: 'reply@example.com',
|
|
948
|
-
type: 'transactional', // or 'marketing', 'notification'
|
|
949
|
-
priority: 'normal', // or 'high'
|
|
950
|
-
accountName: 'Specific Account', // Force specific account
|
|
951
|
-
unsubscribeUrl: 'https://site.com/unsubscribe', // For marketing emails
|
|
952
|
-
|
|
953
|
-
attachments: [
|
|
954
|
-
{
|
|
955
|
-
filename: 'document.pdf',
|
|
956
|
-
path: './path/to/file.pdf',
|
|
957
|
-
contentType: 'application/pdf',
|
|
465
|
+
module.exports = () => ({
|
|
466
|
+
'magic-mail': {
|
|
467
|
+
config: {
|
|
468
|
+
designer: {
|
|
469
|
+
projectId: null, // your own Unlayer project id (positive integer)
|
|
470
|
+
scriptUrl: null, // https URL of a vetted, self-hosted copy of embed.js
|
|
471
|
+
version: null, // exact Unlayer build, e.g. '1.524.0'
|
|
472
|
+
},
|
|
958
473
|
},
|
|
959
|
-
|
|
960
|
-
});
|
|
961
|
-
```
|
|
962
|
-
|
|
963
|
-
### Send Email (Direct MagicMail API)
|
|
964
|
-
|
|
965
|
-
```javascript
|
|
966
|
-
await strapi.plugin('magic-mail').service('email-router').send({
|
|
967
|
-
to: 'user@example.com',
|
|
968
|
-
subject: 'Test Email',
|
|
969
|
-
html: '<p>Content</p>',
|
|
970
|
-
accountName: 'Gmail OAuth', // Optional - force specific account
|
|
971
|
-
type: 'transactional',
|
|
972
|
-
priority: 'normal',
|
|
474
|
+
},
|
|
973
475
|
});
|
|
974
476
|
```
|
|
975
477
|
|
|
976
|
-
###
|
|
977
|
-
|
|
978
|
-
```javascript
|
|
979
|
-
const accounts = await strapi.plugin('magic-mail').service('account-manager').getAllAccounts();
|
|
980
|
-
```
|
|
981
|
-
|
|
982
|
-
### Test Account
|
|
983
|
-
|
|
984
|
-
```javascript
|
|
985
|
-
const result = await strapi.plugin('magic-mail').service('account-manager').testAccount(
|
|
986
|
-
accountId,
|
|
987
|
-
'test@example.com',
|
|
988
|
-
{
|
|
989
|
-
priority: 'high',
|
|
990
|
-
type: 'marketing',
|
|
991
|
-
unsubscribeUrl: 'https://example.com/unsubscribe',
|
|
992
|
-
}
|
|
993
|
-
);
|
|
994
|
-
```
|
|
995
|
-
|
|
996
|
-
---
|
|
997
|
-
|
|
998
|
-
## 🧪 Testing Your Setup
|
|
999
|
-
|
|
1000
|
-
### Test via Admin UI
|
|
1001
|
-
|
|
1002
|
-
1. Go to **Email Accounts**
|
|
1003
|
-
2. Click **Test** button on any account
|
|
1004
|
-
3. Enter recipient email
|
|
1005
|
-
4. Select test options:
|
|
1006
|
-
- **Priority:** Normal / High
|
|
1007
|
-
- **Email Type:** Transactional / Marketing / Notification
|
|
1008
|
-
- **Unsubscribe URL:** (for marketing emails)
|
|
1009
|
-
5. Choose test method:
|
|
1010
|
-
- **Test Direct** - Direct send via account
|
|
1011
|
-
- **Test Strapi Service** - Via Strapi email service (tests Email Designer compatibility)
|
|
478
|
+
### Content Security Policy for the visual editor
|
|
1012
479
|
|
|
1013
|
-
|
|
480
|
+
Strapi's `strapi::security` middleware blocks third-party scripts and frames. To use the visual designer, allow the Unlayer origin in `config/middlewares.js`. Do **not** add `'unsafe-inline'` or `'unsafe-eval'`.
|
|
1014
481
|
|
|
1015
482
|
```javascript
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
'your-email@example.com',
|
|
483
|
+
module.exports = [
|
|
484
|
+
'strapi::logger',
|
|
485
|
+
'strapi::errors',
|
|
1020
486
|
{
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
487
|
+
name: 'strapi::security',
|
|
488
|
+
config: {
|
|
489
|
+
contentSecurityPolicy: {
|
|
490
|
+
useDefaults: true,
|
|
491
|
+
directives: {
|
|
492
|
+
'script-src': ["'self'", 'https://editor.unlayer.com'],
|
|
493
|
+
'frame-src': ["'self'", 'https://editor.unlayer.com'],
|
|
494
|
+
// Media Library images shown in the editor and the preview:
|
|
495
|
+
'img-src': ["'self'", 'data:', 'blob:', 'https://your-media-host.example'],
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
'strapi::cors',
|
|
501
|
+
'strapi::poweredBy',
|
|
502
|
+
'strapi::query',
|
|
503
|
+
'strapi::body',
|
|
504
|
+
'strapi::session',
|
|
505
|
+
'strapi::favicon',
|
|
506
|
+
'strapi::public',
|
|
507
|
+
];
|
|
1026
508
|
```
|
|
1027
509
|
|
|
1028
|
-
|
|
510
|
+
With `designer.scriptUrl` (self-hosted `embed.js`) put that origin into `script-src` instead; the editor iframe still loads from `editor.unlayer.com`, so keep it in `frame-src`. The Unlayer script runs in the admin origin. Only allow it if you accept that trust.
|
|
1029
511
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
**In Gmail:**
|
|
1033
|
-
1. Open email → Click ⋮ (three dots)
|
|
1034
|
-
2. "Show original"
|
|
1035
|
-
3. Look for:
|
|
1036
|
-
```
|
|
1037
|
-
X-Mailer: MagicMail/1.0
|
|
1038
|
-
X-Priority: 1 (Highest)
|
|
1039
|
-
Importance: high
|
|
1040
|
-
List-Unsubscribe: <https://...>
|
|
1041
|
-
Message-ID: <...>
|
|
1042
|
-
DKIM-Signature: ...
|
|
1043
|
-
```
|
|
512
|
+
---
|
|
1044
513
|
|
|
1045
|
-
|
|
1046
|
-
1. Email → File → Properties
|
|
1047
|
-
2. View "Internet headers"
|
|
1048
|
-
3. Should show "High Importance" with red exclamation mark
|
|
514
|
+
## Tracking, privacy and retention
|
|
1049
515
|
|
|
1050
|
-
|
|
516
|
+
Open tracking adds a 1×1 pixel; click tracking rewrites `<a href>` links to a redirect. Both are on by default (**Settings → MagicMail → Plugin Settings**) and can be turned off per email with `enableTracking: false` or `skipLinkTracking: true`.
|
|
1051
517
|
|
|
1052
|
-
|
|
518
|
+
**Public URL.** Tracking URLs use the _Tracking base URL_ setting, then `server.url`, then `server.absoluteUrl`, plus your REST prefix (`api.rest.prefix`, default `/api`), for example `https://cms.example.com/api/magic-mail/track/click/...`. In production a loopback host (`localhost`, `127.0.0.1`, `0.0.0.0`) is never used: without a public URL, emails are sent **untracked** and one warning is logged.
|
|
1053
519
|
|
|
1054
|
-
|
|
520
|
+
**Never tracked.** Authentication and one-time-token links (password reset, confirmation, magic links, OAuth, parameters such as `token`, `code`, `key`, `signature`, `session`, long random values), `mailto:` and `tel:` links, and links beyond `maxLinksPerEmail` (100). Emails sent through the Content API are click-tracked only for hosts in `tracking.allowedLinkHosts` (none by default).
|
|
1055
521
|
|
|
1056
|
-
|
|
1057
|
-
|----------|--------|-------------|
|
|
1058
|
-
| **DKIM** | ✅ | DomainKeys Identified Mail - Signature validation |
|
|
1059
|
-
| **SPF** | ✅ | Sender Policy Framework - Sender verification |
|
|
1060
|
-
| **DMARC** | ✅ | Domain-based Authentication - Policy enforcement |
|
|
1061
|
-
| **TLS 1.2+** | ✅ | Transport Layer Security - Encrypted transmission |
|
|
1062
|
-
| **AES-256-GCM** | ✅ | Credential encryption at rest |
|
|
522
|
+
**What is stored.** Open and click events store the IP truncated to /24 (IPv4) or /48 (IPv6) and at most 512 characters of user agent; no location. Only Koa's `request.ip` is used, so behind a reverse proxy enable [`server.proxy`](https://docs.strapi.io/cms/configurations/server), otherwise every recipient shares the proxy's address.
|
|
1063
523
|
|
|
1064
|
-
|
|
524
|
+
**Retention.** Every 6 hours MagicMail deletes events older than `tracking.eventRetentionDays` (90) and tracked links older than `tracking.linkTtlDays` (180; expired links then show the fallback page). `tracking.logRetentionDays` optionally deletes old email logs with their events and links. Logs contain personal data (recipient addresses); you can also delete them with **Analytics → Clear All** or with `DELETE /magic-mail/analytics/emails?olderThan=<ISO date>`.
|
|
1065
525
|
|
|
1066
|
-
|
|
1067
|
-
|------------|--------|----------------|
|
|
1068
|
-
| **GDPR** | ✅ | List-Unsubscribe header for marketing emails |
|
|
1069
|
-
| **CAN-SPAM** | ✅ | One-click unsubscribe mechanism (RFC 8058) |
|
|
1070
|
-
| **RFC 5322** | ✅ | Proper email format and headers |
|
|
1071
|
-
| **RFC 3834** | ✅ | Auto-Submitted header prevents loops |
|
|
1072
|
-
| **RFC 2156** | ✅ | Priority and Importance headers |
|
|
526
|
+
**Rate limit.** The tracking routes allow 300 requests per minute and IP. Above that, opens and clicks are no longer recorded but recipients still get the pixel and the redirect; only above 3,000 per minute is a plain-text 429 returned.
|
|
1073
527
|
|
|
1074
|
-
|
|
528
|
+
**Signing.** Tracking hashes are signed with `MAGIC_MAIL_TRACKING_SECRET`, falling back to `MAGIC_MAIL_ENCRYPTION_KEY`. To rotate, keep the old values in `MAGIC_MAIL_TRACKING_SECRET_PREVIOUS` (comma-separated) while old emails matter.
|
|
1075
529
|
|
|
1076
|
-
|
|
1077
|
-
- ✅ Valid recipient email format
|
|
1078
|
-
- ✅ Non-empty subject line
|
|
1079
|
-
- ✅ Subject length (warns if > 200 chars)
|
|
1080
|
-
- ✅ Content presence (text or html required)
|
|
1081
|
-
- ✅ No `<script>` tags in HTML
|
|
1082
|
-
- ✅ No `javascript:` protocol in links
|
|
1083
|
-
- ✅ Spam trigger detection (warns only)
|
|
530
|
+
Mention open and click tracking in your privacy notice.
|
|
1084
531
|
|
|
1085
532
|
---
|
|
1086
533
|
|
|
1087
|
-
##
|
|
1088
|
-
|
|
1089
|
-
### Dashboard Stats
|
|
534
|
+
## Content API
|
|
1090
535
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
-
|
|
1094
|
-
-
|
|
536
|
+
| Method | Path | Auth | Purpose |
|
|
537
|
+
| ------ | --------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------- |
|
|
538
|
+
| `POST` | `/api/magic-mail/send` | API token | Send through the router with all [send options](#send-options) |
|
|
539
|
+
| `POST` | `/api/magic-mail/send-message` | API token | Simple email: `to`, `cc`, `bcc`, `subject`, `message`/`text`/`html`, template |
|
|
540
|
+
| `GET` | `/api/magic-mail/track/open/:emailId/:recipientHash` | public | Open-tracking pixel |
|
|
541
|
+
| `GET` | `/api/magic-mail/track/click/:emailId/:linkHash/:recipientHash` | public | Click redirect |
|
|
1095
542
|
|
|
1096
|
-
|
|
1097
|
-
- Emails sent today
|
|
1098
|
-
- Total emails sent
|
|
1099
|
-
- Rate limit usage (visual progress bars)
|
|
1100
|
-
- Last used timestamp
|
|
1101
|
-
- Success/failure rate
|
|
543
|
+
### The API token rule
|
|
1102
544
|
|
|
1103
|
-
|
|
545
|
+
The send routes accept **API tokens only**. Create a _Custom_ token under **Settings → API Tokens** that allows only **Magic-mail → controller → `send`** (and `sendMessage` if you use it). Never enable these actions for the _Public_ or _Authenticated_ role: a users-permissions request is rejected with 403 even if the role has the permission.
|
|
1104
546
|
|
|
1105
|
-
|
|
1106
|
-
- Recipient
|
|
1107
|
-
- Sender
|
|
1108
|
-
- Subject
|
|
1109
|
-
- Account used
|
|
1110
|
-
- Status (sent/failed)
|
|
1111
|
-
- Timestamp
|
|
1112
|
-
- Email type
|
|
1113
|
-
- Error message (if failed)
|
|
547
|
+
To let users-permissions roles send as well (the pre-4.0 behaviour):
|
|
1114
548
|
|
|
1115
|
-
Access logs via:
|
|
1116
549
|
```javascript
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
550
|
+
// config/plugins.js
|
|
551
|
+
module.exports = () => ({
|
|
552
|
+
'magic-mail': {
|
|
553
|
+
config: { contentApi: { allowUserRoles: true } },
|
|
554
|
+
},
|
|
1121
555
|
});
|
|
1122
556
|
```
|
|
1123
557
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
## 🔄 Integration with Email Designer
|
|
1127
|
-
|
|
1128
|
-
**MagicMail is fully compatible with [strapi-plugin-email-designer-5](https://www.npmjs.com/package/strapi-plugin-email-designer-5)!**
|
|
1129
|
-
|
|
1130
|
-
### How It Works
|
|
1131
|
-
|
|
1132
|
-
1. Install Email Designer:
|
|
1133
|
-
```bash
|
|
1134
|
-
npm install strapi-plugin-email-designer-5
|
|
1135
|
-
```
|
|
1136
|
-
|
|
1137
|
-
2. Create email templates in Email Designer
|
|
1138
|
-
|
|
1139
|
-
3. Send via template:
|
|
1140
|
-
```javascript
|
|
1141
|
-
// Email Designer internally uses strapi.plugin('email').service('email').send()
|
|
1142
|
-
// MagicMail automatically intercepts it!
|
|
1143
|
-
```
|
|
1144
|
-
|
|
1145
|
-
4. Your email is automatically:
|
|
1146
|
-
- ✅ Routed through MagicMail's smart routing
|
|
1147
|
-
- ✅ Sent via the best account (based on rules)
|
|
1148
|
-
- ✅ Rate limited and failover protected
|
|
1149
|
-
- ✅ Logged and tracked
|
|
1150
|
-
|
|
1151
|
-
**No configuration needed - it just works!** 🎉
|
|
558
|
+
With user roles allowed, the email log is always attributed to the authenticated user and a body `userId` is ignored. With an API token, `userId` must be the id or documentId of an existing users-permissions user (else 400).
|
|
1152
559
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
## 🚨 Troubleshooting
|
|
560
|
+
The body is validated strictly: unknown fields, `path`/`href` attachments and invalid addresses return 400. Both send routes share a budget of 60 requests per minute per token (in process memory, so each Strapi instance counts separately).
|
|
1156
561
|
|
|
1157
|
-
###
|
|
562
|
+
### Example: send
|
|
1158
563
|
|
|
1159
|
-
**1. Check Logs**
|
|
1160
564
|
```bash
|
|
1161
|
-
|
|
1162
|
-
|
|
565
|
+
curl -X POST https://cms.example.com/api/magic-mail/send \
|
|
566
|
+
-H "Authorization: Bearer $MAGIC_MAIL_API_TOKEN" \
|
|
567
|
+
-H "Content-Type: application/json" \
|
|
568
|
+
-d '{
|
|
569
|
+
"to": "customer@example.com",
|
|
570
|
+
"templateId": 100,
|
|
571
|
+
"templateData": { "user": { "firstName": "Ada" } },
|
|
572
|
+
"type": "transactional"
|
|
573
|
+
}'
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
```json
|
|
577
|
+
{
|
|
578
|
+
"success": true,
|
|
579
|
+
"accountUsed": "Transactional SMTP",
|
|
580
|
+
"messageId": "<…@example.com>",
|
|
581
|
+
"emailId": "…"
|
|
582
|
+
}
|
|
1163
583
|
```
|
|
1164
584
|
|
|
1165
|
-
|
|
1166
|
-
- Is the account marked as "Active"?
|
|
1167
|
-
- Test the account using the Test button
|
|
1168
|
-
- Check rate limits haven't been exceeded
|
|
1169
|
-
|
|
1170
|
-
**3. Check Spam Folder**
|
|
1171
|
-
- First emails from new accounts may land in spam
|
|
1172
|
-
- Check spam/junk folders
|
|
1173
|
-
- Mark as "Not Spam" to train filters
|
|
1174
|
-
|
|
1175
|
-
**4. Verify Credentials**
|
|
1176
|
-
- SMTP: Username and password correct?
|
|
1177
|
-
- Gmail: Using App Password (not regular password)?
|
|
1178
|
-
- OAuth: Tokens may have expired - reconnect
|
|
585
|
+
### Example: send-message
|
|
1179
586
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
```
|
|
587
|
+
```bash
|
|
588
|
+
curl -X POST https://cms.example.com/api/magic-mail/send-message \
|
|
589
|
+
-H "Authorization: Bearer $MAGIC_MAIL_API_TOKEN" \
|
|
590
|
+
-H "Content-Type: application/json" \
|
|
591
|
+
-d '{ "to": "customer@example.com", "subject": "Your code", "message": "Your code is 123456." }'
|
|
592
|
+
```
|
|
1187
593
|
|
|
1188
|
-
|
|
1189
|
-
- Generate DKIM key pair
|
|
1190
|
-
- Add public key to DNS
|
|
1191
|
-
- Add private key to MagicMail SMTP config
|
|
594
|
+
The response has the same shape as `/send`.
|
|
1192
595
|
|
|
1193
|
-
|
|
1194
|
-
```
|
|
1195
|
-
v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com
|
|
1196
|
-
```
|
|
596
|
+
### Errors
|
|
1197
597
|
|
|
1198
|
-
|
|
1199
|
-
- ✅ DKIM handled automatically
|
|
1200
|
-
- ✅ SPF handled automatically
|
|
1201
|
-
- ✅ DMARC should pass automatically
|
|
598
|
+
Errors use Strapi's envelope:
|
|
1202
599
|
|
|
1203
|
-
|
|
600
|
+
```json
|
|
601
|
+
{
|
|
602
|
+
"data": null,
|
|
603
|
+
"error": { "status": 400, "name": "ValidationError", "message": "…", "details": {} }
|
|
604
|
+
}
|
|
605
|
+
```
|
|
1204
606
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
- Mailgun: Varies by plan
|
|
607
|
+
| Status | When |
|
|
608
|
+
| ------ | --------------------------------------------------------------------------------------------------- |
|
|
609
|
+
| 400 | Invalid or unknown fields, invalid addresses, `userId` of a missing user |
|
|
610
|
+
| 401 | Invalid or expired token |
|
|
611
|
+
| 403 | No token, a token without the action, or a users-permissions caller while `allowUserRoles` is false |
|
|
612
|
+
| 429 | More than 60 send requests per minute (`TooManyRequestsError`, `Retry-After` header) |
|
|
613
|
+
| 500 | Delivery failed (no account could send, or the provider rejected the email) |
|
|
1213
614
|
|
|
1214
|
-
|
|
615
|
+
---
|
|
1215
616
|
|
|
1216
|
-
|
|
1217
|
-
- Ensure Gmail API is enabled
|
|
1218
|
-
- Check redirect URI matches exactly
|
|
1219
|
-
- Clear browser cookies and try again
|
|
617
|
+
## Admin API
|
|
1220
618
|
|
|
1221
|
-
|
|
1222
|
-
- Verify all 5 permissions are granted
|
|
1223
|
-
- **Grant admin consent** (required!)
|
|
1224
|
-
- Use correct Tenant ID
|
|
1225
|
-
- Check Client Secret hasn't expired
|
|
619
|
+
All admin routes live under `/magic-mail` on the Strapi server (not `/api`), require an authenticated administrator (admin JWT) and the capability shown. The admin UI uses them; you can call them with an admin token for automation.
|
|
1226
620
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
621
|
+
| Area | Routes | Capability |
|
|
622
|
+
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
|
|
623
|
+
| Accounts | `GET /accounts`, `GET /accounts/:accountId` | `read` |
|
|
624
|
+
| | `POST /accounts`, `PUT /accounts/:accountId`, `DELETE /accounts/:accountId` | `manage` |
|
|
625
|
+
| | `POST /accounts/:accountId/test`, `POST /test-strapi-service` | `test-send` |
|
|
626
|
+
| Routing rules | `GET /routing-rules`, `GET /routing-rules/:ruleId` | `read` |
|
|
627
|
+
| | `POST /routing-rules`, `PUT /routing-rules/:ruleId`, `DELETE /routing-rules/:ruleId` | `manage` |
|
|
628
|
+
| OAuth | `GET /oauth/<gmail\|microsoft\|yahoo>/auth`, `POST /oauth/create-account`, `POST /oauth/reconnect/:accountId` | `manage` |
|
|
629
|
+
| | `GET /oauth/<provider>/callback` | public, signed single-use state |
|
|
630
|
+
| Templates | `GET /designer/templates`, `GET /designer/templates/:id`, `GET /designer/templates/:id/versions`, `GET /designer/templates/:id/download`, `POST /designer/render/:templateReferenceId`, `POST /designer/export`, `GET /designer/stats`, `GET /designer/editor-config` | `read` |
|
|
631
|
+
| | `POST /designer/templates`, `PUT`/`DELETE /designer/templates/:id`, `POST /designer/templates/:id/duplicate`, `POST /designer/import`, version restore/delete routes | `manage` |
|
|
632
|
+
| | `POST /designer/templates/:id/test-send` | `test-send` |
|
|
633
|
+
| | `GET`/`PUT /designer/core/:coreEmailType` | `read`/`manage` + users-permissions email-template permission |
|
|
634
|
+
| Analytics | `GET /analytics/stats`, `GET /analytics/emails`, `GET /analytics/emails/:emailId`, `GET /analytics/users/:userId` | `read` |
|
|
635
|
+
| | `DELETE /analytics/emails/:emailId`, `DELETE /analytics/emails` | `manage` |
|
|
636
|
+
| | `GET /analytics/debug` (only with `MAGIC_MAIL_ENABLE_DEBUG_ENDPOINT=true`, never in production) | `read` |
|
|
637
|
+
| Settings | `GET /settings` / `PUT /settings` | `read` / `manage` |
|
|
1230
638
|
|
|
1231
|
-
|
|
639
|
+
Test sends (`/accounts/:accountId/test`, `/test-strapi-service`, `/designer/templates/:id/test-send`) are limited to 10 per minute per admin.
|
|
1232
640
|
|
|
1233
|
-
|
|
641
|
+
### Example: create a routing rule
|
|
1234
642
|
|
|
1235
|
-
|
|
643
|
+
```http
|
|
644
|
+
POST /magic-mail/routing-rules
|
|
645
|
+
Authorization: Bearer <admin JWT>
|
|
646
|
+
Content-Type: application/json
|
|
1236
647
|
|
|
648
|
+
{
|
|
649
|
+
"name": "Marketing campaigns",
|
|
650
|
+
"matchType": "emailType",
|
|
651
|
+
"matchValue": "marketing",
|
|
652
|
+
"accountName": "Marketing SendGrid",
|
|
653
|
+
"fallbackAccountName": "Transactional SMTP",
|
|
654
|
+
"priority": 50,
|
|
655
|
+
"isActive": true
|
|
656
|
+
}
|
|
1237
657
|
```
|
|
1238
|
-
magic-mail/
|
|
1239
|
-
├── admin/ # React Admin UI
|
|
1240
|
-
│ └── src/
|
|
1241
|
-
│ ├── components/
|
|
1242
|
-
│ │ ├── AddAccountModal.jsx # Multi-step account setup
|
|
1243
|
-
│ │ └── PluginIcon.jsx # Sidebar icon
|
|
1244
|
-
│ └── pages/
|
|
1245
|
-
│ ├── App.jsx # Main app with tabs
|
|
1246
|
-
│ ├── HomePage.jsx # Email accounts page
|
|
1247
|
-
│ └── RoutingRules.jsx # Routing rules page
|
|
1248
|
-
├── server/ # Backend
|
|
1249
|
-
│ └── src/
|
|
1250
|
-
│ ├── bootstrap.js # Overrides Strapi email service
|
|
1251
|
-
│ ├── controllers/
|
|
1252
|
-
│ │ ├── accounts.js # CRUD for accounts
|
|
1253
|
-
│ │ ├── oauth.js # OAuth flows
|
|
1254
|
-
│ │ └── routing-rules.js # CRUD for routing rules
|
|
1255
|
-
│ ├── services/
|
|
1256
|
-
│ │ ├── email-router.js # Core routing logic
|
|
1257
|
-
│ │ ├── account-manager.js # Account management
|
|
1258
|
-
│ │ └── oauth.js # OAuth token exchange
|
|
1259
|
-
│ ├── content-types/
|
|
1260
|
-
│ │ ├── email-account/ # Account schema
|
|
1261
|
-
│ │ ├── email-log/ # Email log schema
|
|
1262
|
-
│ │ └── routing-rule/ # Routing rule schema
|
|
1263
|
-
│ └── utils/
|
|
1264
|
-
│ └── encryption.js # AES-256-GCM encryption
|
|
1265
|
-
└── package.json
|
|
1266
|
-
```
|
|
1267
|
-
|
|
1268
|
-
### Data Flow
|
|
1269
658
|
|
|
659
|
+
```json
|
|
660
|
+
{
|
|
661
|
+
"data": {
|
|
662
|
+
"documentId": "…",
|
|
663
|
+
"name": "Marketing campaigns",
|
|
664
|
+
"matchType": "emailType",
|
|
665
|
+
"priority": 50,
|
|
666
|
+
"…": "…"
|
|
667
|
+
},
|
|
668
|
+
"message": "Routing rule created successfully"
|
|
669
|
+
}
|
|
1270
670
|
```
|
|
1271
|
-
Email Request
|
|
1272
|
-
↓
|
|
1273
|
-
Strapi Email Service (intercepted!)
|
|
1274
|
-
↓
|
|
1275
|
-
MagicMail Email Router
|
|
1276
|
-
↓
|
|
1277
|
-
Routing Rules Engine
|
|
1278
|
-
↓
|
|
1279
|
-
Account Selection (priority/rules)
|
|
1280
|
-
↓
|
|
1281
|
-
Rate Limit Check
|
|
1282
|
-
↓
|
|
1283
|
-
Provider-Specific Sender
|
|
1284
|
-
↓
|
|
1285
|
-
Email Log & Stats Update
|
|
1286
|
-
↓
|
|
1287
|
-
Response
|
|
1288
|
-
```
|
|
1289
|
-
|
|
1290
|
-
---
|
|
1291
671
|
|
|
1292
|
-
|
|
672
|
+
`matchType` is one of `emailType`, `recipient`, `subject`, `template`, `custom`. An unknown `accountName` or `fallbackAccountName` returns 400.
|
|
1293
673
|
|
|
1294
|
-
###
|
|
674
|
+
### Example: list accounts
|
|
1295
675
|
|
|
1296
|
-
```
|
|
1297
|
-
|
|
1298
|
-
// Route by custom field:
|
|
1299
|
-
|
|
1300
|
-
// Routing Rule:
|
|
1301
|
-
// Match Type: Custom Field
|
|
1302
|
-
// Match Value: tenant-123
|
|
1303
|
-
// Target Account: Tenant 123 Gmail
|
|
1304
|
-
|
|
1305
|
-
await strapi.plugin('email').service('email').send({
|
|
1306
|
-
to: 'customer@example.com',
|
|
1307
|
-
subject: 'Order Confirmation',
|
|
1308
|
-
html: '<h1>Order #456</h1>',
|
|
1309
|
-
customField: 'tenant-123', // Matches routing rule
|
|
1310
|
-
});
|
|
676
|
+
```http
|
|
677
|
+
GET /magic-mail/accounts
|
|
1311
678
|
```
|
|
1312
679
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
680
|
+
```json
|
|
681
|
+
{
|
|
682
|
+
"data": [
|
|
683
|
+
{
|
|
684
|
+
"documentId": "…",
|
|
685
|
+
"name": "Transactional SMTP",
|
|
686
|
+
"provider": "smtp",
|
|
687
|
+
"fromEmail": "noreply@example.com",
|
|
688
|
+
"isActive": true,
|
|
689
|
+
"isPrimary": true,
|
|
690
|
+
"priority": 9,
|
|
691
|
+
"dailyLimit": 500,
|
|
692
|
+
"hourlyLimit": 0,
|
|
693
|
+
"emailsSentToday": 128,
|
|
694
|
+
"totalEmailsSent": 18342
|
|
695
|
+
}
|
|
696
|
+
],
|
|
697
|
+
"meta": { "count": 1 }
|
|
698
|
+
}
|
|
1321
699
|
```
|
|
1322
700
|
|
|
1323
|
-
|
|
701
|
+
Credentials are never returned. `GET /accounts/:accountId` returns `********` for each stored secret; send that exact value back in `PUT` to keep a secret. Changing provider, `host`, `port`, `user`, `clientId`, `tenantId`, `domain` or `allowInsecure` requires the secrets again (400 otherwise, with the fields in `details.config`).
|
|
1324
702
|
|
|
1325
|
-
|
|
1326
|
-
// Route marketing emails via SendGrid
|
|
1327
|
-
// Routing Rule:
|
|
1328
|
-
// Match Type: Email Type
|
|
1329
|
-
// Match Value: marketing
|
|
1330
|
-
// Target Account: SendGrid Marketing
|
|
703
|
+
### Example: delivery log
|
|
1331
704
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
subject: 'Weekly Newsletter',
|
|
1335
|
-
html: template,
|
|
1336
|
-
type: 'marketing',
|
|
1337
|
-
unsubscribeUrl: 'https://yoursite.com/unsubscribe?id=123',
|
|
1338
|
-
});
|
|
1339
|
-
// ✅ Automatically routes via SendGrid
|
|
1340
|
-
// ✅ Adds List-Unsubscribe header
|
|
1341
|
-
// ✅ GDPR/CAN-SPAM compliant
|
|
705
|
+
```http
|
|
706
|
+
GET /magic-mail/analytics/emails?search=order&deliveryStatus=sent&page=1&pageSize=25
|
|
1342
707
|
```
|
|
1343
708
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
});
|
|
1360
|
-
// ✅ Routes via Priority SMTP
|
|
1361
|
-
// ✅ High priority headers added
|
|
1362
|
-
// ✅ Automatic fallback if the priority account is unavailable
|
|
709
|
+
```json
|
|
710
|
+
{
|
|
711
|
+
"success": true,
|
|
712
|
+
"data": [
|
|
713
|
+
{
|
|
714
|
+
"emailId": "…",
|
|
715
|
+
"recipient": "user01@example.com",
|
|
716
|
+
"subject": "Your order #1001 has shipped",
|
|
717
|
+
"deliveryStatus": "sent",
|
|
718
|
+
"openCount": 0,
|
|
719
|
+
"clickCount": 0
|
|
720
|
+
}
|
|
721
|
+
],
|
|
722
|
+
"pagination": { "page": 1, "pageSize": 25, "pageCount": 1, "total": 1 }
|
|
723
|
+
}
|
|
1363
724
|
```
|
|
1364
725
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
## 🔌 Email Provider Comparison
|
|
1368
|
-
|
|
1369
|
-
| Feature | Gmail OAuth | Microsoft OAuth | Yahoo OAuth | SMTP | SendGrid | Mailgun |
|
|
1370
|
-
|---------|-------------|-----------------|-------------|------|----------|---------|
|
|
1371
|
-
| **Auth Method** | OAuth 2.0 | OAuth 2.0 | OAuth 2.0 | Password | API Key | API Key |
|
|
1372
|
-
| **Free Tier** | 500/day | Varies | Limited | Varies | 100/day | 5000/month |
|
|
1373
|
-
| **Attachments** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
1374
|
-
| **HTML Emails** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
1375
|
-
| **DKIM Auto** | ✅ | ✅ | ✅ | Optional | ✅ | ✅ |
|
|
1376
|
-
| **Custom Headers** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
1377
|
-
| **Priority Headers** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
1378
|
-
| **Security** | Excellent | Excellent | Good | Good | Excellent | Excellent |
|
|
1379
|
-
| **Deliverability** | Excellent | Excellent | Good | Varies | Excellent | Excellent |
|
|
1380
|
-
| **Best For** | Personal/Small | Enterprise | Personal | Custom | Marketing | Bulk/Dev |
|
|
726
|
+
`search` looks in recipient, subject and template name; `pageSize` is capped at 100.
|
|
1381
727
|
|
|
1382
728
|
---
|
|
1383
729
|
|
|
1384
|
-
##
|
|
1385
|
-
|
|
1386
|
-
### Environment Variables
|
|
730
|
+
## Permissions
|
|
1387
731
|
|
|
1388
|
-
|
|
1389
|
-
# Base URL (required for OAuth callbacks in production)
|
|
1390
|
-
URL=https://yourdomain.com
|
|
1391
|
-
|
|
1392
|
-
# Database (Strapi default)
|
|
1393
|
-
DATABASE_CLIENT=postgres
|
|
1394
|
-
DATABASE_HOST=127.0.0.1
|
|
1395
|
-
DATABASE_PORT=5432
|
|
1396
|
-
DATABASE_NAME=strapi
|
|
1397
|
-
DATABASE_USERNAME=strapi
|
|
1398
|
-
DATABASE_PASSWORD=password
|
|
1399
|
-
|
|
1400
|
-
# Required in production. Keep this stable across deploys and backups.
|
|
1401
|
-
# Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
1402
|
-
MAGIC_MAIL_ENCRYPTION_KEY=your-generated-secret-with-at-least-32-bytes
|
|
1403
|
-
|
|
1404
|
-
# Optional dedicated OAuth-state signing secret. APP_KEYS is used otherwise.
|
|
1405
|
-
MAGIC_MAIL_OAUTH_STATE_SECRET=your-generated-secret-with-at-least-32-bytes
|
|
1406
|
-
```
|
|
732
|
+
MagicMail registers four admin actions under **Settings → Administration Panel → Roles → Plugins → MagicMail**:
|
|
1407
733
|
|
|
1408
|
-
|
|
734
|
+
| Action | Grants |
|
|
735
|
+
| ------------------------------ | --------------------------------------------------------------------------------------------------------------- |
|
|
736
|
+
| `plugin::magic-mail.access` | The MagicMail menu entry and page. No API access by itself |
|
|
737
|
+
| `plugin::magic-mail.read` | Read accounts, routing rules, templates, analytics and settings; render and export templates; the settings link |
|
|
738
|
+
| `plugin::magic-mail.manage` | Create, update and delete accounts, rules, templates, logs and settings; OAuth setup |
|
|
739
|
+
| `plugin::magic-mail.test-send` | Send test emails (account test, Strapi-service test, template test); 10 per minute per admin |
|
|
1409
740
|
|
|
1410
|
-
|
|
741
|
+
Typical roles:
|
|
1411
742
|
|
|
1412
|
-
**
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
```
|
|
743
|
+
- **Read-only:** `access` + `read`
|
|
744
|
+
- **Editor:** `access` + `read` + `manage`
|
|
745
|
+
- **Tester:** `access` + `read` + `test-send`
|
|
1416
746
|
|
|
1417
|
-
|
|
1418
|
-
```
|
|
1419
|
-
https://yourdomain.com/magic-mail/oauth/microsoft/callback
|
|
1420
|
-
```
|
|
747
|
+
Editing the Users & Permissions templates also needs the users-permissions email-template permissions; uploading images in the visual editor needs _Media Library → Create (upload)_. Details: [docs/permission-migration.md](./docs/permission-migration.md).
|
|
1421
748
|
|
|
1422
|
-
|
|
1423
|
-
```
|
|
1424
|
-
https://yourdomain.com/magic-mail/oauth/yahoo/callback
|
|
1425
|
-
```
|
|
749
|
+
---
|
|
1426
750
|
|
|
1427
|
-
|
|
751
|
+
## Configuration reference
|
|
1428
752
|
|
|
1429
|
-
|
|
753
|
+
### Environment variables
|
|
1430
754
|
|
|
1431
|
-
|
|
|
1432
|
-
|
|
1433
|
-
|
|
|
1434
|
-
|
|
|
1435
|
-
|
|
|
1436
|
-
|
|
|
1437
|
-
|
|
|
755
|
+
| Variable | Required | Purpose |
|
|
756
|
+
| ------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
757
|
+
| `MAGIC_MAIL_ENCRYPTION_KEY` | yes, unless `NODE_ENV` is unset, `development` or `test` | Credential encryption key (32+ bytes). Keep it stable and backed up |
|
|
758
|
+
| `MAGIC_MAIL_ENCRYPTION_KEY_PREVIOUS` | no | Previous key during a rotation (decrypt only; saving an account re-encrypts it) |
|
|
759
|
+
| `MAGIC_MAIL_OAUTH_STATE_SECRET` | no | OAuth state signing secret (default: derived from the encryption key) |
|
|
760
|
+
| `MAGIC_MAIL_TRACKING_SECRET` | no | Tracking hash secret (default: derived from the encryption key) |
|
|
761
|
+
| `MAGIC_MAIL_TRACKING_SECRET_PREVIOUS` | no | Comma-separated old tracking secrets that stay valid |
|
|
762
|
+
| `URL` | no | Legacy override of the OAuth callback base URL (default: `server.url`) |
|
|
763
|
+
| `MAGIC_MAIL_ENABLE_DEBUG_ENDPOINT` | no | `true` enables `GET /magic-mail/analytics/debug` outside production |
|
|
1438
764
|
|
|
1439
|
-
|
|
765
|
+
Without `MAGIC_MAIL_ENCRYPTION_KEY`, a development key derived from `ADMIN_JWT_SECRET`/`APP_KEYS` is used only when `NODE_ENV` is unset, `development` or `test`; rotating those secrets then makes stored credentials unreadable. Any other environment (for example `staging`) refuses to start without the key.
|
|
1440
766
|
|
|
1441
|
-
|
|
767
|
+
Also set Strapi's `server.url` to your public URL (OAuth redirects, tracking links) and `server.proxy: true` behind a reverse proxy.
|
|
1442
768
|
|
|
1443
|
-
###
|
|
769
|
+
### Plugin config
|
|
1444
770
|
|
|
1445
|
-
|
|
771
|
+
All keys are optional and validated at startup (an invalid value stops Strapi with a clear message):
|
|
1446
772
|
|
|
1447
773
|
```javascript
|
|
1448
|
-
//
|
|
1449
|
-
{
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
//
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
//
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
accountName: "SendGrid Marketing",
|
|
1475
|
-
priority: 8,
|
|
1476
|
-
isActive: true
|
|
1477
|
-
}
|
|
774
|
+
// config/plugins.js
|
|
775
|
+
module.exports = () => ({
|
|
776
|
+
'magic-mail': {
|
|
777
|
+
enabled: true,
|
|
778
|
+
config: {
|
|
779
|
+
debug: false, // info/debug logs; warnings and errors are always logged
|
|
780
|
+
contentApi: {
|
|
781
|
+
allowUserRoles: false, // true = users-permissions roles may use the send routes
|
|
782
|
+
},
|
|
783
|
+
quotaTimezone: 'UTC', // IANA zone for hourly/daily quota windows
|
|
784
|
+
tracking: {
|
|
785
|
+
eventRetentionDays: 90, // raw open/click events; 0 = keep forever
|
|
786
|
+
linkTtlDays: 180, // tracked links redirect this long; 0 = forever
|
|
787
|
+
logRetentionDays: 0, // email logs (with events and links); 0 = keep forever
|
|
788
|
+
maxLinksPerEmail: 100, // further links stay untracked
|
|
789
|
+
allowedLinkHosts: [], // e.g. ['example.com'] (subdomains included)
|
|
790
|
+
anonymizeIp: true, // store /24 (IPv4) or /48 (IPv6) only
|
|
791
|
+
},
|
|
792
|
+
designer: {
|
|
793
|
+
projectId: null, // optional Unlayer project id
|
|
794
|
+
scriptUrl: null, // optional https URL of the Unlayer loader
|
|
795
|
+
version: null, // optional exact Unlayer build
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
});
|
|
1478
800
|
```
|
|
1479
801
|
|
|
1480
|
-
|
|
802
|
+
`allowedLinkHosts` empty means: every host may be click-tracked for admin, service and native sends, and **no** host for Content-API sends. When the list is set, it applies to all sends.
|
|
1481
803
|
|
|
1482
|
-
|
|
804
|
+
### Admin settings
|
|
1483
805
|
|
|
1484
|
-
**
|
|
1485
|
-
```bash
|
|
1486
|
-
openssl genrsa -out dkim_private.pem 1024
|
|
1487
|
-
openssl rsa -in dkim_private.pem -pubout -out dkim_public.pem
|
|
1488
|
-
```
|
|
806
|
+
**Settings → MagicMail → Plugin Settings**, stored in the database (`GET`/`PUT /magic-mail/settings`):
|
|
1489
807
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
808
|
+
| Setting | Default | Effect |
|
|
809
|
+
| ----------------------------- | ------- | --------------------------------------------------------------------------- |
|
|
810
|
+
| Link tracking / Open tracking | on | Rewrite links / add the open pixel |
|
|
811
|
+
| Tracking base URL | empty | Public base for tracking URLs (else `server.url`) |
|
|
812
|
+
| Default from name | empty | Display name when neither the account nor the caller sets one |
|
|
813
|
+
| Default from email | empty | Reference only; emails always go out from the selected account's address |
|
|
814
|
+
| Unsubscribe header | on | Send `List-Unsubscribe` for every email type and use the plugin-wide URL |
|
|
815
|
+
| Unsubscribe URL | empty | Plugin-wide unsubscribe URL; `{{email}}` is replaced per recipient |
|
|
816
|
+
| `trackingFallbackUrl` (API) | empty | Where expired or unknown tracking links redirect; empty shows a static page |
|
|
1494
817
|
|
|
1495
|
-
|
|
1496
|
-
```javascript
|
|
1497
|
-
// In account config (edit account → credentials):
|
|
1498
|
-
{
|
|
1499
|
-
host: "smtp.yourdomain.com",
|
|
1500
|
-
port: 587,
|
|
1501
|
-
user: "...",
|
|
1502
|
-
pass: "...",
|
|
1503
|
-
dkim: {
|
|
1504
|
-
domainName: "yourdomain.com",
|
|
1505
|
-
keySelector: "default",
|
|
1506
|
-
privateKey: "-----BEGIN PRIVATE KEY-----\n..."
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
```
|
|
818
|
+
### Account fields
|
|
1510
819
|
|
|
1511
|
-
|
|
820
|
+
Name (unique), description, provider, from email and name, reply-to, encrypted credentials (SMTP password, API key, OAuth tokens, optional DKIM key), daily and hourly limit (0 = unlimited), priority (1 to 10, higher preferred), active and primary flags.
|
|
1512
821
|
|
|
1513
|
-
|
|
822
|
+
### Service API
|
|
1514
823
|
|
|
1515
824
|
```javascript
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
// Account B: Priority 9, Daily Limit: 500
|
|
1519
|
-
// Account C: Priority 8, Daily Limit: 500
|
|
1520
|
-
// → Total capacity: 1500 emails/day
|
|
1521
|
-
|
|
1522
|
-
// MagicMail automatically:
|
|
1523
|
-
// 1. Uses Account A until limit hit
|
|
1524
|
-
// 2. Switches to Account B
|
|
1525
|
-
// 3. Switches to Account C
|
|
1526
|
-
// 4. All transparent to your code!
|
|
1527
|
-
```
|
|
825
|
+
const router = strapi.plugin('magic-mail').service('email-router');
|
|
826
|
+
await router.send({ to, subject, html });
|
|
1528
827
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
## 🤝 Contributing
|
|
1532
|
-
|
|
1533
|
-
We welcome contributions!
|
|
1534
|
-
|
|
1535
|
-
**Found a bug?** [Open an issue](https://github.com/Schero94/MagicMail/issues)
|
|
828
|
+
const accounts = await strapi.plugin('magic-mail').service('account-manager').getAllAccounts();
|
|
1536
829
|
|
|
1537
|
-
|
|
830
|
+
// Test one account (also works for inactive accounts)
|
|
831
|
+
await strapi
|
|
832
|
+
.plugin('magic-mail')
|
|
833
|
+
.service('account-manager')
|
|
834
|
+
.testAccount(accountDocumentId, 'you@example.com', { priority: 'high', type: 'marketing' });
|
|
1538
835
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
4. Submit a pull request
|
|
836
|
+
const designer = strapi.plugin('magic-mail').service('email-designer');
|
|
837
|
+
const rendered = await designer.renderTemplate(100, { user: { firstName: 'Ada' } });
|
|
838
|
+
const stats = await designer.getStats(); // { total, active, inactive, byCategory, ... }
|
|
839
|
+
```
|
|
1544
840
|
|
|
1545
841
|
---
|
|
1546
842
|
|
|
1547
|
-
##
|
|
843
|
+
## Troubleshooting
|
|
1548
844
|
|
|
1549
|
-
**
|
|
845
|
+
**Email not received**
|
|
1550
846
|
|
|
1551
|
-
|
|
847
|
+
1. Look for `[magic-mail]` warnings and errors in the Strapi log (always logged; set `debug: true` for details).
|
|
848
|
+
2. Check the account: active, the **Test** action succeeds, today's usage is below the limit.
|
|
849
|
+
3. Check **Analytics**: the log shows status, the account used and, for failures, the error.
|
|
850
|
+
4. Check the spam folder; new senders often land there.
|
|
1552
851
|
|
|
1553
|
-
|
|
852
|
+
**"Could not connect to the mail server"** on an SMTP test: check host, port and TLS (`587` STARTTLS, `465` with _Use SSL/TLS encryption_). Self-signed certificates are rejected unless `allowInsecure` is set on a non-production or loopback host.
|
|
1554
853
|
|
|
1555
|
-
|
|
854
|
+
**OAuth popup fails or reports a redirect mismatch**
|
|
1556
855
|
|
|
1557
|
-
|
|
1558
|
-
|
|
856
|
+
- Register exactly the redirect URI the wizard shows.
|
|
857
|
+
- Production: `server.url` must be public `https`.
|
|
858
|
+
- Admin on its own host: set `admin.url`.
|
|
859
|
+
- Microsoft: all five delegated permissions with admin consent, correct tenant ID, client secret not expired.
|
|
860
|
+
- Several Strapi instances: use sticky admin sessions.
|
|
1559
861
|
|
|
1560
|
-
|
|
862
|
+
**Edit wizard says "Stored credentials cannot be decrypted"**: the encryption key changed. Set the old key in `MAGIC_MAIL_ENCRYPTION_KEY_PREVIOUS`, or enter the secrets again.
|
|
1561
863
|
|
|
1562
|
-
|
|
864
|
+
**Content API returns 403**: use an API token with the `send`/`sendMessage` action; users-permissions roles are rejected unless `contentApi.allowUserRoles` is true.
|
|
1563
865
|
|
|
1564
|
-
|
|
866
|
+
**Content API returns 400**: the response `details` name the field. Common causes: unknown fields (for example `phoneNumber` or `channel` from 3.x), `path` attachments, a `from` that is not the account's address.
|
|
1565
867
|
|
|
1566
|
-
**
|
|
1567
|
-
- [Strapi v5](https://strapi.io) - Headless CMS
|
|
1568
|
-
- [Nodemailer](https://nodemailer.com) - SMTP client
|
|
1569
|
-
- [Heroicons](https://heroicons.com) - Beautiful icons
|
|
1570
|
-
- [styled-components](https://styled-components.com) - CSS-in-JS
|
|
868
|
+
**Links are not tracked**: set _Tracking base URL_ or `server.url` to a public URL; for Content-API sends add the link hosts to `tracking.allowedLinkHosts`. Authentication links are never tracked.
|
|
1571
869
|
|
|
1572
|
-
**
|
|
1573
|
-
- Professional email requirements
|
|
1574
|
-
- Multi-tenant application needs
|
|
1575
|
-
- Developer experience first
|
|
870
|
+
**Visual designer does not load**: add `https://editor.unlayer.com` to `script-src` and `frame-src` (see [Content Security Policy](#content-security-policy-for-the-visual-editor)). The **Plain Text** tab works without it.
|
|
1576
871
|
|
|
1577
|
-
|
|
872
|
+
**DMARC failures with a custom domain**: publish SPF (for example `v=spf1 include:_spf.google.com ~all`), add DKIM (provider or SMTP DKIM config) and a DMARC record. MagicMail always sends from the account's address, which keeps alignment.
|
|
1578
873
|
|
|
1579
|
-
|
|
874
|
+
**"Too many requests"**: the send routes allow 60 requests per minute per token, test sends 10 per minute per admin. Also check provider limits (for example Gmail 500/day for free accounts, 2,000/day for Workspace).
|
|
1580
875
|
|
|
1581
|
-
|
|
1582
|
-
- 📖 [Documentation](https://github.com/Schero94/Magic-Mail#readme)
|
|
1583
|
-
- 🐛 [Report Issues](https://github.com/Schero94/Magic-Mail/issues)
|
|
1584
|
-
- 💡 [Feature Requests](https://github.com/Schero94/Magic-Mail/discussions)
|
|
876
|
+
---
|
|
1585
877
|
|
|
878
|
+
## Upgrading to 4.0.0
|
|
1586
879
|
|
|
1587
|
-
|
|
880
|
+
4.0.0 is a major release with security and correctness changes. **Back up the database first**: credentials are re-encrypted in a format 3.x cannot read, and two legacy routing-rule columns are dropped. The full list with migration notes is in [CHANGELOG.md](./CHANGELOG.md).
|
|
1588
881
|
|
|
1589
|
-
|
|
882
|
+
Breaking changes and what to do:
|
|
1590
883
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
-
|
|
1594
|
-
|
|
884
|
+
1. **Node.js 22.12+** and **`@strapi/plugin-users-permissions`** are required. Peer ranges are `^5.57.0`.
|
|
885
|
+
2. **Encryption key everywhere but development/test.** Set `MAGIC_MAIL_ENCRYPTION_KEY` for staging too. The constant fallback key is gone.
|
|
886
|
+
3. **Email only.** The non-email messaging routes and service methods are removed and `/send` no longer accepts `phoneNumber` (both already in 3.1.0); `/send-message` no longer accepts `channel` (400 for unknown fields).
|
|
887
|
+
4. **Content API needs API tokens.** Switch callers to an API token, or set `contentApi.allowUserRoles: true`. `userId` must reference an existing user.
|
|
888
|
+
5. **Admin capabilities.** Grant `read`, `manage` and `test-send` in addition to `access` ([permission guide](./docs/permission-migration.md)).
|
|
889
|
+
6. **Attachments:** replace `path`/`href` with `content`.
|
|
890
|
+
7. **`from` and headers:** `from` must be the sending account's address; custom headers must be `X-*` or one of the allowed names.
|
|
891
|
+
8. **Explicit `accountName` is never replaced**; a template's category now sets the email type when `type` is omitted.
|
|
892
|
+
9. **Tracking needs a public URL.** Set _Tracking base URL_ or `server.url`; Content-API emails are click-tracked only for `tracking.allowedLinkHosts`; links expire after 180 days, events after 90 days, IPs are truncated.
|
|
893
|
+
10. **Secrets:** the placeholder is `********`; changing host, user, provider or OAuth client requires the secret again.
|
|
894
|
+
11. **OAuth:** in production the redirect URI comes from `server.url` (public `https`). Authorizations in progress during the upgrade must be restarted.
|
|
895
|
+
12. **Users & Permissions templates** need the users-permissions email-template permissions and only accept the listed placeholders.
|
|
896
|
+
13. **Visual designer:** add the CSP directives and the Media Library upload permission.
|
|
897
|
+
14. **Removed:** provider `ses` (never had an adapter), two legacy messaging columns of the routing-rule table (names in the changelog), the admin keep-alive polling.
|
|
1595
898
|
|
|
1596
899
|
---
|
|
1597
900
|
|
|
1598
|
-
##
|
|
1599
|
-
|
|
1600
|
-
MagicMail's direct dependencies are audited on every release, but two
|
|
1601
|
-
transitive paths are governed by upstream packages we do not control.
|
|
1602
|
-
If your own `npm audit` flags either of them, add the matching entry
|
|
1603
|
-
to your **Strapi project's** `package.json` (not this plugin's — npm
|
|
1604
|
-
`overrides` only take effect on the root project):
|
|
901
|
+
## Development
|
|
1605
902
|
|
|
1606
|
-
```
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
903
|
+
```bash
|
|
904
|
+
npm ci --ignore-scripts
|
|
905
|
+
npm run lint
|
|
906
|
+
npm run format:check
|
|
907
|
+
npm test
|
|
908
|
+
npm run build
|
|
909
|
+
npm run verify
|
|
910
|
+
npm run verify:runtime
|
|
1616
911
|
```
|
|
1617
912
|
|
|
1618
|
-
|
|
913
|
+
`npm run watch` (or `npm run watch:link` with yalc) rebuilds the admin and the server bundle on change. Pull requests run the same checks on Node 22 and 24.
|
|
1619
914
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
npm install
|
|
1623
|
-
npm audit
|
|
1624
|
-
```
|
|
915
|
+
- **Bug?** [Open an issue](https://github.com/Schero94/Magic-Mail/issues)
|
|
916
|
+
- **Idea?** [Start a discussion](https://github.com/Schero94/Magic-Mail/discussions)
|
|
1625
917
|
|
|
1626
|
-
|
|
1627
|
-
place. Remaining `@strapi/design-system → lodash` advisories are
|
|
1628
|
-
Strapi-core issues and will clear up automatically when you upgrade
|
|
1629
|
-
`@strapi/strapi` to the latest patch release.
|
|
918
|
+
### Dependency audits
|
|
1630
919
|
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
`npm
|
|
1634
|
-
already guards every Baileys import with a try/catch and will log
|
|
1635
|
-
"WhatsApp features disabled" rather than crash.
|
|
920
|
+
Run `npm audit` from your Strapi project root to check the complete installed tree; npm applies overrides from the root project only.
|
|
921
|
+
|
|
922
|
+
Known host-side advisory: `npm audit --omit=dev` in this repository reports GHSA-mrhx-c7xc-m3w5 (zod < 4.6.6) inside `@strapi/utils`, which the Strapi host provides. MagicMail validates every request with its own patched zod (`^4.6.7`) and bounds all collections, so the advisory is not reachable through MagicMail inputs. It disappears once your Strapi version ships a fixed zod.
|
|
1636
923
|
|
|
1637
924
|
---
|
|
1638
925
|
|
|
1639
|
-
|
|
926
|
+
## License
|
|
927
|
+
|
|
928
|
+
MIT License, Copyright © 2025 Schero D. See [LICENSE](./LICENSE).
|
|
1640
929
|
|
|
1641
|
-
|
|
930
|
+
Built with [Strapi](https://strapi.io), [Nodemailer](https://nodemailer.com), [Unlayer](https://unlayer.com) / [react-email-editor](https://github.com/unlayer/react-email-editor), [Heroicons](https://heroicons.com) and [styled-components](https://styled-components.com).
|