From 530f21744567c2cf2cd2c52c38177824efda210d Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:04:59 -0800 Subject: [PATCH] feat: Add AWS SES v2 email service Complete transactional email layer for Elmeg: - 3 SES templates (verification, password reset, security alert) - TypeScript integration module with AWS SDK v3 - Template deployment script - Usage examples - Comprehensive README with compliance notes --- email/README.md | 147 ++++++++++++++ email/package.json | 33 ++++ email/scripts/deploy-templates.ts | 234 +++++++++++++++++++++++ email/src/email-service.ts | 209 ++++++++++++++++++++ email/src/examples.ts | 176 +++++++++++++++++ email/templates/README.md | 306 ++++++++++++++++++++++++++++++ email/tsconfig.json | 26 +++ 7 files changed, 1131 insertions(+) create mode 100644 email/README.md create mode 100644 email/package.json create mode 100644 email/scripts/deploy-templates.ts create mode 100644 email/src/email-service.ts create mode 100644 email/src/examples.ts create mode 100644 email/templates/README.md create mode 100644 email/tsconfig.json diff --git a/email/README.md b/email/README.md new file mode 100644 index 0000000..3a7b14d --- /dev/null +++ b/email/README.md @@ -0,0 +1,147 @@ +# Elmeg Email Service + +Transactional email layer for Elmeg using Amazon SES v2 with stored templates. + +## Purpose + +This module provides a production-ready email service for user-initiated transactional emails: + +- **Email Verification** – Sent after user registration +- **Password Reset** – Sent when user requests password recovery +- **Security Alerts** – Sent for account security events (new logins, password changes) + +> **Compliance Note:** This service is strictly for transactional, user-initiated emails. No newsletters, marketing emails, or cold outreach. No purchased or third-party email lists are used. + +## Requirements + +- Node.js >= 18.0.0 +- AWS account with SES verified domain +- SES templates deployed (see below) + +## Environment Variables + +```bash +# Required +AWS_ACCESS_KEY_ID=AKIA... # IAM user with SES permissions +AWS_SECRET_ACCESS_KEY=... # IAM user secret key +AWS_SES_REGION=us-east-1 # SES region (domain must be verified here) +EMAIL_FROM=noreply@elmeg.xyz # Verified sender address + +# Optional +FRONTEND_URL=https://elmeg.xyz # For generating email links +SUPPORT_EMAIL=support@elmeg.xyz # Contact email in templates +``` + +## Installation + +```bash +cd email +npm install +npm run build +``` + +## Deploy Templates to SES + +Before sending emails, deploy the templates to AWS SES: + +```bash +npm run deploy-templates +``` + +This creates/updates three templates in SES: + +- `ELMEG_EMAIL_VERIFICATION` +- `ELMEG_PASSWORD_RESET` +- `ELMEG_SECURITY_ALERT` + +## Usage + +```typescript +import { + sendVerificationEmail, + sendPasswordResetEmail, + sendSecurityAlertEmail, + generateVerificationLink, + generateResetLink, +} from "@elmeg/email-service"; + +// After user registration +await sendVerificationEmail({ + to: "user@example.com", + userName: "John", + verificationLink: generateVerificationLink(token), +}); + +// After password reset request +await sendPasswordResetEmail({ + to: "user@example.com", + userName: "John", + resetLink: generateResetLink(token), +}); + +// After suspicious login +await sendSecurityAlertEmail({ + to: "user@example.com", + userName: "John", + securityEventDescription: "New sign-in from Chrome on Windows at 10:30 AM", +}); +``` + +## Template Placeholders + +| Placeholder | Description | Templates | +|-------------|-------------|-----------| +| `{{app_name}}` | "Elmeg" | All | +| `{{user_name}}` | User's name or email prefix | All | +| `{{support_email}}` | Support contact | All | +| `{{verification_link}}` | Email verification URL | Verification | +| `{{reset_link}}` | Password reset URL | Password Reset | +| `{{security_event_description}}` | Event details | Security Alert | + +## AWS SES Setup Checklist + +1. **Verify Domain** – Add `elmeg.xyz` in SES console with DKIM records +2. **Request Production Access** – Move out of sandbox to send to any address +3. **Create IAM User** – With `ses:SendEmail` and `ses:SendTemplatedEmail` permissions +4. **Deploy Templates** – Run `npm run deploy-templates` + +## Compliance & Best Practices + +| Requirement | Implementation | +|-------------|----------------| +| User-initiated only | All emails triggered by user actions | +| No purchased lists | Only registered users receive emails | +| Bounce handling | SES automatically suppresses bounced addresses | +| Complaint handling | SES suppresses addresses that report spam | +| Unsubscribe | N/A for transactional (required action emails) | + +## Error Handling + +All send functions return a structured result: + +```typescript +interface EmailResult { + success: boolean; + messageId?: string; // SES message ID on success + error?: { + code: string; // Error code from SES + message: string; // Human-readable error message + }; +} +``` + +## File Structure + +``` +email/ +├── src/ +│ ├── email-service.ts # Main service module +│ └── examples.ts # Usage examples +├── scripts/ +│ └── deploy-templates.ts # Template deployment script +├── templates/ +│ └── README.md # Template documentation +├── package.json +├── tsconfig.json +└── README.md # This file +``` diff --git a/email/package.json b/email/package.json new file mode 100644 index 0000000..2e7823b --- /dev/null +++ b/email/package.json @@ -0,0 +1,33 @@ +{ + "name": "@elmeg/email-service", + "version": "1.0.0", + "description": "Transactional email service for Elmeg using AWS SES v2", + "main": "dist/email-service.js", + "types": "dist/email-service.d.ts", + "scripts": { + "build": "tsc", + "test": "jest", + "deploy-templates": "ts-node scripts/deploy-templates.ts" + }, + "keywords": [ + "email", + "ses", + "aws", + "transactional" + ], + "author": "Elmeg", + "license": "MIT", + "dependencies": { + "@aws-sdk/client-sesv2": "^3.478.0" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "typescript": "^5.3.0", + "ts-node": "^10.9.0", + "jest": "^29.7.0", + "@types/jest": "^29.5.0" + }, + "engines": { + "node": ">=18.0.0" + } +} \ No newline at end of file diff --git a/email/scripts/deploy-templates.ts b/email/scripts/deploy-templates.ts new file mode 100644 index 0000000..5b6ae2e --- /dev/null +++ b/email/scripts/deploy-templates.ts @@ -0,0 +1,234 @@ +/** + * Deploy SES Templates to AWS + * + * Run this script to create or update the email templates in AWS SES. + * Usage: npx ts-node scripts/deploy-templates.ts + */ + +import { SESv2Client, CreateEmailTemplateCommand, UpdateEmailTemplateCommand } from "@aws-sdk/client-sesv2"; + +const sesClient = new SESv2Client({ + region: process.env.AWS_SES_REGION || "us-east-1", +}); + +const APP_NAME = "Elmeg"; +const SUPPORT_EMAIL = "support@elmeg.xyz"; + +// Template definitions +const templates = [ + { + TemplateName: "ELMEG_EMAIL_VERIFICATION", + TemplateContent: { + Subject: "Verify your Elmeg account", + Html: ` + + + + + + + + + + + + + + + + +
+

