strapi-plugin-magic-mail 1.0.1

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.
Files changed (91) hide show
  1. package/COPYRIGHT_NOTICE.txt +13 -0
  2. package/LICENSE +22 -0
  3. package/README.md +1420 -0
  4. package/admin/jsconfig.json +10 -0
  5. package/admin/src/components/AddAccountModal.jsx +1943 -0
  6. package/admin/src/components/Initializer.jsx +14 -0
  7. package/admin/src/components/LicenseGuard.jsx +475 -0
  8. package/admin/src/components/PluginIcon.jsx +5 -0
  9. package/admin/src/hooks/useAuthRefresh.js +44 -0
  10. package/admin/src/hooks/useLicense.js +158 -0
  11. package/admin/src/index.js +86 -0
  12. package/admin/src/pages/Analytics.jsx +762 -0
  13. package/admin/src/pages/App.jsx +111 -0
  14. package/admin/src/pages/EmailDesigner/EditorPage.jsx +1405 -0
  15. package/admin/src/pages/EmailDesigner/TemplateList.jsx +1807 -0
  16. package/admin/src/pages/HomePage.jsx +1233 -0
  17. package/admin/src/pages/LicensePage.jsx +424 -0
  18. package/admin/src/pages/RoutingRules.jsx +1141 -0
  19. package/admin/src/pages/Settings.jsx +603 -0
  20. package/admin/src/pluginId.js +3 -0
  21. package/admin/src/translations/de.json +71 -0
  22. package/admin/src/translations/en.json +70 -0
  23. package/admin/src/translations/es.json +71 -0
  24. package/admin/src/translations/fr.json +71 -0
  25. package/admin/src/translations/pt.json +71 -0
  26. package/admin/src/utils/fetchWithRetry.js +123 -0
  27. package/admin/src/utils/getTranslation.js +5 -0
  28. package/dist/_chunks/App-B-Gp4Vbr.js +7568 -0
  29. package/dist/_chunks/App-BymMjoGM.mjs +7543 -0
  30. package/dist/_chunks/LicensePage-Bl02myMx.mjs +342 -0
  31. package/dist/_chunks/LicensePage-CJXwPnEe.js +344 -0
  32. package/dist/_chunks/Settings-C_TmKwcz.mjs +400 -0
  33. package/dist/_chunks/Settings-zuFQ3pnn.js +402 -0
  34. package/dist/_chunks/de-CN-G9j1S.js +64 -0
  35. package/dist/_chunks/de-DS04rP54.mjs +64 -0
  36. package/dist/_chunks/en-BDc7Jk8u.js +64 -0
  37. package/dist/_chunks/en-BEFQJXvR.mjs +64 -0
  38. package/dist/_chunks/es-BpV1MIdm.js +64 -0
  39. package/dist/_chunks/es-DQHwzPpP.mjs +64 -0
  40. package/dist/_chunks/fr-BG1WfEVm.mjs +64 -0
  41. package/dist/_chunks/fr-vpziIpRp.js +64 -0
  42. package/dist/_chunks/pt-CMoGrOib.mjs +64 -0
  43. package/dist/_chunks/pt-ODpAhDNa.js +64 -0
  44. package/dist/admin/index.js +89 -0
  45. package/dist/admin/index.mjs +90 -0
  46. package/dist/server/index.js +6214 -0
  47. package/dist/server/index.mjs +6208 -0
  48. package/package.json +113 -0
  49. package/server/jsconfig.json +10 -0
  50. package/server/src/bootstrap.js +153 -0
  51. package/server/src/config/features.js +260 -0
  52. package/server/src/config/index.js +6 -0
  53. package/server/src/content-types/email-account/schema.json +93 -0
  54. package/server/src/content-types/email-event/index.js +8 -0
  55. package/server/src/content-types/email-event/schema.json +57 -0
  56. package/server/src/content-types/email-link/index.js +8 -0
  57. package/server/src/content-types/email-link/schema.json +49 -0
  58. package/server/src/content-types/email-log/index.js +8 -0
  59. package/server/src/content-types/email-log/schema.json +106 -0
  60. package/server/src/content-types/email-template/schema.json +74 -0
  61. package/server/src/content-types/email-template-version/schema.json +60 -0
  62. package/server/src/content-types/index.js +33 -0
  63. package/server/src/content-types/routing-rule/schema.json +59 -0
  64. package/server/src/controllers/accounts.js +220 -0
  65. package/server/src/controllers/analytics.js +347 -0
  66. package/server/src/controllers/controller.js +26 -0
  67. package/server/src/controllers/email-designer.js +474 -0
  68. package/server/src/controllers/index.js +21 -0
  69. package/server/src/controllers/license.js +267 -0
  70. package/server/src/controllers/oauth.js +474 -0
  71. package/server/src/controllers/routing-rules.js +122 -0
  72. package/server/src/controllers/test.js +383 -0
  73. package/server/src/destroy.js +23 -0
  74. package/server/src/index.js +25 -0
  75. package/server/src/middlewares/index.js +3 -0
  76. package/server/src/policies/index.js +3 -0
  77. package/server/src/register.js +5 -0
  78. package/server/src/routes/admin.js +469 -0
  79. package/server/src/routes/content-api.js +37 -0
  80. package/server/src/routes/index.js +9 -0
  81. package/server/src/services/account-manager.js +277 -0
  82. package/server/src/services/analytics.js +496 -0
  83. package/server/src/services/email-designer.js +870 -0
  84. package/server/src/services/email-router.js +1420 -0
  85. package/server/src/services/index.js +17 -0
  86. package/server/src/services/license-guard.js +418 -0
  87. package/server/src/services/oauth.js +515 -0
  88. package/server/src/services/service.js +7 -0
  89. package/server/src/utils/encryption.js +81 -0
  90. package/strapi-admin.js +4 -0
  91. package/strapi-server.js +4 -0
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Analytics Controller
3
+ * Handles tracking endpoints and analytics API
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ module.exports = ({ strapi }) => ({
9
+ /**
10
+ * Tracking pixel endpoint
11
+ * GET /magic-mail/track/open/:emailId/:recipientHash
12
+ */
13
+ async trackOpen(ctx) {
14
+ const { emailId, recipientHash } = ctx.params;
15
+
16
+ // Record the open event
17
+ await strapi.plugin('magic-mail').service('analytics').recordOpen(emailId, recipientHash, ctx.request);
18
+
19
+ // Return 1x1 transparent GIF
20
+ const pixel = Buffer.from(
21
+ 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
22
+ 'base64'
23
+ );
24
+
25
+ ctx.type = 'image/gif';
26
+ ctx.body = pixel;
27
+ },
28
+
29
+ /**
30
+ * Click tracking endpoint
31
+ * GET /magic-mail/track/click/:emailId/:linkHash/:recipientHash
32
+ */
33
+ async trackClick(ctx) {
34
+ const { emailId, linkHash, recipientHash } = ctx.params;
35
+ let { url } = ctx.query;
36
+
37
+ // Try to get URL from database if not in query string
38
+ if (!url) {
39
+ const analyticsService = strapi.plugin('magic-mail').service('analytics');
40
+ url = await analyticsService.getOriginalUrlFromHash(emailId, linkHash);
41
+ }
42
+
43
+ if (!url) {
44
+ return ctx.badRequest('Missing target URL');
45
+ }
46
+
47
+ // Record the click event
48
+ await strapi
49
+ .plugin('magic-mail')
50
+ .service('analytics')
51
+ .recordClick(emailId, linkHash, recipientHash, url, ctx.request);
52
+
53
+ // Redirect to the original URL
54
+ ctx.redirect(url);
55
+ },
56
+
57
+ /**
58
+ * Get analytics statistics
59
+ * GET /magic-mail/analytics/stats
60
+ */
61
+ async getStats(ctx) {
62
+ try {
63
+ const filters = {
64
+ userId: ctx.query.userId ? parseInt(ctx.query.userId) : null,
65
+ templateId: ctx.query.templateId ? parseInt(ctx.query.templateId) : null,
66
+ accountId: ctx.query.accountId ? parseInt(ctx.query.accountId) : null,
67
+ dateFrom: ctx.query.dateFrom || null,
68
+ dateTo: ctx.query.dateTo || null,
69
+ };
70
+
71
+ // Remove null values
72
+ Object.keys(filters).forEach(key => filters[key] === null && delete filters[key]);
73
+
74
+ const stats = await strapi.plugin('magic-mail').service('analytics').getStats(filters);
75
+
76
+ return ctx.send({
77
+ success: true,
78
+ data: stats,
79
+ });
80
+ } catch (error) {
81
+ ctx.throw(500, error);
82
+ }
83
+ },
84
+
85
+ /**
86
+ * Get email logs
87
+ * GET /magic-mail/analytics/emails
88
+ */
89
+ async getEmailLogs(ctx) {
90
+ try {
91
+ const filters = {
92
+ userId: ctx.query.userId ? parseInt(ctx.query.userId) : null,
93
+ templateId: ctx.query.templateId ? parseInt(ctx.query.templateId) : null,
94
+ search: ctx.query.search || null,
95
+ };
96
+
97
+ const pagination = {
98
+ page: ctx.query.page ? parseInt(ctx.query.page) : 1,
99
+ pageSize: ctx.query.pageSize ? parseInt(ctx.query.pageSize) : 25,
100
+ };
101
+
102
+ // Remove null values
103
+ Object.keys(filters).forEach(key => filters[key] === null && delete filters[key]);
104
+
105
+ const result = await strapi
106
+ .plugin('magic-mail')
107
+ .service('analytics')
108
+ .getEmailLogs(filters, pagination);
109
+
110
+ return ctx.send({
111
+ success: true,
112
+ ...result,
113
+ });
114
+ } catch (error) {
115
+ ctx.throw(500, error);
116
+ }
117
+ },
118
+
119
+ /**
120
+ * Get email log details
121
+ * GET /magic-mail/analytics/emails/:emailId
122
+ */
123
+ async getEmailDetails(ctx) {
124
+ try {
125
+ const { emailId } = ctx.params;
126
+
127
+ const emailLog = await strapi
128
+ .plugin('magic-mail')
129
+ .service('analytics')
130
+ .getEmailLogDetails(emailId);
131
+
132
+ if (!emailLog) {
133
+ return ctx.notFound('Email log not found');
134
+ }
135
+
136
+ return ctx.send({
137
+ success: true,
138
+ data: emailLog,
139
+ });
140
+ } catch (error) {
141
+ ctx.throw(500, error);
142
+ }
143
+ },
144
+
145
+ /**
146
+ * Get user email activity
147
+ * GET /magic-mail/analytics/users/:userId
148
+ */
149
+ async getUserActivity(ctx) {
150
+ try {
151
+ const { userId } = ctx.params;
152
+
153
+ const activity = await strapi
154
+ .plugin('magic-mail')
155
+ .service('analytics')
156
+ .getUserActivity(parseInt(userId));
157
+
158
+ return ctx.send({
159
+ success: true,
160
+ data: activity,
161
+ });
162
+ } catch (error) {
163
+ ctx.throw(500, error);
164
+ }
165
+ },
166
+
167
+ /**
168
+ * Debug Analytics - Check database state
169
+ * GET /magic-mail/analytics/debug
170
+ */
171
+ async debug(ctx) {
172
+ try {
173
+ strapi.log.info('[magic-mail] 🔍 Running Analytics Debug...');
174
+
175
+ // Get email logs
176
+ const emailLogs = await strapi.db.query('plugin::magic-mail.email-log').findMany({
177
+ limit: 10,
178
+ orderBy: { sentAt: 'DESC' },
179
+ });
180
+
181
+ // Get email events
182
+ const emailEvents = await strapi.db.query('plugin::magic-mail.email-event').findMany({
183
+ limit: 20,
184
+ orderBy: { timestamp: 'DESC' },
185
+ populate: ['emailLog'],
186
+ });
187
+
188
+ // Get stats
189
+ const analyticsService = strapi.plugin('magic-mail').service('analytics');
190
+ const stats = await analyticsService.getStats();
191
+
192
+ // Get active accounts
193
+ const accounts = await strapi.entityService.findMany('plugin::magic-mail.email-account', {
194
+ filters: { isActive: true },
195
+ fields: ['id', 'name', 'provider', 'fromEmail', 'emailsSentToday', 'totalEmailsSent'],
196
+ });
197
+
198
+ // Generate sample tracking URLs
199
+ let sampleTrackingUrls = null;
200
+ if (emailLogs.length > 0) {
201
+ const testLog = emailLogs[0];
202
+ const testHash = analyticsService.generateRecipientHash(testLog.emailId, testLog.recipient);
203
+
204
+ const baseUrl = strapi.config.get('server.url') || 'http://localhost:1337';
205
+ sampleTrackingUrls = {
206
+ trackingPixel: `${baseUrl}/api/magic-mail/track/open/${testLog.emailId}/${testHash}`,
207
+ clickTracking: `${baseUrl}/api/magic-mail/track/click/${testLog.emailId}/test/${testHash}?url=https://example.com`,
208
+ emailId: testLog.emailId,
209
+ recipient: testLog.recipient,
210
+ };
211
+ }
212
+
213
+ return ctx.send({
214
+ success: true,
215
+ debug: {
216
+ timestamp: new Date().toISOString(),
217
+ stats,
218
+ emailLogsCount: emailLogs.length,
219
+ emailEventsCount: emailEvents.length,
220
+ activeAccountsCount: accounts.length,
221
+ recentEmailLogs: emailLogs.map(log => ({
222
+ emailId: log.emailId,
223
+ recipient: log.recipient,
224
+ subject: log.subject,
225
+ sentAt: log.sentAt,
226
+ openCount: log.openCount,
227
+ clickCount: log.clickCount,
228
+ firstOpenedAt: log.firstOpenedAt,
229
+ accountName: log.accountName,
230
+ templateName: log.templateName,
231
+ })),
232
+ recentEvents: emailEvents.map(event => ({
233
+ type: event.type,
234
+ timestamp: event.timestamp,
235
+ emailId: event.emailLog?.emailId,
236
+ ipAddress: event.ipAddress,
237
+ linkUrl: event.linkUrl,
238
+ })),
239
+ accounts,
240
+ sampleTrackingUrls,
241
+ notes: [
242
+ 'If emailLogsCount is 0: Emails are not being tracked (check if enableTracking=true)',
243
+ 'If openCount is 0: Tracking pixel not being loaded (check email HTML source)',
244
+ 'Test tracking URLs should be publicly accessible without authentication',
245
+ 'Check Strapi console logs for tracking events when opening emails',
246
+ ],
247
+ },
248
+ });
249
+ } catch (error) {
250
+ strapi.log.error('[magic-mail] Debug error:', error);
251
+ ctx.throw(500, error);
252
+ }
253
+ },
254
+
255
+ /**
256
+ * Delete single email log
257
+ * DELETE /magic-mail/analytics/emails/:emailId
258
+ */
259
+ async deleteEmailLog(ctx) {
260
+ try {
261
+ const { emailId } = ctx.params;
262
+
263
+ // Find email log
264
+ const emailLog = await strapi.db.query('plugin::magic-mail.email-log').findOne({
265
+ where: { emailId },
266
+ });
267
+
268
+ if (!emailLog) {
269
+ return ctx.notFound('Email log not found');
270
+ }
271
+
272
+ // Delete associated events first
273
+ await strapi.db.query('plugin::magic-mail.email-event').deleteMany({
274
+ where: { emailLog: emailLog.id },
275
+ });
276
+
277
+ // Delete email log
278
+ await strapi.db.query('plugin::magic-mail.email-log').delete({
279
+ where: { id: emailLog.id },
280
+ });
281
+
282
+ strapi.log.info(`[magic-mail] 🗑️ Deleted email log: ${emailId}`);
283
+
284
+ return ctx.send({
285
+ success: true,
286
+ message: 'Email log deleted successfully',
287
+ });
288
+ } catch (error) {
289
+ strapi.log.error('[magic-mail] Error deleting email log:', error);
290
+ ctx.throw(500, error);
291
+ }
292
+ },
293
+
294
+ /**
295
+ * Clear all email logs
296
+ * DELETE /magic-mail/analytics/emails
297
+ */
298
+ async clearAllEmailLogs(ctx) {
299
+ try {
300
+ // Optional: Add query params for filtered deletion
301
+ const { olderThan } = ctx.query; // e.g., ?olderThan=2024-01-01
302
+
303
+ const where = {};
304
+ if (olderThan) {
305
+ where.sentAt = { $lt: new Date(olderThan) };
306
+ }
307
+
308
+ // Get all email logs to delete
309
+ const emailLogs = await strapi.db.query('plugin::magic-mail.email-log').findMany({
310
+ where,
311
+ select: ['id'],
312
+ });
313
+
314
+ const emailLogIds = emailLogs.map(log => log.id);
315
+
316
+ if (emailLogIds.length === 0) {
317
+ return ctx.send({
318
+ success: true,
319
+ message: 'No email logs to delete',
320
+ deletedCount: 0,
321
+ });
322
+ }
323
+
324
+ // Delete all associated events
325
+ await strapi.db.query('plugin::magic-mail.email-event').deleteMany({
326
+ where: { emailLog: { $in: emailLogIds } },
327
+ });
328
+
329
+ // Delete all email logs
330
+ await strapi.db.query('plugin::magic-mail.email-log').deleteMany({
331
+ where: { id: { $in: emailLogIds } },
332
+ });
333
+
334
+ strapi.log.info(`[magic-mail] 🗑️ Cleared ${emailLogIds.length} email logs`);
335
+
336
+ return ctx.send({
337
+ success: true,
338
+ message: `Successfully deleted ${emailLogIds.length} email log(s)`,
339
+ deletedCount: emailLogIds.length,
340
+ });
341
+ } catch (error) {
342
+ strapi.log.error('[magic-mail] Error clearing email logs:', error);
343
+ ctx.throw(500, error);
344
+ }
345
+ },
346
+ });
347
+
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Main Controller
5
+ * Handles email sending requests
6
+ */
7
+
8
+ module.exports = {
9
+ /**
10
+ * Send email via MagicMail router
11
+ */
12
+ async send(ctx) {
13
+ try {
14
+ const emailRouter = strapi.plugin('magic-mail').service('email-router');
15
+ const result = await emailRouter.send(ctx.request.body);
16
+
17
+ ctx.body = {
18
+ success: true,
19
+ ...result,
20
+ };
21
+ } catch (err) {
22
+ strapi.log.error('[magic-mail] Error sending email:', err);
23
+ ctx.throw(500, err.message || 'Failed to send email');
24
+ }
25
+ },
26
+ };