pls
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled

This commit is contained in:
Atridad Lahiji 2025-05-26 14:48:41 -06:00
parent f87c888a96
commit 8d75a36a64
Signed by: atridad
SSH Key Fingerprint: SHA256:LGomp8Opq0jz+7kbwNcdfTcuaLRb5Nh0k5AchDDb438
5 changed files with 809 additions and 817 deletions

View File

@ -1,4 +1,13 @@
JMAP_ACCOUNT_ID=""
JMAP_ACCESS_TOKEN=""
FROM_EMAIL=""
TO_EMAIL=""
# SMTP Configuration
SMTP_HOST=smtp.site.com
SMTP_PORT=587
SMTP_USER=email@site.com
SMTP_PASSWORD=your-app-password
# Email Configuration
FROM_EMAIL=email@site.com
TO_EMAIL=email@site.com
# Application Configuration
NODE_ENV=production
APP_PORT=4321

View File

@ -5,8 +5,10 @@ services:
- "${APP_PORT}:4321"
environment:
NODE_ENV: production
JMAP_ACCESS_TOKEN: ${JMAP_ACCESS_TOKEN}
JMAP_ACCOUNT_ID: ${JMAP_ACCOUNT_ID}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
FROM_EMAIL: ${FROM_EMAIL}
TO_EMAIL: ${TO_EMAIL}
restart: unless-stopped

View File

@ -1,5 +1,5 @@
{
"name": "web",
"name": "atashdotdev",
"type": "module",
"version": "0.0.1",
"scripts": {
@ -9,15 +9,17 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/node": "^9.0.2",
"@astrojs/solid-js": "^5.0.4",
"@astrojs/tailwind": "^5.1.5",
"astro": "^5.1.8",
"@astrojs/node": "^9.2.2",
"@astrojs/solid-js": "^5.1.0",
"@astrojs/tailwind": "^6.0.2",
"astro": "^5.8.0",
"nodemailer": "^6.9.8",
"solid-js": "^1.9.4",
"tailwindcss": "^3.0.24"
},
"devDependencies": {
"@types/node": "^22.10.10",
"@types/nodemailer": "^6.4.14",
"daisyui": "^4.12.23"
}
}

