alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
103 lines (88 loc) • 3.36 kB
text/typescript
import { $atom, $hook, $inject, $state, type Static, z } from "alepha";
import { $logger } from "alepha/logger";
import { FileSystemProvider } from "alepha/system";
import { EmailError } from "../errors/EmailError.ts";
import type { EmailProvider, EmailSendOptions } from "./EmailProvider.ts";
// ---------------------------------------------------------------------------------------------------------------------
/**
* Local email provider configuration atom
*/
export const localEmailOptions = $atom({
name: "alepha.email.local.options",
schema: z.object({
directory: z
.string()
.describe("Directory path where email files will be stored"),
}),
default: {
directory: "node_modules/.alepha/emails",
},
});
export type LocalEmailProviderOptions = Static<typeof localEmailOptions.schema>;
declare module "alepha" {
interface State {
[localEmailOptions.key]: LocalEmailProviderOptions;
}
}
// ---------------------------------------------------------------------------------------------------------------------
export class LocalEmailProvider implements EmailProvider {
protected readonly log = $logger();
protected readonly fs = $inject(FileSystemProvider);
protected readonly options = $state(localEmailOptions);
protected get directory(): string {
return this.options.directory;
}
protected onStart = $hook({
on: "start",
handler: async () => {
try {
await this.fs.mkdir(this.directory, { recursive: true });
this.log.info("Email directory OK", {
at: this.directory,
});
} catch (error) {
const message = `Failed to create email directory: ${error instanceof Error ? error.message : String(error)}`;
this.log.error(message, { at: this.directory });
throw new EmailError(
message,
error instanceof Error ? error : undefined,
);
}
},
});
public async send(options: EmailSendOptions): Promise<void> {
const { to, subject, body } = options;
this.log.debug("Sending email to local file", {
to,
subject,
directory: this.directory,
});
try {
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
for (const recipient of Array.isArray(to) ? to : [to]) {
const sanitizedEmail = recipient.replace(/[^a-zA-Z0-9@.-]/g, "_");
const filename = `${sanitizedEmail},${timestamp}.eml.json`;
const filepath = this.fs.join(this.directory, filename);
const content = this.createEmailJson({ to: recipient, subject, body });
await this.fs.writeFile(filepath, JSON.stringify(content, null, 2));
this.log.info("Email saved to local file", { filepath, to, subject });
}
} catch (error) {
const message = `Failed to save email to local file: ${error instanceof Error ? error.message : String(error)}`;
this.log.error(message, { to, subject, directory: this.directory });
throw new EmailError(message, error instanceof Error ? error : undefined);
}
}
public createEmailJson(options: {
to: string;
subject: string;
body: string;
}): { to: string; subject: string; body: string; sentAt: string } {
return {
to: options.to,
subject: options.subject,
body: options.body,
sentAt: new Date().toISOString(),
};
}
}