{{app_name}}

+
+

Verify your email address

+

Hi {{user_name}},

+

Thanks for signing up for {{app_name}}. Please verify your email address by clicking the button below.

+ + + + +
+ Verify Email Address +
+

This link will expire after 24 hours for your security.

+

If you did not create an account, you can safely ignore this email.

+
+

If the button above doesn't work, copy and paste this URL: {{verification_link}}

+
+

{{app_name}} • Contact: {{support_email}}

+
+ +`, + Text: `Verify your Elmeg account + +Hi {{user_name}}, + +Thanks for signing up for {{app_name}}. Please verify your email address by clicking the link below: + +{{verification_link}} + +This link will expire after 24 hours for your security. + +If you did not create an account, you can safely ignore this email. + +--- +{{app_name}} • Contact: {{support_email}}`, + }, + }, + { + TemplateName: "ELMEG_PASSWORD_RESET", + TemplateContent: { + Subject: "Reset your Elmeg password", + Html: ` + + + + + + + + + + + + + + + + +
+

{{app_name}}

+
+

Reset your password

+

Hi {{user_name}},

+

We received a request to reset the password for your {{app_name}} account. Click the button below to choose a new password.

+ + + + +
+ Reset Password +
+

This link will expire after 1 hour for your security.

+

If you did not request a password reset, you can safely ignore this email.

