strapi-plugin-magic-mail 2.0.3 → 2.0.4

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 📧 MagicMail - Email Business Suite for Strapi v5
2
2
 
3
- > **Enterprise-grade multi-account email management with smart routing, OAuth 2.0 support, and complete security compliance**
3
+ > **Professional-grade multi-account email management with smart routing, OAuth 2.0 support, and complete security compliance**
4
4
 
5
5
  [![NPM Version](https://img.shields.io/npm/v/strapi-plugin-magic-mail.svg)](https://www.npmjs.com/package/strapi-plugin-magic-mail)
6
6
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
@@ -10,7 +10,7 @@
10
10
 
11
11
  ## 🌟 Why MagicMail?
12
12
 
13
- **Stop fighting with .env files and email configuration!** MagicMail brings enterprise email management to Strapi v5 with:
13
+ **Stop fighting with .env files and email configuration!** MagicMail brings professional email management to Strapi v5 with:
14
14
 
15
15
  - ✅ **6 Email Providers** - Gmail, Microsoft 365, Yahoo, SMTP, SendGrid, Mailgun
16
16
  - ✅ **OAuth 2.0 Authentication** - No passwords needed for Gmail, Microsoft, Yahoo
@@ -20,7 +20,7 @@
20
20
  - ✅ **Zero Configuration** - No .env files, everything in the database
21
21
  - ✅ **Email Designer Compatible** - Works seamlessly with strapi-plugin-email-designer-5
22
22
  - ✅ **GDPR/CAN-SPAM Compliant** - Built-in List-Unsubscribe headers
23
- - ✅ **Enterprise Security** - TLS 1.2+, DKIM, SPF, DMARC validation
23
+ - ✅ **Professional Security** - TLS 1.2+, DKIM, SPF, DMARC validation
24
24
 
25
25
  ---
26
26
 
@@ -228,17 +228,15 @@ await strapi.plugin('magic-mail').service('email-router').send({
228
228
 
229
229
  ### ✨ Features
230
230
 
231
- | Feature | FREE | PREMIUM | ADVANCED | ENTERPRISE |
232
- |---------|------|---------|----------|------------|
233
- | **Visual Designer** | ✅ Basic builder | ✅ + template library | ✅ Pro components | ✅ Enterprise suite |
234
- | **Templates Included** | 25 | 100 | 500 | Unlimited |
235
- | **Drag & Drop + Mustache** | ✅ | ✅ | ✅ | ✅ |
236
- | **Import / Export** | ✅ | ✅ | ✅ | ✅ |
237
- | **Template Versioning** | ❌ | ✅ | ✅ | ✅ |
238
- | **Analytics** | ❌ | Basic insights | Advanced dashboard | Enterprise analytics |
239
- | **Custom Blocks** | ❌ | ❌ | ❌ | ✅ |
240
- | **Team Library** | ❌ | ❌ | ❌ | ✅ |
241
- | **A/B Testing** | ❌ | ❌ | ❌ | ✅ |
231
+ | Feature | FREE | PREMIUM | ADVANCED |
232
+ |---------|------|---------|----------|
233
+ | **Visual Designer** | ✅ Basic builder | ✅ + template library | ✅ Pro components |
234
+ | **Templates Included** | 25 | 100 | 500 |
235
+ | **Drag & Drop + Mustache** | ✅ | ✅ | ✅ |
236
+ | **Import / Export** | ✅ | ✅ | ✅ |
237
+ | **Template Versioning** | ❌ | ✅ | ✅ |
238
+ | **Analytics** | ❌ | Basic insights | Advanced dashboard |
239
+ | **A/B Testing** | ❌ | ❌ | ✅ |
242
240
 
243
241
  ### 📧 Creating Email Templates
244
242
 
@@ -1399,7 +1397,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
1399
1397
  - [styled-components](https://styled-components.com) - CSS-in-JS
1400
1398
 
1401
1399
  **Inspired by:**
1402
- - Enterprise email requirements
1400
+ - Professional email requirements
1403
1401
  - Multi-tenant application needs
1404
1402
  - Developer experience first
1405
1403
 
@@ -1424,6 +1422,6 @@ If MagicMail helps your project, please:
1424
1422
 
1425
1423
  ---
1426
1424
 
1427
- **Made with ❤️ for the Strapi Community**
1425
+ **Made for the Strapi Community**
1428
1426
 
1429
- **MagicMail - Because email management should be magical, not painful.**
1427
+ **MagicMail - Because email management should be magical, not painful.**
@@ -5,6 +5,15 @@ import PluginIcon from './components/PluginIcon';
5
5
 
6
6
  const name = pluginPkg.strapi.name;
7
7
 
8
+ // Prefix translation keys with pluginId (required for Strapi)
9
+ const prefixPluginTranslations = (data, pluginId) => {
10
+ const prefixed = {};
11
+ Object.keys(data).forEach((key) => {
12
+ prefixed[`${pluginId}.${key}`] = data[key];
13
+ });
14
+ return prefixed;
15
+ };
16
+
8
17
  export default {
9
18
  register(app) {
10
19
  app.addMenuLink({
@@ -55,32 +64,24 @@ export default {
55
64
  },
56
65
 
57
66
  async registerTrads({ locales }) {
58
- const importedTrads = {
59
- en: () => import('./translations/en.json'),
60
- de: () => import('./translations/de.json'),
61
- es: () => import('./translations/es.json'),
62
- fr: () => import('./translations/fr.json'),
63
- pt: () => import('./translations/pt.json'),
64
- };
65
-
66
- const translatedLanguages = Object.keys(importedTrads).filter((lang) =>
67
- locales.includes(lang)
68
- );
69
-
70
- const translations = await Promise.all(
71
- translatedLanguages.map((language) =>
72
- importedTrads[language]()
73
- .then(({ default: data }) => ({
74
- data: data,
75
- locale: language,
76
- }))
77
- .catch(() => ({
78
- data: {},
79
- locale: language,
80
- }))
81
- )
67
+ const importedTrads = await Promise.all(
68
+ locales.map((locale) => {
69
+ return import(`./translations/${locale}.json`)
70
+ .then(({ default: data }) => {
71
+ return {
72
+ data: prefixPluginTranslations(data, pluginId),
73
+ locale,
74
+ };
75
+ })
76
+ .catch(() => {
77
+ return {
78
+ data: {},
79
+ locale,
80
+ };
81
+ });
82
+ })
82
83
  );
83
84
 
84
- return Promise.resolve(translations);
85
+ return Promise.resolve(importedTrads);
85
86
  },
86
87
  };
@@ -130,7 +130,7 @@ const HeaderContent = styled(Flex)`
130
130
  `;
131
131
 
132
132
  const Title = styled(Typography)`
133
- color: ${theme.colors.neutral[0]};
133
+ color: white;
134
134
  font-size: 2rem;
135
135
  font-weight: 700;
136
136
  letter-spacing: -0.025em;
@@ -185,7 +185,7 @@ const StatsGrid = styled.div`
185
185
  `;
186
186
 
187
187
  const StatCard = styled(Box)`
188
- background: ${theme.colors.neutral[0]};
188
+ background: ${props => props.theme.colors.neutral0};
189
189
  border-radius: ${theme.borderRadius.lg};
190
190
  padding: 28px ${theme.spacing.lg};
191
191
  position: relative;
@@ -194,7 +194,7 @@ const StatCard = styled(Box)`
194
194
  ${css`animation: ${fadeIn} ${theme.transitions.slow} backwards;`}
195
195
  animation-delay: ${props => props.$delay || '0s'};
196
196
  box-shadow: ${theme.shadows.sm};
197
- border: 1px solid ${theme.colors.neutral[200]};
197
+ border: 1px solid ${props => props.theme.colors.neutral200};
198
198
  min-width: 200px;
199
199
  flex: 1;
200
200
  text-align: center;
@@ -260,7 +260,7 @@ const StatIcon = styled(Box)`
260
260
  const StatValue = styled(Typography)`
261
261
  font-size: 2.25rem;
262
262
  font-weight: 700;
263
- color: ${theme.colors.neutral[800]};
263
+ color: ${props => props.theme.colors.neutral800};
264
264
  transition: all ${theme.transitions.normal};
265
265
  line-height: 1;
266
266
  margin-bottom: ${theme.spacing.xs};
@@ -272,7 +272,7 @@ const StatValue = styled(Typography)`
272
272
 
273
273
  const StatLabel = styled(Typography)`
274
274
  font-size: 0.875rem;
275
- color: ${theme.colors.neutral[600]};
275
+ color: ${props => props.theme.colors.neutral600};
276
276
  font-weight: 500;
277
277
  text-transform: uppercase;
278
278
  letter-spacing: 0.05em;
@@ -283,22 +283,22 @@ const StatLabel = styled(Typography)`
283
283
  `;
284
284
 
285
285
  const FilterBar = styled(Box)`
286
- background: ${theme.colors.neutral[0]};
286
+ background: ${props => props.theme.colors.neutral0};
287
287
  border-radius: ${theme.borderRadius.lg};
288
288
  padding: ${theme.spacing.lg} ${theme.spacing.xl};
289
289
  margin-bottom: ${theme.spacing.lg};
290
290
  box-shadow: ${theme.shadows.sm};
291
- border: 1px solid ${theme.colors.neutral[200]};
291
+ border: 1px solid ${props => props.theme.colors.neutral200};
292
292
  `;
293
293
 
294
294
  const StyledTable = styled(Table)`
295
295
  thead {
296
- background: ${theme.colors.neutral[50]};
297
- border-bottom: 2px solid ${theme.colors.neutral[200]};
296
+ background: ${props => props.theme.colors.neutral100};
297
+ border-bottom: 2px solid ${props => props.theme.colors.neutral200};
298
298
 
299
299
  th {
300
300
  font-weight: 600;
301
- color: ${theme.colors.neutral[700]};
301
+ color: ${props => props.theme.colors.neutral800};
302
302
  font-size: 0.875rem;
303
303
  text-transform: uppercase;
304
304
  letter-spacing: 0.025em;
@@ -308,37 +308,37 @@ const StyledTable = styled(Table)`
308
308
 
309
309
  tbody tr {
310
310
  transition: all ${theme.transitions.fast};
311
- border-bottom: 1px solid ${theme.colors.neutral[100]};
311
+ border-bottom: 1px solid ${props => props.theme.colors.neutral150};
312
312
 
313
313
  &:last-child {
314
314
  border-bottom: none;
315
315
  }
316
316
 
317
317
  &:hover {
318
- background: ${theme.colors.neutral[50]};
318
+ background: ${props => props.theme.colors.primary100};
319
319
  }
320
320
 
321
321
  td {
322
322
  padding: ${theme.spacing.lg} ${theme.spacing.lg};
323
- color: ${theme.colors.neutral[700]};
323
+ color: ${props => props.theme.colors.neutral800};
324
324
  vertical-align: middle;
325
325
  }
326
326
  }
327
327
  `;
328
328
 
329
329
  const TableContainer = styled(Box)`
330
- background: ${theme.colors.neutral[0]};
330
+ background: ${props => props.theme.colors.neutral0};
331
331
  border-radius: ${theme.borderRadius.lg};
332
332
  box-shadow: ${theme.shadows.md};
333
- border: 1px solid ${theme.colors.neutral[200]};
333
+ border: 1px solid ${props => props.theme.colors.neutral200};
334
334
  overflow: hidden;
335
335
  margin-bottom: ${theme.spacing.xl};
336
336
  `;
337
337
 
338
338
  const EmptyState = styled(Box)`
339
- background: ${theme.colors.neutral[0]};
339
+ background: ${props => props.theme.colors.neutral0};
340
340
  border-radius: ${theme.borderRadius.xl};
341
- border: 2px dashed ${theme.colors.neutral[200]};
341
+ border: 2px dashed ${props => props.theme.colors.neutral300};
342
342
  padding: 80px 32px;
343
343
  text-align: center;
344
344
  position: relative;
@@ -684,7 +684,7 @@ const Analytics = () => {
684
684
  </Flex>
685
685
  ) : (
686
686
  <Flex alignItems="center" gap={1}>
687
- <XCircleIcon style={{ width: 16, height: 16, color: theme.colors.neutral[400] }} />
687
+ <XCircleIcon style={{ width: 16, height: 16, color: '#9CA3AF' }} />
688
688
  <Typography variant="pi" textColor="neutral600">
689
689
  No
690
690
  </Typography>
@@ -701,7 +701,7 @@ const Analytics = () => {
701
701
  </Flex>
702
702
  ) : (
703
703
  <Flex alignItems="center" gap={1}>
704
- <XCircleIcon style={{ width: 16, height: 16, color: theme.colors.neutral[400] }} />
704
+ <XCircleIcon style={{ width: 16, height: 16, color: '#9CA3AF' }} />
705
705
  <Typography variant="pi" textColor="neutral600">
706
706
  No
707
707
  </Typography>
@@ -122,12 +122,12 @@ const Container = styled.div`
122
122
  min-height: 100vh;
123
123
  display: flex;
124
124
  flex-direction: column;
125
- background: #f6f6f9;
125
+ background: ${props => props.theme.colors.neutral100};
126
126
  `;
127
127
 
128
128
  const Header = styled.div`
129
129
  padding: 24px;
130
- background: white;
130
+ background: ${props => props.theme.colors.neutral0};
131
131
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
132
132
  `;
133
133
 
@@ -207,8 +207,8 @@ const TabsWrapper = styled.div`
207
207
 
208
208
  const TabListWrapper = styled.div`
209
209
  padding: 0 24px;
210
- background: white;
211
- border-bottom: 1px solid #eaeaef;
210
+ background: ${props => props.theme.colors.neutral0};
211
+ border-bottom: 1px solid ${props => props.theme.colors.neutral200};
212
212
  `;
213
213
 
214
214
  const StyledTabsRoot = styled(Tabs.Root)`
@@ -225,7 +225,7 @@ const StyledTabsContent = styled(Tabs.Content)`
225
225
 
226
226
  const TabContentWrapper = styled.div`
227
227
  height: calc(100vh - 240px);
228
- background: white;
228
+ background: ${props => props.theme.colors.neutral0};
229
229
  position: relative;
230
230
  `;
231
231
 
@@ -304,10 +304,10 @@ const ImportExportButton = styled.span`
304
304
  gap: 6px;
305
305
  padding: 8px 16px;
306
306
  height: 36px;
307
- background: white;
308
- border: 1px solid #dcdce4;
307
+ background: ${props => props.theme.colors.neutral0};
308
+ border: 1px solid ${props => props.theme.colors.neutral200};
309
309
  border-radius: 4px;
310
- color: #32324d;
310
+ color: ${props => props.theme.colors.neutral800};
311
311
  font-weight: 500;
312
312
  font-size: 13px;
313
313
  cursor: pointer;
@@ -315,9 +315,9 @@ const ImportExportButton = styled.span`
315
315
  white-space: nowrap;
316
316
 
317
317
  &:hover {
318
- background: #f6f6f9;
319
- border-color: #0EA5E9;
320
- color: #0EA5E9;
318
+ background: ${props => props.theme.colors.neutral100};
319
+ border-color: ${props => props.theme.colors.primary600};
320
+ color: ${props => props.theme.colors.primary600};
321
321
  transform: translateY(-1px);
322
322
  box-shadow: 0 2px 8px rgba(14, 165, 233, 0.15);
323
323
  }
@@ -338,8 +338,8 @@ const ImportLabel = styled.label`
338
338
  `;
339
339
 
340
340
  const BackButton = styled.button`
341
- background: white;
342
- border: 1px solid #dcdce4;
341
+ background: ${props => props.theme.colors.neutral0};
342
+ border: 1px solid ${props => props.theme.colors.neutral200};
343
343
  border-radius: 4px;
344
344
  padding: 8px 10px;
345
345
  height: 36px;
@@ -350,8 +350,8 @@ const BackButton = styled.button`
350
350
  transition: all 200ms;
351
351
 
352
352
  &:hover {
353
- background: #f6f6f9;
354
- border-color: #c0c0cf;
353
+ background: ${props => props.theme.colors.neutral100};
354
+ border-color: ${props => props.theme.colors.neutral300};
355
355
  transform: translateY(-1px);
356
356
  }
357
357
 
@@ -366,8 +366,8 @@ const BackButton = styled.button`
366
366
  `;
367
367
 
368
368
  const VersionButton = styled.button`
369
- background: white;
370
- border: 1px solid #dcdce4;
369
+ background: ${props => props.theme.colors.neutral0};
370
+ border: 1px solid ${props => props.theme.colors.neutral200};
371
371
  border-radius: 4px;
372
372
  padding: 8px 16px;
373
373
  height: 36px;
@@ -379,13 +379,13 @@ const VersionButton = styled.button`
379
379
  transition: all 200ms;
380
380
  font-size: 13px;
381
381
  font-weight: 500;
382
- color: #32324d;
382
+ color: ${props => props.theme.colors.neutral800};
383
383
  white-space: nowrap;
384
384
 
385
385
  &:hover {
386
- background: #f6f6f9;
387
- border-color: #0EA5E9;
388
- color: #0EA5E9;
386
+ background: ${props => props.theme.colors.neutral100};
387
+ border-color: ${props => props.theme.colors.primary600};
388
+ color: ${props => props.theme.colors.primary600};
389
389
  transform: translateY(-1px);
390
390
  box-shadow: 0 2px 8px rgba(14, 165, 233, 0.15);
391
391
  }
@@ -407,7 +407,7 @@ const VersionModal = styled.div`
407
407
  right: ${props => props.$isOpen ? '0' : '-450px'};
408
408
  width: 450px;
409
409
  height: 100vh;
410
- background: white;
410
+ background: ${props => props.theme.colors.neutral0};
411
411
  box-shadow: -4px 0 24px rgba(0, 0, 0, 0.15);
412
412
  z-index: 9999;
413
413
  transition: right 300ms cubic-bezier(0.4, 0, 0.2, 1);
@@ -430,7 +430,7 @@ const VersionModalOverlay = styled.div`
430
430
 
431
431
  const VersionModalHeader = styled.div`
432
432
  padding: 24px;
433
- border-bottom: 1px solid #eaeaef;
433
+ border-bottom: 1px solid ${props => props.theme.colors.neutral200};
434
434
  display: flex;
435
435
  justify-content: space-between;
436
436
  align-items: center;
@@ -444,13 +444,13 @@ const VersionModalContent = styled.div`
444
444
 
445
445
  const VersionItem = styled.div`
446
446
  padding: 16px;
447
- border: 1px solid #eaeaef;
447
+ border: 1px solid ${props => props.theme.colors.neutral200};
448
448
  border-radius: 8px;
449
449
  margin-bottom: 12px;
450
450
  transition: all 150ms;
451
451
 
452
452
  &:hover {
453
- border-color: #0EA5E9;
453
+ border-color: ${props => props.theme.colors.primary600};
454
454
  box-shadow: 0 2px 8px rgba(14, 165, 233, 0.15);
455
455
  }
456
456
  `;
@@ -464,7 +464,7 @@ const VersionItemHeader = styled.div`
464
464
 
465
465
  const VersionNumber = styled.div`
466
466
  font-weight: 600;
467
- color: #32324d;
467
+ color: ${props => props.theme.colors.neutral800};
468
468
  display: flex;
469
469
  align-items: center;
470
470
  gap: 8px;
@@ -481,12 +481,12 @@ const VersionBadge = styled.span`
481
481
 
482
482
  const VersionDate = styled.div`
483
483
  font-size: 13px;
484
- color: #666687;
484
+ color: ${props => props.theme.colors.neutral600};
485
485
  `;
486
486
 
487
487
  const VersionMeta = styled.div`
488
488
  font-size: 13px;
489
- color: #666687;
489
+ color: ${props => props.theme.colors.neutral600};
490
490
  margin-bottom: 12px;
491
491
  `;
492
492
 
@@ -543,12 +543,12 @@ const CloseButton = styled.button`
543
543
  display: flex;
544
544
  align-items: center;
545
545
  justify-content: center;
546
- color: #666687;
546
+ color: ${props => props.theme.colors.neutral600};
547
547
  transition: all 150ms;
548
548
 
549
549
  &:hover {
550
- color: #32324d;
551
- background: #f6f6f9;
550
+ color: ${props => props.theme.colors.neutral800};
551
+ background: ${props => props.theme.colors.neutral100};
552
552
  border-radius: 4px;
553
553
  }
554
554
 
@@ -561,7 +561,7 @@ const CloseButton = styled.button`
561
561
  const EmptyVersions = styled.div`
562
562
  text-align: center;
563
563
  padding: 60px 20px;
564
- color: #666687;
564
+ color: ${props => props.theme.colors.neutral600};
565
565
  display: flex;
566
566
  flex-direction: column;
567
567
  align-items: center;
@@ -571,7 +571,7 @@ const EmptyVersions = styled.div`
571
571
  width: 64px;
572
572
  height: 64px;
573
573
  margin-bottom: 16px;
574
- color: #dcdce4;
574
+ color: ${props => props.theme.colors.neutral300};
575
575
  }
576
576
  `;
577
577