@bernierllc/email 1.0.1 → 1.1.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.
- package/README.md +76 -217
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/simple-email-service.d.ts +58 -0
- package/dist/simple-email-service.d.ts.map +1 -0
- package/dist/simple-email-service.js +416 -0
- package/dist/simple-email-service.js.map +1 -0
- package/dist/types.d.ts +311 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +33 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -22
- package/.eslintrc.json +0 -112
- package/.flake8 +0 -18
- package/.github/workflows/ci.yml +0 -300
- package/EXTRACTION_SUMMARY.md +0 -265
- package/IMPLEMENTATION_STATUS.md +0 -159
- package/OPEN_SOURCE_SETUP.md +0 -420
- package/PACKAGE_USAGE.md +0 -471
- package/examples/fastapi-example/main.py +0 -257
- package/examples/nextjs-example/next-env.d.ts +0 -13
- package/examples/nextjs-example/package.json +0 -26
- package/examples/nextjs-example/pages/admin/templates.tsx +0 -157
- package/examples/nextjs-example/tsconfig.json +0 -28
- package/packages/core/package.json +0 -70
- package/packages/core/rollup.config.js +0 -37
- package/packages/core/specification.md +0 -416
- package/packages/core/src/adapters/supabase.ts +0 -291
- package/packages/core/src/core/scheduler.ts +0 -356
- package/packages/core/src/core/template-manager.ts +0 -388
- package/packages/core/src/index.ts +0 -30
- package/packages/core/src/providers/base.ts +0 -104
- package/packages/core/src/providers/sendgrid.ts +0 -368
- package/packages/core/src/types/provider.ts +0 -91
- package/packages/core/src/types/scheduled.ts +0 -78
- package/packages/core/src/types/template.ts +0 -97
- package/packages/core/tsconfig.json +0 -23
- package/packages/python/README.md +0 -106
- package/packages/python/email_template_manager/__init__.py +0 -66
- package/packages/python/email_template_manager/config.py +0 -98
- package/packages/python/email_template_manager/core/magic_links.py +0 -245
- package/packages/python/email_template_manager/core/manager.py +0 -344
- package/packages/python/email_template_manager/core/scheduler.py +0 -473
- package/packages/python/email_template_manager/exceptions.py +0 -67
- package/packages/python/email_template_manager/models/magic_link.py +0 -59
- package/packages/python/email_template_manager/models/scheduled.py +0 -78
- package/packages/python/email_template_manager/models/template.py +0 -90
- package/packages/python/email_template_manager/providers/aws_ses.py +0 -44
- package/packages/python/email_template_manager/providers/base.py +0 -94
- package/packages/python/email_template_manager/providers/sendgrid.py +0 -325
- package/packages/python/email_template_manager/providers/smtp.py +0 -44
- package/packages/python/pyproject.toml +0 -133
- package/packages/python/setup.py +0 -93
- package/packages/python/specification.md +0 -930
- package/packages/react/README.md +0 -13
- package/packages/react/package.json +0 -105
- package/packages/react/rollup.config.js +0 -37
- package/packages/react/specification.md +0 -569
- package/packages/react/src/index.ts +0 -20
- package/packages/react/tsconfig.json +0 -24
- package/plans/email-template-manager_app-admin.md +0 -590
- package/src/index.js +0 -1
- package/test_package.py +0 -125
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Copyright (c) 2025 Bernier LLC
|
|
3
|
-
|
|
4
|
-
This file is licensed to the client under a limited-use license.
|
|
5
|
-
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
-
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from fastapi import FastAPI, BackgroundTasks, HTTPException
|
|
10
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
11
|
-
from contextlib import asynccontextmanager
|
|
12
|
-
import os
|
|
13
|
-
from typing import List, Dict, Any
|
|
14
|
-
|
|
15
|
-
from email_template_manager import (
|
|
16
|
-
EmailTemplateManager,
|
|
17
|
-
EmailScheduler,
|
|
18
|
-
MagicLinkManager,
|
|
19
|
-
SendGridProvider,
|
|
20
|
-
EmailTemplate,
|
|
21
|
-
ScheduledEmail,
|
|
22
|
-
MagicLink
|
|
23
|
-
)
|
|
24
|
-
from email_template_manager.integrations.fastapi import create_email_router
|
|
25
|
-
|
|
26
|
-
# Configuration
|
|
27
|
-
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://user:pass@localhost/email_templates")
|
|
28
|
-
SENDGRID_API_KEY = os.getenv("SENDGRID_API_KEY")
|
|
29
|
-
ENCRYPTION_KEY = os.getenv("ENCRYPTION_KEY", "your-secret-encryption-key")
|
|
30
|
-
|
|
31
|
-
# Global managers
|
|
32
|
-
template_manager: EmailTemplateManager = None
|
|
33
|
-
scheduler: EmailScheduler = None
|
|
34
|
-
magic_link_manager: MagicLinkManager = None
|
|
35
|
-
|
|
36
|
-
@asynccontextmanager
|
|
37
|
-
async def lifespan(app: FastAPI):
|
|
38
|
-
# Startup
|
|
39
|
-
global template_manager, scheduler, magic_link_manager
|
|
40
|
-
|
|
41
|
-
# Initialize components
|
|
42
|
-
template_manager = EmailTemplateManager(DATABASE_URL)
|
|
43
|
-
|
|
44
|
-
email_provider = SendGridProvider({
|
|
45
|
-
"api_key": SENDGRID_API_KEY,
|
|
46
|
-
"from_email": "noreply@yourapp.com",
|
|
47
|
-
"from_name": "Your App"
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
scheduler = EmailScheduler(template_manager, email_provider)
|
|
51
|
-
magic_link_manager = MagicLinkManager(template_manager, ENCRYPTION_KEY)
|
|
52
|
-
|
|
53
|
-
print("Email Template Manager initialized")
|
|
54
|
-
yield
|
|
55
|
-
|
|
56
|
-
# Shutdown
|
|
57
|
-
print("Email Template Manager shutting down")
|
|
58
|
-
|
|
59
|
-
# Create FastAPI app
|
|
60
|
-
app = FastAPI(
|
|
61
|
-
title="Email Template Manager Example",
|
|
62
|
-
description="Example FastAPI application using email-template-manager",
|
|
63
|
-
version="1.0.0",
|
|
64
|
-
lifespan=lifespan
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
# Add CORS middleware
|
|
68
|
-
app.add_middleware(
|
|
69
|
-
CORSMiddleware,
|
|
70
|
-
allow_origins=["http://localhost:3000"], # React dev server
|
|
71
|
-
allow_credentials=True,
|
|
72
|
-
allow_methods=["*"],
|
|
73
|
-
allow_headers=["*"],
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
# Include the email management router
|
|
77
|
-
email_router = create_email_router(
|
|
78
|
-
get_template_manager=lambda: template_manager,
|
|
79
|
-
get_scheduler=lambda: scheduler,
|
|
80
|
-
get_magic_link_manager=lambda: magic_link_manager
|
|
81
|
-
)
|
|
82
|
-
app.include_router(email_router, prefix="/api/email", tags=["email"])
|
|
83
|
-
|
|
84
|
-
# Custom endpoints for your application
|
|
85
|
-
@app.post("/api/users/{user_id}/welcome-email")
|
|
86
|
-
async def send_welcome_email(
|
|
87
|
-
user_id: str,
|
|
88
|
-
user_email: str,
|
|
89
|
-
user_name: str,
|
|
90
|
-
background_tasks: BackgroundTasks
|
|
91
|
-
):
|
|
92
|
-
"""Send a welcome email to a new user"""
|
|
93
|
-
try:
|
|
94
|
-
# Find the welcome email template
|
|
95
|
-
templates = await template_manager.list_templates(
|
|
96
|
-
filters={"name": "Welcome Email"}
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
if not templates:
|
|
100
|
-
raise HTTPException(status_code=404, detail="Welcome email template not found")
|
|
101
|
-
|
|
102
|
-
welcome_template = templates[0]
|
|
103
|
-
|
|
104
|
-
# Schedule the email
|
|
105
|
-
scheduled_email = ScheduledEmail(
|
|
106
|
-
template_id=welcome_template.id,
|
|
107
|
-
recipient_email=user_email,
|
|
108
|
-
recipient_name=user_name,
|
|
109
|
-
variables={
|
|
110
|
-
"name": user_name,
|
|
111
|
-
"email": user_email,
|
|
112
|
-
"user_id": user_id
|
|
113
|
-
},
|
|
114
|
-
trigger_type="immediate",
|
|
115
|
-
metadata={"user_id": user_id, "type": "welcome"}
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
result = await scheduler.schedule_email(scheduled_email)
|
|
119
|
-
|
|
120
|
-
# Process immediately in the background
|
|
121
|
-
background_tasks.add_task(scheduler.process_pending_emails)
|
|
122
|
-
|
|
123
|
-
return {"success": True, "scheduled_email_id": result.id}
|
|
124
|
-
|
|
125
|
-
except Exception as e:
|
|
126
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
127
|
-
|
|
128
|
-
@app.post("/api/events/{event_id}/reminder-emails")
|
|
129
|
-
async def schedule_event_reminders(
|
|
130
|
-
event_id: str,
|
|
131
|
-
event_date: str,
|
|
132
|
-
attendees: List[Dict[str, str]]
|
|
133
|
-
):
|
|
134
|
-
"""Schedule reminder emails for an event"""
|
|
135
|
-
try:
|
|
136
|
-
# Find reminder email templates
|
|
137
|
-
templates = await template_manager.list_templates(
|
|
138
|
-
filters={"tags": ["event", "reminder"]}
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
if not templates:
|
|
142
|
-
raise HTTPException(status_code=404, detail="Event reminder template not found")
|
|
143
|
-
|
|
144
|
-
reminder_template = templates[0]
|
|
145
|
-
scheduled_emails = []
|
|
146
|
-
|
|
147
|
-
for attendee in attendees:
|
|
148
|
-
# Schedule 7 days before
|
|
149
|
-
scheduled_email = ScheduledEmail(
|
|
150
|
-
template_id=reminder_template.id,
|
|
151
|
-
recipient_email=attendee["email"],
|
|
152
|
-
recipient_name=attendee.get("name", ""),
|
|
153
|
-
variables={
|
|
154
|
-
"name": attendee.get("name", ""),
|
|
155
|
-
"event_id": event_id,
|
|
156
|
-
"event_date": event_date
|
|
157
|
-
},
|
|
158
|
-
trigger_type="relative",
|
|
159
|
-
trigger_offset=-7, # 7 days before
|
|
160
|
-
reference_date=event_date,
|
|
161
|
-
metadata={"event_id": event_id, "type": "reminder"}
|
|
162
|
-
)
|
|
163
|
-
scheduled_emails.append(scheduled_email)
|
|
164
|
-
|
|
165
|
-
results = await scheduler.schedule_batch(scheduled_emails)
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
"success": True,
|
|
169
|
-
"scheduled_count": len(results),
|
|
170
|
-
"scheduled_email_ids": [r.id for r in results]
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
except Exception as e:
|
|
174
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
175
|
-
|
|
176
|
-
@app.post("/api/magic-links/user-onboarding")
|
|
177
|
-
async def generate_onboarding_link(
|
|
178
|
-
user_email: str,
|
|
179
|
-
user_id: str,
|
|
180
|
-
expires_in_hours: int = 72
|
|
181
|
-
):
|
|
182
|
-
"""Generate a magic link for user onboarding"""
|
|
183
|
-
try:
|
|
184
|
-
magic_link = await magic_link_manager.generate_magic_link(
|
|
185
|
-
email=user_email,
|
|
186
|
-
link_type="user_onboarding",
|
|
187
|
-
payload={"user_id": user_id, "step": "profile_setup"},
|
|
188
|
-
expires_in_hours=expires_in_hours
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
# In a real app, you'd send this link via email
|
|
192
|
-
onboarding_url = f"https://yourapp.com/onboarding?token={magic_link.token}"
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
"success": True,
|
|
196
|
-
"magic_link_id": magic_link.id,
|
|
197
|
-
"onboarding_url": onboarding_url,
|
|
198
|
-
"expires_at": magic_link.expires_at
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
except Exception as e:
|
|
202
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
203
|
-
|
|
204
|
-
@app.get("/api/onboarding")
|
|
205
|
-
async def validate_onboarding_token(token: str):
|
|
206
|
-
"""Validate and use an onboarding magic link"""
|
|
207
|
-
try:
|
|
208
|
-
magic_link = await magic_link_manager.validate_magic_link(token)
|
|
209
|
-
|
|
210
|
-
if not magic_link:
|
|
211
|
-
raise HTTPException(status_code=404, detail="Invalid or expired token")
|
|
212
|
-
|
|
213
|
-
if magic_link.type != "user_onboarding":
|
|
214
|
-
raise HTTPException(status_code=400, detail="Invalid token type")
|
|
215
|
-
|
|
216
|
-
# Use the token (marks it as used)
|
|
217
|
-
used_link = await magic_link_manager.use_magic_link(token)
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
"success": True,
|
|
221
|
-
"user_id": used_link.payload.get("user_id"),
|
|
222
|
-
"step": used_link.payload.get("step"),
|
|
223
|
-
"email": used_link.email
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
except Exception as e:
|
|
227
|
-
raise HTTPException(status_code=400, detail=str(e))
|
|
228
|
-
|
|
229
|
-
# Background task endpoint for processing emails
|
|
230
|
-
@app.post("/internal/process-emails")
|
|
231
|
-
async def process_pending_emails():
|
|
232
|
-
"""Internal endpoint to process pending emails (called by cron job)"""
|
|
233
|
-
try:
|
|
234
|
-
result = await scheduler.process_pending_emails()
|
|
235
|
-
return {
|
|
236
|
-
"success": True,
|
|
237
|
-
"processed": result.get("processed", 0),
|
|
238
|
-
"failed": result.get("failed", 0),
|
|
239
|
-
"errors": result.get("errors", [])
|
|
240
|
-
}
|
|
241
|
-
except Exception as e:
|
|
242
|
-
raise HTTPException(status_code=500, detail=str(e))
|
|
243
|
-
|
|
244
|
-
# Health check
|
|
245
|
-
@app.get("/health")
|
|
246
|
-
async def health_check():
|
|
247
|
-
"""Health check endpoint"""
|
|
248
|
-
try:
|
|
249
|
-
# Test database connection
|
|
250
|
-
await template_manager.list_templates(skip=0, limit=1)
|
|
251
|
-
return {"status": "healthy", "service": "email-template-manager"}
|
|
252
|
-
except Exception as e:
|
|
253
|
-
raise HTTPException(status_code=503, detail=f"Service unhealthy: {str(e)}")
|
|
254
|
-
|
|
255
|
-
if __name__ == "__main__":
|
|
256
|
-
import uvicorn
|
|
257
|
-
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright (c) 2025 Bernier LLC
|
|
3
|
-
|
|
4
|
-
This file is licensed to the client under a limited-use license.
|
|
5
|
-
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
-
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/// <reference types="next" />
|
|
10
|
-
/// <reference types="next/image-types/global" />
|
|
11
|
-
|
|
12
|
-
// NOTE: This file should not be edited
|
|
13
|
-
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "nextjs-email-template-example",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "next dev",
|
|
7
|
-
"build": "next build",
|
|
8
|
-
"start": "next start",
|
|
9
|
-
"lint": "next lint"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"@email-template-manager/core": "^1.0.0",
|
|
13
|
-
"@email-template-manager/react": "^1.0.0",
|
|
14
|
-
"next": "14.0.0",
|
|
15
|
-
"react": "^19.1.0",
|
|
16
|
-
"react-dom": "^19.1.0",
|
|
17
|
-
"typescript": "^5.0.0"
|
|
18
|
-
},
|
|
19
|
-
"devDependencies": {
|
|
20
|
-
"@types/node": "^20.0.0",
|
|
21
|
-
"@types/react": "^19.0.0",
|
|
22
|
-
"@types/react-dom": "^19.0.0",
|
|
23
|
-
"eslint": "^8.0.0",
|
|
24
|
-
"eslint-config-next": "14.0.0"
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright (c) 2025 Bernier LLC
|
|
3
|
-
|
|
4
|
-
This file is licensed to the client under a limited-use license.
|
|
5
|
-
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
-
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { useState } from 'react';
|
|
10
|
-
import {
|
|
11
|
-
EmailTemplateProvider,
|
|
12
|
-
EmailTemplateList,
|
|
13
|
-
EmailTemplateEditor,
|
|
14
|
-
useEmailTemplates
|
|
15
|
-
} from '@email-template-manager/react';
|
|
16
|
-
import { EmailManagerConfig } from '@email-template-manager/core';
|
|
17
|
-
|
|
18
|
-
const config: EmailManagerConfig = {
|
|
19
|
-
database: {
|
|
20
|
-
type: 'postgresql',
|
|
21
|
-
connectionString: process.env.DATABASE_URL!,
|
|
22
|
-
},
|
|
23
|
-
emailProvider: {
|
|
24
|
-
provider: 'sendgrid',
|
|
25
|
-
apiKey: process.env.SENDGRID_API_KEY!,
|
|
26
|
-
fromEmail: 'noreply@yourapp.com',
|
|
27
|
-
fromName: 'Your App'
|
|
28
|
-
},
|
|
29
|
-
templates: {
|
|
30
|
-
defaultFromEmail: 'noreply@yourapp.com',
|
|
31
|
-
defaultFromName: 'Your App',
|
|
32
|
-
liquidEngine: {
|
|
33
|
-
strictVariables: false
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
scheduling: {
|
|
37
|
-
defaultMaxRetries: 3,
|
|
38
|
-
retryDelayMinutes: [5, 15, 60],
|
|
39
|
-
batchSize: 100,
|
|
40
|
-
processingIntervalMinutes: 5
|
|
41
|
-
},
|
|
42
|
-
security: {
|
|
43
|
-
encryptionKey: process.env.ENCRYPTION_KEY!,
|
|
44
|
-
tokenLength: 32,
|
|
45
|
-
defaultExpiryHours: 24
|
|
46
|
-
},
|
|
47
|
-
logging: {
|
|
48
|
-
level: 'info',
|
|
49
|
-
includePayload: false
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
function TemplateManager() {
|
|
54
|
-
const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null);
|
|
55
|
-
const [showEditor, setShowEditor] = useState(false);
|
|
56
|
-
const { templates, createTemplate, updateTemplate, deleteTemplate } = useEmailTemplates();
|
|
57
|
-
|
|
58
|
-
const handleCreateNew = () => {
|
|
59
|
-
setSelectedTemplateId(null);
|
|
60
|
-
setShowEditor(true);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const handleEdit = (templateId: string) => {
|
|
64
|
-
setSelectedTemplateId(templateId);
|
|
65
|
-
setShowEditor(true);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const handleSave = async (template: any) => {
|
|
69
|
-
if (selectedTemplateId) {
|
|
70
|
-
await updateTemplate(selectedTemplateId, template);
|
|
71
|
-
} else {
|
|
72
|
-
await createTemplate(template);
|
|
73
|
-
}
|
|
74
|
-
setShowEditor(false);
|
|
75
|
-
setSelectedTemplateId(null);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const handleCancel = () => {
|
|
79
|
-
setShowEditor(false);
|
|
80
|
-
setSelectedTemplateId(null);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
if (showEditor) {
|
|
84
|
-
const selectedTemplate = selectedTemplateId
|
|
85
|
-
? templates.find(t => t.id === selectedTemplateId)
|
|
86
|
-
: undefined;
|
|
87
|
-
|
|
88
|
-
return (
|
|
89
|
-
<div className="container mx-auto p-6">
|
|
90
|
-
<EmailTemplateEditor
|
|
91
|
-
template={selectedTemplate}
|
|
92
|
-
onSave={handleSave}
|
|
93
|
-
onCancel={handleCancel}
|
|
94
|
-
variables={[
|
|
95
|
-
{ name: 'name', type: 'text', required: true, description: 'User name' },
|
|
96
|
-
{ name: 'email', type: 'text', required: true, description: 'User email' },
|
|
97
|
-
{ name: 'company', type: 'text', required: false, description: 'Company name' },
|
|
98
|
-
{ name: 'custom_message', type: 'text', required: false, description: 'Custom message' }
|
|
99
|
-
]}
|
|
100
|
-
previewData={{
|
|
101
|
-
name: 'John Doe',
|
|
102
|
-
email: 'john@example.com',
|
|
103
|
-
company: 'Acme Corp',
|
|
104
|
-
custom_message: 'Welcome to our platform!'
|
|
105
|
-
}}
|
|
106
|
-
className="max-w-6xl"
|
|
107
|
-
/>
|
|
108
|
-
</div>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return (
|
|
113
|
-
<div className="container mx-auto p-6">
|
|
114
|
-
<div className="flex justify-between items-center mb-6">
|
|
115
|
-
<h1 className="text-3xl font-bold text-gray-900">Email Templates</h1>
|
|
116
|
-
<button
|
|
117
|
-
onClick={handleCreateNew}
|
|
118
|
-
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md font-medium"
|
|
119
|
-
>
|
|
120
|
-
Create New Template
|
|
121
|
-
</button>
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
<EmailTemplateList
|
|
125
|
-
templates={templates}
|
|
126
|
-
onSelect={(template) => console.log('Selected:', template)}
|
|
127
|
-
onEdit={(template) => handleEdit(template.id!)}
|
|
128
|
-
onDelete={(templateId) => deleteTemplate(templateId)}
|
|
129
|
-
onDuplicate={async (template) => {
|
|
130
|
-
const newTemplate = { ...template, name: `${template.name} (Copy)` };
|
|
131
|
-
delete newTemplate.id;
|
|
132
|
-
await createTemplate(newTemplate);
|
|
133
|
-
}}
|
|
134
|
-
className="bg-white rounded-lg shadow"
|
|
135
|
-
/>
|
|
136
|
-
</div>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export default function TemplatesPage() {
|
|
141
|
-
return (
|
|
142
|
-
<EmailTemplateProvider config={config}>
|
|
143
|
-
<div className="min-h-screen bg-gray-50">
|
|
144
|
-
<nav className="bg-white shadow-sm border-b">
|
|
145
|
-
<div className="container mx-auto px-6 py-4">
|
|
146
|
-
<h1 className="text-xl font-semibold text-gray-900">
|
|
147
|
-
Email Template Manager
|
|
148
|
-
</h1>
|
|
149
|
-
</div>
|
|
150
|
-
</nav>
|
|
151
|
-
<main>
|
|
152
|
-
<TemplateManager />
|
|
153
|
-
</main>
|
|
154
|
-
</div>
|
|
155
|
-
</EmailTemplateProvider>
|
|
156
|
-
);
|
|
157
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"lib": [
|
|
4
|
-
"dom",
|
|
5
|
-
"dom.iterable",
|
|
6
|
-
"esnext"
|
|
7
|
-
],
|
|
8
|
-
"allowJs": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"strict": false,
|
|
11
|
-
"noEmit": true,
|
|
12
|
-
"incremental": true,
|
|
13
|
-
"esModuleInterop": true,
|
|
14
|
-
"module": "esnext",
|
|
15
|
-
"moduleResolution": "node",
|
|
16
|
-
"resolveJsonModule": true,
|
|
17
|
-
"isolatedModules": true,
|
|
18
|
-
"jsx": "preserve"
|
|
19
|
-
},
|
|
20
|
-
"include": [
|
|
21
|
-
"next-env.d.ts",
|
|
22
|
-
"**/*.ts",
|
|
23
|
-
"**/*.tsx"
|
|
24
|
-
],
|
|
25
|
-
"exclude": [
|
|
26
|
-
"node_modules"
|
|
27
|
-
]
|
|
28
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@email-template-manager/core",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Core email template management library - framework agnostic",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.esm.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"files": [
|
|
9
|
-
"dist",
|
|
10
|
-
"README.md"
|
|
11
|
-
],
|
|
12
|
-
"scripts": {
|
|
13
|
-
"build": "rollup -c",
|
|
14
|
-
"build:watch": "rollup -c -w",
|
|
15
|
-
"test": "jest",
|
|
16
|
-
"test:watch": "jest --watch",
|
|
17
|
-
"test:coverage": "jest --coverage",
|
|
18
|
-
"lint": "eslint src --ext .ts,.tsx",
|
|
19
|
-
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
20
|
-
"type-check": "tsc --noEmit",
|
|
21
|
-
"clean": "rimraf dist",
|
|
22
|
-
"dev": "npm run build:watch",
|
|
23
|
-
"prepublishOnly": "npm run clean && npm run build"
|
|
24
|
-
},
|
|
25
|
-
"keywords": [
|
|
26
|
-
"email",
|
|
27
|
-
"templates",
|
|
28
|
-
"scheduling",
|
|
29
|
-
"sendgrid",
|
|
30
|
-
"notifications",
|
|
31
|
-
"typescript"
|
|
32
|
-
],
|
|
33
|
-
"author": "Matt Bernier <matt@example.com>",
|
|
34
|
-
"license": "MIT",
|
|
35
|
-
"repository": {
|
|
36
|
-
"type": "git",
|
|
37
|
-
"url": "https://github.com/mattbernier/email-template-manager.git",
|
|
38
|
-
"directory": "packages/core"
|
|
39
|
-
},
|
|
40
|
-
"homepage": "https://github.com/mattbernier/email-template-manager#readme",
|
|
41
|
-
"bugs": {
|
|
42
|
-
"url": "https://github.com/mattbernier/email-template-manager/issues"
|
|
43
|
-
},
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"date-fns": "^2.30.0",
|
|
46
|
-
"liquidjs": "^10.9.0",
|
|
47
|
-
"zod": "^3.22.4"
|
|
48
|
-
},
|
|
49
|
-
"devDependencies": {
|
|
50
|
-
"@rollup/plugin-commonjs": "^25.0.7",
|
|
51
|
-
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
52
|
-
"@rollup/plugin-typescript": "^11.1.5",
|
|
53
|
-
"@types/jest": "^29.5.8",
|
|
54
|
-
"@types/node": "^20.9.0",
|
|
55
|
-
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
|
56
|
-
"@typescript-eslint/parser": "^6.11.0",
|
|
57
|
-
"eslint": "^8.54.0",
|
|
58
|
-
"jest": "^29.7.0",
|
|
59
|
-
"rimraf": "^5.0.5",
|
|
60
|
-
"rollup": "^4.5.0",
|
|
61
|
-
"ts-jest": "^29.1.1",
|
|
62
|
-
"typescript": "^5.2.2"
|
|
63
|
-
},
|
|
64
|
-
"engines": {
|
|
65
|
-
"node": ">=16.0.0"
|
|
66
|
-
},
|
|
67
|
-
"publishConfig": {
|
|
68
|
-
"access": "public"
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright (c) 2025 Bernier LLC
|
|
3
|
-
|
|
4
|
-
This file is licensed to the client under a limited-use license.
|
|
5
|
-
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
-
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import resolve from '@rollup/plugin-node-resolve';
|
|
10
|
-
import commonjs from '@rollup/plugin-commonjs';
|
|
11
|
-
import typescript from '@rollup/plugin-typescript';
|
|
12
|
-
|
|
13
|
-
export default {
|
|
14
|
-
input: 'src/index.ts',
|
|
15
|
-
output: [
|
|
16
|
-
{
|
|
17
|
-
file: 'dist/index.js',
|
|
18
|
-
format: 'cjs',
|
|
19
|
-
sourcemap: true,
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
file: 'dist/index.esm.js',
|
|
23
|
-
format: 'esm',
|
|
24
|
-
sourcemap: true,
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
external: ['date-fns', 'liquidjs', 'zod'],
|
|
28
|
-
plugins: [
|
|
29
|
-
resolve(),
|
|
30
|
-
commonjs(),
|
|
31
|
-
typescript({
|
|
32
|
-
tsconfig: './tsconfig.json',
|
|
33
|
-
declaration: true,
|
|
34
|
-
declarationDir: './dist',
|
|
35
|
-
}),
|
|
36
|
-
],
|
|
37
|
-
};
|