+
+

If the button above doesn't work, copy and paste this URL: {{reset_link}}

+
+

{{app_name}} • Contact: {{support_email}}

+
+ +`, + Text: `Reset your Elmeg password + +Hi {{user_name}}, + +We received a request to reset the password for your {{app_name}} account. + +Click the link below to choose a new password: + +{{reset_link}} + +This link will expire after 1 hour for your security. + +If you did not request a password reset, you can safely ignore this email. + +--- +{{app_name}} • Contact: {{support_email}}`, + }, + }, + { + TemplateName: "ELMEG_SECURITY_ALERT", + TemplateContent: { + Subject: "Security alert for your Elmeg account", + Html: ` + + + + + + + + + + + + + + + + +
+

{{app_name}}

+
+ + + + +
+

⚠️ Security Notice

+
+

Account activity detected

+

Hi {{user_name}},

+

We detected the following activity on your {{app_name}} account:

+ + + + +
+

{{security_event_description}}

+
+

If this was you, no further action is needed.

+

If you did not perform this action, please secure your account immediately.

+
+

{{app_name}} • Contact: {{support_email}}

+
+ +`, + Text: `Security alert for your Elmeg account + +Hi {{user_name}}, + +We detected the following activity on your {{app_name}} account: + +{{security_event_description}} + +If this was you, no further action is needed. + +If you did not perform this action, please secure your account immediately. + +--- +{{app_name}} • Contact: {{support_email}}`, + }, + }, +]; + +async function deployTemplates() { + console.log("🚀 Deploying SES email templates...\n"); + + for (const template of templates) { + try { + // Try to create the template first + const createCommand = new CreateEmailTemplateCommand(template); + await sesClient.send(createCommand); + console.log(`✅ Created template: ${template.TemplateName}`); + } catch (error: unknown) { + const err = error as { name?: string }; + if (err.name === "AlreadyExistsException") { + // Template exists, update it + try { + const updateCommand = new UpdateEmailTemplateCommand(template); + await sesClient.send(updateCommand); + console.log(`🔄 Updated template: ${template.TemplateName}`); + } catch (updateError) { + console.error(`❌ Failed to update ${template.TemplateName}:`, updateError); + } + } else { + console.error(`❌ Failed to create ${template.TemplateName}:`, error); + } + } + } + + console.log("\n✅ Template deployment complete!"); +} + +deployTemplates().catch(console.error); diff --git a/email/src/email-service.ts b/email/src/email-service.ts new file mode 100644 index 0000000..51076a1 --- /dev/null +++ b/email/src/email-service.ts @@ -0,0 +1,209 @@ +/** + * Elmeg Email Service - AWS SES v2 Integration + * + * Transactional email layer for user-initiated emails only. + * Uses AWS SES stored templates for consistent, reliable delivery. + */ + +import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2"; + +// Configuration from environment variables +const config = { + region: process.env.AWS_SES_REGION || "us-east-1", + fromAddress: process.env.EMAIL_FROM || "noreply@elmeg.xyz", + appName: "Elmeg", + supportEmail: process.env.SUPPORT_EMAIL || "support@elmeg.xyz", + frontendUrl: process.env.FRONTEND_URL || "https://elmeg.xyz", +}; + +// SES Template Names +export const SES_TEMPLATES = { + EMAIL_VERIFICATION: "ELMEG_EMAIL_VERIFICATION", + PASSWORD_RESET: "ELMEG_PASSWORD_RESET", + SECURITY_ALERT: "ELMEG_SECURITY_ALERT", +} as const; + +// Initialize SES v2 client +const sesClient = new SESv2Client({ + region: config.region, + // Credentials are loaded automatically from env vars: + // AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY +}); + +// ============================================================================= +// Types +// ============================================================================= + +export interface SendVerificationEmailParams { + to: string; + userName: string; + verificationLink: string; +} + +export interface SendPasswordResetEmailParams { + to: string; + userName: string; + resetLink: string; +} + +export interface SendSecurityAlertEmailParams { + to: string; + userName: string; + securityEventDescription: string; +} + +export interface EmailResult { + success: boolean; + messageId?: string; + error?: { + code: string; + message: string; + }; +} + +export class EmailError extends Error { + code: string; + + constructor(code: string, message: string) { + super(message); + this.name = "EmailError"; + this.code = code; + } +} + +// ============================================================================= +// Email Sending Functions +// ============================================================================= + +/** + * Send email verification email to new users + */ +export async function sendVerificationEmail( + params: SendVerificationEmailParams +): Promise { + const templateData = { + user_name: params.userName, + verification_link: params.verificationLink, + app_name: config.appName, + support_email: config.supportEmail, + }; + + return sendTemplatedEmail( + params.to, + SES_TEMPLATES.EMAIL_VERIFICATION, + templateData + ); +} + +/** + * Send password reset email + */ +export async function sendPasswordResetEmail( + params: SendPasswordResetEmailParams +): Promise { + const templateData = { + user_name: params.userName, + reset_link: params.resetLink, + app_name: config.appName, + support_email: config.supportEmail, + }; + + return sendTemplatedEmail( + params.to, + SES_TEMPLATES.PASSWORD_RESET, + templateData + ); +} + +/** + * Send security alert email for account events + */ +export async function sendSecurityAlertEmail( + params: SendSecurityAlertEmailParams +): Promise { + const templateData = { + user_name: params.userName, + security_event_description: params.securityEventDescription, + app_name: config.appName, + support_email: config.supportEmail, + }; + + return sendTemplatedEmail( + params.to, + SES_TEMPLATES.SECURITY_ALERT, + templateData + ); +} + +// ============================================================================= +// Core Email Function +// ============================================================================= + +async function sendTemplatedEmail( + to: string, + templateName: string, + templateData: Record +): Promise { + try { + const command = new SendEmailCommand({ + FromEmailAddress: config.fromAddress, + Destination: { + ToAddresses: [to], + }, + Content: { + Template: { + TemplateName: templateName, + TemplateData: JSON.stringify(templateData), + }, + }, + }); + + const response = await sesClient.send(command); + + return { + success: true, + messageId: response.MessageId, + }; + } catch (error: unknown) { + const err = error as { name?: string; message?: string; Code?: string }; + + console.error(`[Email] Failed to send ${templateName} to ${to}:`, err.message); + + return { + success: false, + error: { + code: err.Code || err.name || "UNKNOWN_ERROR", + message: err.message || "Failed to send email", + }, + }; + } +} + +// ============================================================================= +// Utility Functions +// ============================================================================= + +/** + * Generate a verification link for a user + */ +export function generateVerificationLink(token: string): string { + return `${config.frontendUrl}/verify-email?token=${encodeURIComponent(token)}`; +} + +/** + * Generate a password reset link for a user + */ +export function generateResetLink(token: string): string { + return `${config.frontendUrl}/reset-password?token=${encodeURIComponent(token)}`; +} + +/** + * Check if the email service is properly configured + */ +export function isEmailConfigured(): boolean { + return !!( + process.env.AWS_ACCESS_KEY_ID && + process.env.AWS_SECRET_ACCESS_KEY && + process.env.AWS_SES_REGION + ); +} diff --git a/email/src/examples.ts b/email/src/examples.ts new file mode 100644 index 0000000..38c6682 --- /dev/null +++ b/email/src/examples.ts @@ -0,0 +1,176 @@ +/** + * Elmeg Email Service - Usage Examples + * + * These examples show how to integrate the email service + * into your application's user flows. + */ + +import { + sendVerificationEmail, + sendPasswordResetEmail, + sendSecurityAlertEmail, + generateVerificationLink, + generateResetLink, + isEmailConfigured, +} from "./email-service"; + +// ============================================================================= +// Example 1: User Registration Flow +// ============================================================================= + +async function handleUserRegistration( + userEmail: string, + userName: string, + verificationToken: string +) { + // Check if email is configured + if (!isEmailConfigured()) { + console.warn("[Email] Email service not configured, skipping verification email"); + return; + } + + // Generate the verification link + const verificationLink = generateVerificationLink(verificationToken); + + // Send the verification email + const result = await sendVerificationEmail({ + to: userEmail, + userName: userName || userEmail.split("@")[0], + verificationLink, + }); + + if (result.success) { + console.log(`[Email] Verification email sent to ${userEmail}, messageId: ${result.messageId}`); + } else { + console.error(`[Email] Failed to send verification email: ${result.error?.message}`); + // Handle error - maybe retry or alert admin + } +} + +// ============================================================================= +// Example 2: Forgot Password Flow +// ============================================================================= + +async function handleForgotPassword( + userEmail: string, + userName: string, + resetToken: string +) { + if (!isEmailConfigured()) { + console.warn("[Email] Email service not configured, skipping password reset email"); + return; + } + + const resetLink = generateResetLink(resetToken); + + const result = await sendPasswordResetEmail({ + to: userEmail, + userName: userName || userEmail.split("@")[0], + resetLink, + }); + + if (result.success) { + console.log(`[Email] Password reset email sent to ${userEmail}`); + } else { + console.error(`[Email] Failed to send password reset email: ${result.error?.message}`); + } +} + +// ============================================================================= +// Example 3: Security Alert - New Login +// ============================================================================= + +async function handleNewLogin( + userEmail: string, + userName: string, + loginDetails: { ip: string; browser: string; location?: string; timestamp: Date } +) { + if (!isEmailConfigured()) { + return; + } + + const eventDescription = [ + `New sign-in to your account`, + ``, + `Time: ${loginDetails.timestamp.toLocaleString()}`, + `IP Address: ${loginDetails.ip}`, + `Browser: ${loginDetails.browser}`, + loginDetails.location ? `Location: ${loginDetails.location}` : null, + ] + .filter(Boolean) + .join("\n"); + + const result = await sendSecurityAlertEmail({ + to: userEmail, + userName: userName || userEmail.split("@")[0], + securityEventDescription: eventDescription, + }); + + if (!result.success) { + console.error(`[Email] Failed to send security alert: ${result.error?.message}`); + } +} + +// ============================================================================= +// Example 4: Security Alert - Password Changed +// ============================================================================= + +async function handlePasswordChanged( + userEmail: string, + userName: string, + timestamp: Date +) { + if (!isEmailConfigured()) { + return; + } + + const eventDescription = `Your password was changed on ${timestamp.toLocaleString()}. If you did not make this change, please contact support immediately.`; + + await sendSecurityAlertEmail({ + to: userEmail, + userName: userName || userEmail.split("@")[0], + securityEventDescription: eventDescription, + }); +} + +// ============================================================================= +// Example 5: Express.js Route Handler Integration +// ============================================================================= + +/* +import express from "express"; +import { sendVerificationEmail, generateVerificationLink } from "./email-service"; + +const router = express.Router(); + +router.post("/register", async (req, res) => { + const { email, password, name } = req.body; + + // ... create user in database ... + const user = await createUser({ email, password, name }); + + // Generate verification token + const verificationToken = generateSecureToken(); + await saveVerificationToken(user.id, verificationToken); + + // Send verification email + const verificationLink = generateVerificationLink(verificationToken); + + const emailResult = await sendVerificationEmail({ + to: email, + userName: name || email.split("@")[0], + verificationLink, + }); + + if (!emailResult.success) { + console.error("Failed to send verification email:", emailResult.error); + // Don't fail registration, just log the error + } + + res.status(201).json({ + message: "Account created. Please check your email to verify your account." + }); +}); + +export default router; +*/ diff --git a/email/templates/README.md b/email/templates/README.md new file mode 100644 index 0000000..108f7bc --- /dev/null +++ b/email/templates/README.md @@ -0,0 +1,306 @@ +# AWS SES Email Templates for Elmeg + +## Template 1: Email Verification + +**Template Name:** `ELMEG_EMAIL_VERIFICATION` + +### Subject + +``` +Verify your Elmeg account +``` + +### HTML Body + +```html + + + + + + Verify your email + + + + + + + + + + + + +
+

