@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.
Files changed (65) hide show
  1. package/README.md +76 -217
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +28 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/simple-email-service.d.ts +58 -0
  7. package/dist/simple-email-service.d.ts.map +1 -0
  8. package/dist/simple-email-service.js +416 -0
  9. package/dist/simple-email-service.js.map +1 -0
  10. package/dist/types.d.ts +311 -0
  11. package/dist/types.d.ts.map +1 -0
  12. package/dist/types.js +33 -0
  13. package/dist/types.js.map +1 -0
  14. package/package.json +57 -22
  15. package/.eslintrc.json +0 -112
  16. package/.flake8 +0 -18
  17. package/.github/workflows/ci.yml +0 -300
  18. package/EXTRACTION_SUMMARY.md +0 -265
  19. package/IMPLEMENTATION_STATUS.md +0 -159
  20. package/OPEN_SOURCE_SETUP.md +0 -420
  21. package/PACKAGE_USAGE.md +0 -471
  22. package/examples/fastapi-example/main.py +0 -257
  23. package/examples/nextjs-example/next-env.d.ts +0 -13
  24. package/examples/nextjs-example/package.json +0 -26
  25. package/examples/nextjs-example/pages/admin/templates.tsx +0 -157
  26. package/examples/nextjs-example/tsconfig.json +0 -28
  27. package/packages/core/package.json +0 -70
  28. package/packages/core/rollup.config.js +0 -37
  29. package/packages/core/specification.md +0 -416
  30. package/packages/core/src/adapters/supabase.ts +0 -291
  31. package/packages/core/src/core/scheduler.ts +0 -356
  32. package/packages/core/src/core/template-manager.ts +0 -388
  33. package/packages/core/src/index.ts +0 -30
  34. package/packages/core/src/providers/base.ts +0 -104
  35. package/packages/core/src/providers/sendgrid.ts +0 -368
  36. package/packages/core/src/types/provider.ts +0 -91
  37. package/packages/core/src/types/scheduled.ts +0 -78
  38. package/packages/core/src/types/template.ts +0 -97
  39. package/packages/core/tsconfig.json +0 -23
  40. package/packages/python/README.md +0 -106
  41. package/packages/python/email_template_manager/__init__.py +0 -66
  42. package/packages/python/email_template_manager/config.py +0 -98
  43. package/packages/python/email_template_manager/core/magic_links.py +0 -245
  44. package/packages/python/email_template_manager/core/manager.py +0 -344
  45. package/packages/python/email_template_manager/core/scheduler.py +0 -473
  46. package/packages/python/email_template_manager/exceptions.py +0 -67
  47. package/packages/python/email_template_manager/models/magic_link.py +0 -59
  48. package/packages/python/email_template_manager/models/scheduled.py +0 -78
  49. package/packages/python/email_template_manager/models/template.py +0 -90
  50. package/packages/python/email_template_manager/providers/aws_ses.py +0 -44
  51. package/packages/python/email_template_manager/providers/base.py +0 -94
  52. package/packages/python/email_template_manager/providers/sendgrid.py +0 -325
  53. package/packages/python/email_template_manager/providers/smtp.py +0 -44
  54. package/packages/python/pyproject.toml +0 -133
  55. package/packages/python/setup.py +0 -93
  56. package/packages/python/specification.md +0 -930
  57. package/packages/react/README.md +0 -13
  58. package/packages/react/package.json +0 -105
  59. package/packages/react/rollup.config.js +0 -37
  60. package/packages/react/specification.md +0 -569
  61. package/packages/react/src/index.ts +0 -20
  62. package/packages/react/tsconfig.json +0 -24
  63. package/plans/email-template-manager_app-admin.md +0 -590
  64. package/src/index.js +0 -1
  65. 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
- };