This commit is contained in:
parent
f87c888a96
commit
8d75a36a64
17
.env.example
17
.env.example
@ -1,4 +1,13 @@
|
|||||||
JMAP_ACCOUNT_ID=""
|
# SMTP Configuration
|
||||||
JMAP_ACCESS_TOKEN=""
|
SMTP_HOST=smtp.site.com
|
||||||
FROM_EMAIL=""
|
SMTP_PORT=587
|
||||||
TO_EMAIL=""
|
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
|
@ -5,8 +5,10 @@ services:
|
|||||||
- "${APP_PORT}:4321"
|
- "${APP_PORT}:4321"
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
JMAP_ACCESS_TOKEN: ${JMAP_ACCESS_TOKEN}
|
SMTP_HOST: ${SMTP_HOST}
|
||||||
JMAP_ACCOUNT_ID: ${JMAP_ACCOUNT_ID}
|
SMTP_PORT: ${SMTP_PORT}
|
||||||
|
SMTP_USER: ${SMTP_USER}
|
||||||
|
SMTP_PASSWORD: ${SMTP_PASSWORD}
|
||||||
FROM_EMAIL: ${FROM_EMAIL}
|
FROM_EMAIL: ${FROM_EMAIL}
|
||||||
TO_EMAIL: ${TO_EMAIL}
|
TO_EMAIL: ${TO_EMAIL}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
12
package.json
12
package.json
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "web",
|
"name": "atashdotdev",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -9,15 +9,17 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/node": "^9.0.2",
|
"@astrojs/node": "^9.2.2",
|
||||||
"@astrojs/solid-js": "^5.0.4",
|
"@astrojs/solid-js": "^5.1.0",
|
||||||
"@astrojs/tailwind": "^5.1.5",
|
"@astrojs/tailwind": "^6.0.2",
|
||||||
"astro": "^5.1.8",
|
"astro": "^5.8.0",
|
||||||
|
"nodemailer": "^6.9.8",
|
||||||
"solid-js": "^1.9.4",
|
"solid-js": "^1.9.4",
|
||||||
"tailwindcss": "^3.0.24"
|
"tailwindcss": "^3.0.24"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.10.10",
|
"@types/node": "^22.10.10",
|
||||||
|
"@types/nodemailer": "^6.4.14",
|
||||||
"daisyui": "^4.12.23"
|
"daisyui": "^4.12.23"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1367
pnpm-lock.yaml
generated
1367
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,216 +1,64 @@
|
|||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from "astro";
|
||||||
|
import nodemailer from "nodemailer";
|
||||||
|
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
const sendEmailViaJMAP = async ({
|
const sendEmailViaSMTP = async ({
|
||||||
subject,
|
subject,
|
||||||
message,
|
message,
|
||||||
}: {
|
}: {
|
||||||
subject: string;
|
subject: string;
|
||||||
message: string;
|
message: string;
|
||||||
}) => {
|
}) => {
|
||||||
const accessToken = process.env.JMAP_ACCESS_TOKEN
|
// Get environment variables
|
||||||
? process.env.JMAP_ACCESS_TOKEN
|
const smtpHost = process.env.SMTP_HOST
|
||||||
: import.meta.env.JMAP_ACCESS_TOKEN;
|
? process.env.SMTP_HOST
|
||||||
const accountId = process.env.JMAP_ACCOUNT_ID
|
: import.meta.env.SMTP_HOST;
|
||||||
? process.env.JMAP_ACCOUNT_ID
|
const smtpPort = process.env.SMTP_PORT
|
||||||
: import.meta.env.JMAP_ACCOUNT_ID;
|
? 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
|
const fromEmail = process.env.FROM_EMAIL
|
||||||
? process.env.FROM_EMAIL
|
? process.env.FROM_EMAIL
|
||||||
: import.meta.env.FROM_EMAIL;
|
: import.meta.env.FROM_EMAIL;
|
||||||
const toEmail = process.env.TO_EMAIL
|
const toEmail = process.env.TO_EMAIL
|
||||||
? process.env.TO_EMAIL
|
? process.env.TO_EMAIL
|
||||||
: import.meta.env.TO_EMAIL;
|
: import.meta.env.TO_EMAIL;
|
||||||
const apiUrl = "https://api.fastmail.com/jmap/api/";
|
|
||||||
|
|
||||||
if (!accessToken || !accountId || !fromEmail || !toEmail) {
|
if (!smtpHost || !smtpUser || !smtpPassword || !fromEmail || !toEmail) {
|
||||||
throw new Error("Missing environment variables configuration");
|
throw new Error("Missing SMTP configuration environment variables");
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = new Headers({
|
const transporter = nodemailer.createTransport({
|
||||||
Authorization: `Bearer ${accessToken}`,
|
host: smtpHost,
|
||||||
"Content-Type": "application/json",
|
port: smtpPort,
|
||||||
Accept: "application/json",
|
secure: smtpPort === 465,
|
||||||
|
auth: {
|
||||||
|
user: smtpUser,
|
||||||
|
pass: smtpPassword,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get email identities
|
await transporter.verify();
|
||||||
const identityRequest = {
|
|
||||||
using: ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:submission"],
|
|
||||||
methodCalls: [
|
|
||||||
[
|
|
||||||
"Identity/get",
|
|
||||||
{
|
|
||||||
accountId,
|
|
||||||
properties: ["id", "email", "name"],
|
|
||||||
},
|
|
||||||
"0",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const identityResponse = await fetch(apiUrl, {
|
const info = await transporter.sendMail({
|
||||||
method: "POST",
|
from: fromEmail,
|
||||||
headers,
|
to: toEmail,
|
||||||
body: JSON.stringify(identityRequest),
|
subject: subject,
|
||||||
|
text: message,
|
||||||
|
html: `<pre>${message}</pre>`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const identityData = await identityResponse.json();
|
console.log("Message sent: %s", info.messageId);
|
||||||
const identity = identityData.methodResponses[0][1].list.find(
|
return { success: true, messageId: info.messageId };
|
||||||
(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 };
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("JMAP Error:", error);
|
console.error("SMTP Error:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -230,7 +78,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendEmailViaJMAP({ subject, message });
|
await sendEmailViaSMTP({ subject, message });
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({ success: true, message: "Email sent successfully" }),
|
JSON.stringify({ success: true, message: "Email sent successfully" }),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user