{{app_name}}

+
+

Verify your email address

+

+ Hi {{user_name}}, +

+

+ Thanks for signing up for {{app_name}}. Please verify your email address by clicking the button below. +

+ + + + +
+ Verify Email Address +
+

+ This link will expire after 24 hours for your security. +

+

+ If you did not create an account, you can safely ignore this email. +

+
+

+ If the button above doesn't work, copy and paste this URL into your browser: +

+

+ {{verification_link}} +

+
+

+ {{app_name}} – The Goose Community Archive +

+

+ Questions? Contact us at {{support_email}} +

+
+ + +``` + +### Plain Text Body + +``` +Verify your Elmeg account + +Hi {{user_name}}, + +Thanks for signing up for {{app_name}}. Please verify your email address by clicking the link below: + +{{verification_link}} + +This link will expire after 24 hours for your security. + +If you did not create an account, you can safely ignore this email. + +--- +{{app_name}} – The Goose Community Archive +Questions? Contact us at {{support_email}} +``` + +--- + +## Template 2: Password Reset + +**Template Name:** `ELMEG_PASSWORD_RESET` + +### Subject + +``` +Reset your Elmeg password +``` + +### HTML Body + +```html + + + + + + Reset your password + + + + + + + + + + + + +
+

{{app_name}}