1367
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,216 +1,64 @@
import type { APIRoute } from "astro";
import nodemailer from "nodemailer";
export const prerender = false;
const sendEmailViaJMAP = async ({
const sendEmailViaSMTP = async ({
subject,
message,
}: {
subject: string;
message: string;
}) => {
const accessToken = process.env.JMAP_ACCESS_TOKEN
? process.env.JMAP_ACCESS_TOKEN
: import.meta.env.JMAP_ACCESS_TOKEN;
const accountId = process.env.JMAP_ACCOUNT_ID
? process.env.JMAP_ACCOUNT_ID
: import.meta.env.JMAP_ACCOUNT_ID;
// Get environment variables
const smtpHost = process.env.SMTP_HOST
? process.env.SMTP_HOST
: import.meta.env.SMTP_HOST;
const smtpPort = process.env.SMTP_PORT
? parseInt(process.env.SMTP_PORT)
: parseInt(import.meta.env.SMTP_PORT || "587");
const smtpUser = process.env.SMTP_USER
? process.env.SMTP_USER
: import.meta.env.SMTP_USER;
const smtpPassword = process.env.SMTP_PASSWORD
? process.env.SMTP_PASSWORD
: import.meta.env.SMTP_PASSWORD;
const fromEmail = process.env.FROM_EMAIL
? process.env.FROM_EMAIL
: import.meta.env.FROM_EMAIL;
const toEmail = process.env.TO_EMAIL
? process.env.TO_EMAIL
: import.meta.env.TO_EMAIL;
const apiUrl = "https://api.fastmail.com/jmap/api/";
if (!accessToken || !accountId || !fromEmail || !toEmail) {
throw new Error("Missing environment variables configuration");
if (!smtpHost || !smtpUser || !smtpPassword || !fromEmail || !toEmail) {
throw new Error("Missing SMTP configuration environment variables");
}
const headers = new Headers({
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
Accept: "application/json",
const transporter = nodemailer.createTransport({
host: smtpHost,
port: smtpPort,
secure: smtpPort === 465,
auth: {
user: smtpUser,
pass: smtpPassword,
},
});
try {
// Get email identities
const identityRequest = {
using: ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
methodCalls: [
[
"Identity/get",
{
accountId,
properties: ["id", "email", "name"],
},
"0",
],
],
};
await transporter.verify();
const identityResponse = await fetch(apiUrl, {
method: "POST",
headers,
body: JSON.stringify(identityRequest),
const info = await transporter.sendMail({
from: fromEmail,
to: toEmail,
subject: subject,
text: message,
html: `<pre>${message}</pre>`,
});
const identityData = await identityResponse.json();
const identity = identityData.methodResponses[0][1].list.find(
(id: any) => id.email === fromEmail,
);
if (!identity) {
throw new Error(`No identity found for email: ${fromEmail}`);
}
// Get drafts mailbox
const mailboxRequest = {
using: ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
methodCalls: [
[
"Mailbox/get",
{
accountId,
properties: ["id", "role"],
},
"0",
],
],
};
const mailboxResponse = await fetch(apiUrl, {
method: "POST",
headers,
body: JSON.stringify(mailboxRequest),
});
const mailboxData = await mailboxResponse.json();
const drafts = mailboxData.methodResponses[0][1].list.find(
(box: any) => box.role === "drafts",
);
if (!drafts) {
throw new Error("Could not find Drafts folder");
}
// Create email draft
const emailRequest = {
using: [
"urn:ietf:params:jmap:core",
"urn:ietf:params:jmap:mail",
"urn:ietf:params:jmap:submission",
],
methodCalls: [
[
"Email/set",
{
accountId,
create: {
"draft-1": {
mailboxIds: { [drafts.id]: true },
from: [{ email: fromEmail, name: identity.name }],
to: [{ email: toEmail }],
subject,
bodyValues: {
body1: {
value: message,
charset: "utf-8",
},
},
textBody: [
{
partId: "body1",
type: "text/plain",
},
],
},
},
},
"0",
],
],
};
const emailResponse = await fetch(apiUrl, {
method: "POST",
headers,
body: JSON.stringify(emailRequest),
});
const emailResult = await emailResponse.json();
const createdId = emailResult.methodResponses[0][1].created["draft-1"].id;
// Submit email with identity
const submitRequest = {
using: [
"urn:ietf:params:jmap:core",
"urn:ietf:params:jmap:mail",
"urn:ietf:params:jmap:submission",
],
methodCalls: [
[
"EmailSubmission/set",
{
accountId,
create: {
"submit-1": {
emailId: createdId,
identityId: identity.id,
envelope: {
mailFrom: {
email: fromEmail,
},
rcptTo: [{ email: toEmail }],
},
},
},
},
"0",
],
],
};
const submitResponse = await fetch(apiUrl, {
method: "POST",
headers,
body: JSON.stringify(submitRequest),
});
const submitResult = await submitResponse.json();
if (!submitResult.methodResponses[0][1].created?.["submit-1"]) {
throw new Error(`Submission failed: ${JSON.stringify(submitResult)}`);
}
const deleteRequest = {
using: ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
methodCalls: [
[
"Email/set",
{
accountId,
destroy: [createdId],
},
"0",
],
],
};
const deleteResponse = await fetch(apiUrl, {
method: "POST",
headers,
body: JSON.stringify(deleteRequest),
});
const deleteResult = await deleteResponse.json();
if (!deleteResult.methodResponses[0][1].destroyed?.includes(createdId)) {
console.warn("Failed to delete draft email", deleteResult);
}
return { success: true };
console.log("Message sent: %s", info.messageId);
return { success: true, messageId: info.messageId };
} catch (error) {
console.error("JMAP Error:", error);
console.error("SMTP Error:", error);
throw error;
}
};
@ -230,7 +78,7 @@ export const POST: APIRoute = async ({ request }) => {
);
}
await sendEmailViaJMAP({ subject, message });
await sendEmailViaSMTP({ subject, message });
return new Response(
JSON.stringify({ success: true, message: "Email sent successfully" }),