fediversion/email/src/email-service.ts
fullsizemalt b4cddf41ea feat: Initialize Fediversion multi-band platform
- Fork elmeg-demo codebase for multi-band support
- Add data importer infrastructure with base class
- Create band-specific importers:
  - phish.py: Phish.net API v5
  - grateful_dead.py: Grateful Stats API
  - setlistfm.py: Dead & Company, Billy Strings (Setlist.fm)
- Add spec-kit configuration for Gemini
- Update README with supported bands and architecture
2025-12-28 12:39:28 -08:00

209 lines
5.6 KiB
TypeScript

/**
* 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<EmailResult> {
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<EmailResult> {
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<EmailResult> {
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<string, string>
): Promise<EmailResult> {
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
);
}