+
+

Reset your password

+

+ Hi {{user_name}}, +

+

+ We received a request to reset the password for your {{app_name}} account. Click the button below to choose a new password. +

+ + + + +
+ Reset Password +
+

+ This link will expire after 1 hour for your security. +

+

+ If you did not request a password reset, you can safely ignore this email. Your password will remain unchanged. +

+
+

+ If the button above doesn't work, copy and paste this URL into your browser: +

+

+ {{reset_link}} +

+
+

+ {{app_name}} – The Goose Community Archive +

+

+ Questions? Contact us at {{support_email}} +

+
+ + +``` + +### Plain Text Body + +``` +Reset your Elmeg password + +Hi {{user_name}}, + +We received a request to reset the password for your {{app_name}} account. + +Click the link below to choose a new password: + +{{reset_link}} + +This link will expire after 1 hour for your security. + +If you did not request a password reset, you can safely ignore this email. Your password will remain unchanged. + +--- +{{app_name}} – The Goose Community Archive +Questions? Contact us at {{support_email}} +``` + +--- + +## Template 3: Security Alert + +**Template Name:** `ELMEG_SECURITY_ALERT` + +### Subject + +``` +Security alert for your Elmeg account +``` + +### HTML Body + +```html + + + + + + Security Alert + + + + + + + + + + + + +
+

