aboutsummaryrefslogtreecommitdiff
path: root/services/docker/mail-server/aws-sendmail/aws
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2025-04-30 00:20:23 +0800
committerYuqian Yang <crupest@crupest.life>2025-05-04 19:15:31 +0800
commit690f9f843324c359a46bcb8b51b5563e2bec800a (patch)
treefb17ed2aac5cbd5335b5e819ee9b1a8786c0d6de /services/docker/mail-server/aws-sendmail/aws
parentee338238805953b505459eac09aa07610865ebf6 (diff)
downloadcrupest-690f9f843324c359a46bcb8b51b5563e2bec800a.tar.gz
crupest-690f9f843324c359a46bcb8b51b5563e2bec800a.tar.bz2
crupest-690f9f843324c359a46bcb8b51b5563e2bec800a.zip
HALF WORK!: 2025-5-4
Diffstat (limited to 'services/docker/mail-server/aws-sendmail/aws')
-rw-r--r--services/docker/mail-server/aws-sendmail/aws/base.ts15
-rw-r--r--services/docker/mail-server/aws-sendmail/aws/deliver.ts42
-rw-r--r--services/docker/mail-server/aws-sendmail/aws/retriver.ts89
3 files changed, 146 insertions, 0 deletions
diff --git a/services/docker/mail-server/aws-sendmail/aws/base.ts b/services/docker/mail-server/aws-sendmail/aws/base.ts
new file mode 100644
index 0000000..1e23009
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/aws/base.ts
@@ -0,0 +1,15 @@
+import { getEnvRequired } from "../base.ts";
+
+export class AwsContext {
+ readonly region = "ap-southeast-1";
+
+ accessKeyId = getEnvRequired("AWS_USER", "aws access key id");
+ secretAccessKey = getEnvRequired("AWS_PASSWORD", "aws secret access key");
+
+ getCredentials() {
+ const { accessKeyId, secretAccessKey } = this;
+ return Promise.resolve({ accessKeyId, secretAccessKey });
+ }
+
+ readonly credentials = this.getCredentials.bind(this);
+}
diff --git a/services/docker/mail-server/aws-sendmail/aws/deliver.ts b/services/docker/mail-server/aws-sendmail/aws/deliver.ts
new file mode 100644
index 0000000..bdc9584
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/aws/deliver.ts
@@ -0,0 +1,42 @@
+import { SendEmailCommand, SESv2Client } from "@aws-sdk/client-sesv2";
+
+import { AwsContext } from "./base.ts";
+import { Mail, MailDeliverer } from "../mail.ts";
+
+export class AwsMailDeliverer extends MailDeliverer {
+ private _ses;
+
+ constructor(readonly aws: AwsContext) {
+ super("aws");
+
+ const { region, credentials } = aws;
+
+ this._ses = new SESv2Client({ region, credentials });
+ }
+
+ protected override async doDeliver(mail: Mail): Promise<void> {
+ let awsMessageId: string | undefined;
+
+ try {
+ const sendCommand = new SendEmailCommand({
+ Content: {
+ Raw: { Data: mail.encodeUtf8() },
+ },
+ });
+
+ const res = await this._ses.send(sendCommand);
+ awsMessageId = res.MessageId;
+ } catch (cause) {
+ mail.throwDeliverError(
+ this,
+ "failed to call send-email api of aws.",
+ cause,
+ );
+ }
+
+ if (awsMessageId == null) {
+ mail.setDelivered(this, new Error("No message id is returned from aws."));
+ }
+ mail.awsMessageId = awsMessageId ?? null;
+ }
+}
diff --git a/services/docker/mail-server/aws-sendmail/aws/retriver.ts b/services/docker/mail-server/aws-sendmail/aws/retriver.ts
new file mode 100644
index 0000000..cdfe6f1
--- /dev/null
+++ b/services/docker/mail-server/aws-sendmail/aws/retriver.ts
@@ -0,0 +1,89 @@
+/// <reference types="npm:@types/node" />
+
+import {
+ GetObjectCommand,
+ ListObjectsV2Command,
+ S3Client,
+} from "@aws-sdk/client-s3";
+
+import { generateTimeStringForFileName, getEnvRequired } from "../base.ts";
+import { getLogger } from "../logger.ts";
+import { AwsContext } from "./base.ts";
+import { MailDeliverer } from "../mail.ts";
+
+export class AwsMailRetriever {
+ mailBucket = getEnvRequired(
+ "AWS_MAIL_BUCKET",
+ "aws s3 bucket saving raw mails",
+ );
+ liveMailPrefix = "mail/live/";
+ archiveMailPrefix = "mail/archive/";
+
+ private s3Client;
+ private liveMailRecyclerAborter = new AbortController();
+
+ constructor(private aws: AwsContext, private localDeliverer: MailDeliverer) {
+ const { region, credentials } = aws;
+ this.s3Client = new S3Client({ region, credentials });
+ }
+
+ setupLiveMailRecycler() {
+ Deno.cron("live-mail-recycler", "0 */6 * * *", {
+ signal: this.liveMailRecyclerAborter.signal,
+ }, () => {
+ });
+ }
+
+ generateArchivePrefix(instant: Date | Temporal.Instant): string {
+ return `${this.archiveMailPrefix}${
+ generateTimeStringForFileName(instant, true)
+ }/`;
+ }
+
+ async listLiveMails(): Promise<string[]> {
+ const listCommand = new ListObjectsV2Command({
+ Bucket: this.mailBucket,
+ Prefix: this.liveMailPrefix,
+ });
+ const res = await this.s3Client.send(listCommand);
+
+ if (res.Contents == null) {
+ getLogger().warn("Listing live mails in S3 returns null Content.");
+ return [];
+ }
+
+ const result: string[] = [];
+ for (const object of res.Contents) {
+ if (object.Key != null) {
+ result.push(object.Key);
+ } else {
+ getLogger().warn(
+ "Listing live mails in S3 returns an object with no Key.",
+ );
+ }
+ }
+ return result;
+ }
+
+ async deliverS3MailObject(messageId: string) {
+ const command = new GetObjectCommand({
+ Bucket: this.mailBucket,
+ Key: `${this.liveMailPrefix}${messageId}`,
+ });
+ const res = await this.s3Client.send(command);
+
+ if (res.Body == null) {
+ // TODO: Better error.
+ throw new Error();
+ }
+
+ const rawMail = await res.Body.transformToString();
+ await this.localDeliverer.deliverRaw(rawMail);
+
+ const archiveCommand = new
+ }
+
+ async recycleLiveMails() {
+ const mails = await this.listLiveMails();
+ }
+}