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
234 lines
10 KiB
TypeScript
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);
|