The problem:
At my company Conio, we have a custom job application page in our site. This page allow users to submit a regular application. In the form we ask also CV and Cover letter. Since the site is a Next JS project, the form will call a POST api which is handled by Next Routing. If you want to send a simple html/text email, code is so simple, but if you want to add files, you need to build a raw template and it is very complicated when you need to mix --NextPart
with Content-Transfer-Encoding
and Content-Disposition
.
The solution (my solution):
Since I have multiple place in my company site where I need to build emails (not only job application page), I decided to develop an utility class which give me appendFile()
method to call sequentially so I can add more than 1 file.
export class RawMessageDataBuilder {
private msg = "";
constructor({
to,
fullname,
}: {
fullname: string;
to: string[];
}) {
this.msg = `From: <noreply@yourdomain.com>
To: ${to.join(",")}
Subject: Job Application - ${fullname}
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="NextPart"
--NextPart
Content-Type: text/html; charset=us-ascii
<body>
<p>Thank for application ${fullname}</p>
</body>
`;
}
appendFile(base64String: string, fileName: string, mimeType: string) {
this.msg += `--NextPart
Content-Type: ${mimeType};
Content-Disposition: attachment; filename="${fileName}"
Content-Transfer-Encoding: base64
${base64String}
`;
}
build() {
if (!this.msg.endsWith("--NextPart--")) {
this.msg += `--NextPart--`;
}
return this.msg;
}
}
Before I described scenario where we have a page with form and user can add files. How to handle files in form with Next.JS api is out of scope, because it add complexity. The following code show a more simple use case where maybe the company send a job_details.pdf file to user.
import { SendRawEmailCommand, SendRawEmailCommandInput, SESClient } from "@aws-sdk/client-ses";
import { RawMessageDataBuilder } from "components/jobs/utils";
import { readFileSync } from "fs";
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const jobDetailPdf = readFileSync("job_details.pdf"); // return a buffer
const fileBase64Cv = jobDetailPdf.toString("base64");
const builder = new RawMessageDataBuilder({
fullname: "Mario Rossi",
to: ["mario.rossi@gmail.com"],
});
builder.appendFile(fileBase64Cv, "job_details.pdf", "application/pdf");
const params: SendRawEmailCommandInput = {
RawMessage: {
Data: Buffer.from(builder.build(), "utf8"),
},
Source: "noreply@yourdomain.com" /* required */,
};
const command = new SendRawEmailCommand(params);
const sesClient = new SESClient({
region: "eu-west-1",
credentials: {
accessKeyId: "xxx",
secretAccessKey: "yyy",
},
});
try {
await sesClient.send(command);
return new Response("Success", {
status: 200,
});
} catch {
return NextResponse.json({ error: "error sending email" }, { status: 400 });
}
}
Line 17:18 show how to use SESClient
to send a raw email.