{{app_name}}

+
+ + + + +
+

⚠️ Security Notice

+
+

Account activity detected

+

+ Hi {{user_name}}, +

+

+ We detected the following activity on your {{app_name}} account: +

+ + + + +
+

+ {{security_event_description}} +

+
+

+ If this was you, no further action is needed. +

+

+ If you did not perform this action, we recommend you secure your account immediately by changing your password. +

+
+

+ This is an automated security notification. If you have concerns about your account security, please contact us at {{support_email}}. +

+
+

+ {{app_name}} – The Goose Community Archive +

+

+ Questions? Contact us at {{support_email}} +

+
+ + +``` + +### Plain Text Body + +``` +Security alert for your Elmeg account + +Hi {{user_name}}, + +We detected the following activity on your {{app_name}} account: + +{{security_event_description}} + +If this was you, no further action is needed. + +If you did not perform this action, we recommend you secure your account immediately by changing your password. + +--- +This is an automated security notification. If you have concerns about your account security, please contact us at {{support_email}}. + +{{app_name}} – The Goose Community Archive +``` + +--- + +## Template Placeholders Reference + +| Placeholder | Description | Used In | +|-------------|-------------|---------| +| `{{app_name}}` | Application name ("Elmeg") | All templates | +| `{{user_name}}` | User's display name or email | All templates | +| `{{support_email}}` | Support contact email | All templates | +| `{{verification_link}}` | Email verification URL | Email Verification | +| `{{reset_link}}` | Password reset URL | Password Reset | +| `{{security_event_description}}` | Description of the security event | Security Alert | diff --git a/email/tsconfig.json b/email/tsconfig.json new file mode 100644 index 0000000..3fb3621 --- /dev/null +++ b/email/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": [ + "ES2022" + ], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file