elmeg-demo/email/scripts/deploy-templates.ts
fullsizemalt 530f217445 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
2025-12-21 16:04:59 -08:00

234 lines
10 KiB
TypeScript

/**
* 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: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width: 600px; margin: 0 auto; background-color: #ffffff;">
<tr>
<td style="padding: 40px 30px; text-align: center; background-color: #1a1a2e;">
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 700;">{{app_name}}</h1>
</td>
</tr>
<tr>
<td style="padding: 40px 30px;">
<h2 style="margin: 0 0 20px; color: #1a1a2e; font-size: 22px; font-weight: 600;">Verify your email address</h2>
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.5;">Hi {{user_name}},</p>
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.5;">Thanks for signing up for {{app_name}}. Please verify your email address by clicking the button below.</p>
<table role="presentation" cellspacing="0" cellpadding="0" style="margin: 30px 0;">
<tr>
<td style="background-color: #4f46e5; border-radius: 6px;">
<a href="{{verification_link}}" style="display: inline-block; padding: 14px 32px; color: #ffffff; text-decoration: none; font-size: 16px; font-weight: 600;">Verify Email Address</a>
</td>
</tr>
</table>
<p style="margin: 0 0 20px; color: #666666; font-size: 14px; line-height: 1.5;">This link will expire after 24 hours for your security.</p>
<p style="margin: 0 0 20px; color: #666666; font-size: 14px; line-height: 1.5;">If you did not create an account, you can safely ignore this email.</p>
<hr style="border: none; border-top: 1px solid #eeeeee; margin: 30px 0;">
<p style="margin: 0; color: #999999; font-size: 12px;">If the button above doesn't work, copy and paste this URL: {{verification_link}}</p>
</td>
</tr>
<tr>
<td style="padding: 30px; background-color: #f9fafb; text-align: center;">
<p style="margin: 0; color: #999999; font-size: 12px;">{{app_name}} • Contact: {{support_email}}</p>
</td>
</tr>
</table>
</body>
</html>`,
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: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width: 600px; margin: 0 auto; background-color: #ffffff;">
<tr>
<td style="padding: 40px 30px; text-align: center; background-color: #1a1a2e;">
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 700;">{{app_name}}</h1>
</td>
</tr>
<tr>
<td style="padding: 40px 30px;">
<h2 style="margin: 0 0 20px; color: #1a1a2e; font-size: 22px; font-weight: 600;">Reset your password</h2>
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.5;">Hi {{user_name}},</p>
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.5;">We received a request to reset the password for your {{app_name}} account. Click the button below to choose a new password.</p>
<table role="presentation" cellspacing="0" cellpadding="0" style="margin: 30px 0;">
<tr>
<td style="background-color: #4f46e5; border-radius: 6px;">
<a href="{{reset_link}}" style="display: inline-block; padding: 14px 32px; color: #ffffff; text-decoration: none; font-size: 16px; font-weight: 600;">Reset Password</a>
</td>
</tr>
</table>
<p style="margin: 0 0 20px; color: #666666; font-size: 14px; line-height: 1.5;">This link will expire after 1 hour for your security.</p>
<p style="margin: 0 0 20px; color: #666666; font-size: 14px; line-height: 1.5;">If you did not request a password reset, you can safely ignore this email.</p>
<hr style="border: none; border-top: 1px solid #eeeeee; margin: 30px 0;">
<p style="margin: 0; color: #999999; font-size: 12px;">If the button above doesn't work, copy and paste this URL: {{reset_link}}</p>
</td>
</tr>
<tr>
<td style="padding: 30px; background-color: #f9fafb; text-align: center;">
<p style="margin: 0; color: #999999; font-size: 12px;">{{app_name}} • Contact: {{support_email}}</p>
</td>
</tr>
</table>
</body>
</html>`,
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: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="max-width: 600px; margin: 0 auto; background-color: #ffffff;">
<tr>
<td style="padding: 40px 30px; text-align: center; background-color: #1a1a2e;">
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 700;">{{app_name}}</h1>
</td>
</tr>
<tr>
<td style="padding: 40px 30px;">
<table role="presentation" cellspacing="0" cellpadding="0" style="margin-bottom: 20px;">
<tr>
<td style="background-color: #fef3c7; border-radius: 6px; padding: 12px 16px;">
<p style="margin: 0; color: #92400e; font-size: 14px; font-weight: 600;">⚠️ Security Notice</p>
</td>
</tr>
</table>
<h2 style="margin: 0 0 20px; color: #1a1a2e; font-size: 22px; font-weight: 600;">Account activity detected</h2>
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.5;">Hi {{user_name}},</p>
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.5;">We detected the following activity on your {{app_name}} account:</p>
<table role="presentation" cellspacing="0" cellpadding="0" style="margin: 20px 0; width: 100%;">
<tr>
<td style="background-color: #f3f4f6; border-radius: 6px; padding: 16px;">
<p style="margin: 0; color: #374151; font-size: 15px; line-height: 1.5;">{{security_event_description}}</p>
</td>
</tr>
</table>
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.5;">If this was you, no further action is needed.</p>
<p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.5;">If you did not perform this action, please secure your account immediately.</p>
</td>
</tr>
<tr>
<td style="padding: 30px; background-color: #f9fafb; text-align: center;">
<p style="margin: 0; color: #999999; font-size: 12px;">{{app_name}} • Contact: {{support_email}}</p>
</td>
</tr>
</table>
</body>
</html>`,
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);