Fixed Stalwart API
Publish Docker image / Build and Push Docker image (release) Successful in 20s

This commit is contained in:
2026-08-17 14:16:05 +02:00 Verified
parent 8d3fa108d6
commit 7de7d11d11
3 changed files with 85 additions and 14 deletions
+81 -10
View File
@@ -85,8 +85,64 @@ export async function submitRegistration(prevState: unknown, formData: FormData)
};
}
// Hjälpfunktion för att bygga och skicka via Stalwart JMAP
async function sendMailJMAP(toEmail: string, replyToEmail: string, subject: string, htmlContent: string, fromName: string) {
async function sendMailJMAP(toEmail: string, replyToEmail: string, subject: string, htmlContent: string, fromName: string, fromEmail: string) {
// 1. Hämta Mapp-ID (Drafts/Utkast) och Identitets-ID från Stalwart
const setupPayload = {
using: ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
methodCalls: [
["Mailbox/query", { accountId: accountId, filter: { role: "drafts" } }, "getMailbox"],
["Identity/get", { accountId: accountId }, "getIdentity"]
]
};
const setupRes = await fetch(jmapUrl as string, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${jmapToken}` },
body: JSON.stringify(setupPayload)
});
const setupData = await setupRes.json();
const mailboxRes = setupData.methodResponses.find((r: any) => r[0] === "Mailbox/query");
const identityRes = setupData.methodResponses.find((r: any) => r[0] === "Identity/get");
let mailboxId = mailboxRes && mailboxRes[1].ids && mailboxRes[1].ids.length > 0 ? mailboxRes[1].ids[0] : null;
// ---- NY LOGIK HÄR: Hitta rätt identitet baserat på fromEmail ----
let identityId = null;
let actualFromEmail = fromEmail;
if (identityRes && identityRes[1].list) {
// Leta efter identiteten som matchar adressen vi vill skicka från
const matchedIdentity = identityRes[1].list.find((i: any) => i.email === fromEmail);
if (matchedIdentity) {
identityId = matchedIdentity.id;
} else if (identityRes[1].list.length > 0) {
// Fallback om den exakta adressen inte fanns i listan
identityId = identityRes[1].list[0].id;
actualFromEmail = identityRes[1].list[0].email;
}
}
// Om ingen mapp med rollen "drafts" finns, hämta första bästa mapp
if (!mailboxId) {
const fallbackRes = await fetch(jmapUrl as string, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${jmapToken}` },
body: JSON.stringify({
using: ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
methodCalls: [["Mailbox/query", { accountId: accountId }, "0"]]
})
});
const fallbackData = await fallbackRes.json();
mailboxId = fallbackData.methodResponses[0][1].ids[0];
}
if (!mailboxId || !identityId) {
throw new Error("Kunde inte hitta mailboxId eller identityId i Stalwart.");
}
// 2. Skapa och skicka mailet
const payload = {
using: [
"urn:ietf:params:jmap:core",
@@ -100,7 +156,8 @@ export async function submitRegistration(prevState: unknown, formData: FormData)
accountId: accountId,
create: {
"draft-1": {
from: [{ email: accountId, name: fromName }],
mailboxIds: { [mailboxId as string]: true },
from: [{ email: actualFromEmail, name: fromName }], // Nu använder vi rätt adress
to: [{ email: toEmail }],
replyTo: [{ email: replyToEmail }],
subject: subject,
@@ -123,7 +180,8 @@ export async function submitRegistration(prevState: unknown, formData: FormData)
accountId: accountId,
create: {
"sub-1": {
emailId: "#draft-1"
emailId: "#draft-1",
identityId: identityId // Nu använder vi rätt ID
}
}
},
@@ -141,11 +199,22 @@ export async function submitRegistration(prevState: unknown, formData: FormData)
body: JSON.stringify(payload)
});
if (!res.ok) {
throw new Error(`JMAP API Error: ${res.status} ${res.statusText}`);
const data = await res.json();
const hasErrors = data.methodResponses.some(
(response: any) =>
response[0] === "error" ||
(response[1] && response[1].notCreated)
);
if (hasErrors) {
console.error("--- JMAP METHOD ERROR ---");
console.error(JSON.stringify(data.methodResponses, null, 2));
console.error("-------------------------");
throw new Error("JMAP accepterade anropet, men vägrade skapa/skicka mailet.");
}
return res.json();
return data;
}
// 6. Skapa och skicka mail
@@ -204,21 +273,23 @@ export async function submitRegistration(prevState: unknown, formData: FormData)
</div>
`;
// Skicka mailen parallellt för prestanda (frivilligt, men oftast bättre än sekventiellt)
// Skicka mailen parallellt
await Promise.all([
sendMailJMAP(
'beach@lerbergetsvolleyboll.se', // To
rawData.email, // ReplyTo
`Anmälan: ${rawData.team}`, // Subject
mailTillKlubbHtml, // HTML Content
`Beach-anmälan: ${rawData.name}` // From Name
`Beach-anmälan: ${rawData.name}`,// From Name
'beach@lerbergetsvolleyboll.se' // From Email (VIKTIGT!)
),
sendMailJMAP(
rawData.email, // To
'beach@lerbergetsvolleyboll.se', // ReplyTo
`Vi har tagit emot din anmälan för ${rawData.team}`, // Subject
mailTillAnvandareHtml, // HTML Content
"LVS" // From Name
"LVS Beach", // From Name
'beach@lerbergetsvolleyboll.se' // From Email (VIKTIGT!)
)
]);
+1 -1
View File
@@ -29,7 +29,7 @@ export async function getConfig() {
return {
showBeachPromo: true,
beachPage: {
matchStart: "2026-06-13T10:00",
matchStart: "2027-06-12T10:00",
isClosedOverride: false,
closeReason: "Det är hela 26 lag anmälda och vi kan tyvärr inte ta emot fler anmälningar.\r\n\r\nVi är glada över förväntan och bjuder in alla andra till att komma och kolla men vi har tyvärr inte kapaciteten till att ha med fler lag.",
festivalLink: "https://www.kullahalvon.com/upptacka--uppleva/kultur--noje/evenemang-pa-kullahalvon/mat---sommarfesten.html",
+3 -3
View File
@@ -2,6 +2,6 @@ TURNSTILE_SITE_KEY=1x00000000000000000000AA
TURNSTILE_SECRET=1x0000000000000000000000000000000AA
ADMIN_PASSWORD=PASSWORD
SMTP_HOST=smtp.example.com
SMTP_USER=USER
SMTP_PASS=PASSWORD
STALWART_JMAP_URL=https://mail.example.com/jmap/
STALWART_ACCOUNT_ID=ID
STALWART_API_TOKEN=API_XXXXXXXXXXXXXXXXXXXXX