diff --git a/web/src/lib/components/AuthenticationCard.svelte b/web/src/lib/components/AuthenticationCard.svelte
index cf1b80f..b76b48a 100644
--- a/web/src/lib/components/AuthenticationCard.svelte
+++ b/web/src/lib/components/AuthenticationCard.svelte
@@ -1,13 +1,13 @@
@@ -58,13 +58,13 @@
{#if spf.all_qualifier}
All Mechanism Policy:
- {#if spf.all_qualifier === '-'}
+ {#if spf.all_qualifier === "-"}
Strict (-all)
- {:else if spf.all_qualifier === '~'}
+ {:else if spf.all_qualifier === "~"}
Softfail (~all)
- {:else if spf.all_qualifier === '+'}
+ {:else if spf.all_qualifier === "+"}
Pass (+all)
- {:else if spf.all_qualifier === '?'}
+ {:else if spf.all_qualifier === "?"}
Neutral (?all)
{/if}
{#if index === 0 || (index === 1 && spfRecords[0].record?.includes('redirect='))}
diff --git a/web/src/lib/components/SummaryCard.svelte b/web/src/lib/components/SummaryCard.svelte
index 971c1ac..9eb6272 100644
--- a/web/src/lib/components/SummaryCard.svelte
+++ b/web/src/lib/components/SummaryCard.svelte
@@ -5,8 +5,10 @@
interface TextSegment {
text: string;
highlight?: {
- color: "good" | "warning" | "danger";
+ color?: "good" | "warning" | "danger";
bold?: boolean;
+ emphasis?: boolean;
+ monospace?: boolean;
};
link?: string;
}
@@ -22,19 +24,19 @@
// Email sender information
const mailFrom = report.header_analysis?.headers?.from?.value || "an unknown sender";
- const hasDkim = report.authentication?.dkim && report.authentication.dkim.length > 0;
- const dkimPassed = hasDkim && report.authentication.dkim.some(d => d.result === "pass");
+ const hasDkim = report.authentication?.dkim && report.authentication?.dkim.length > 0;
+ const dkimPassed = hasDkim && report.authentication?.dkim?.some((d) => d.result === "pass");
segments.push({ text: "Received a " });
segments.push({
text: dkimPassed ? "DKIM-signed" : "non-DKIM-signed",
highlight: { color: dkimPassed ? "good" : "danger", bold: true },
- link: "#authentication-dkim"
+ link: "#authentication-dkim",
});
segments.push({ text: " email from " });
segments.push({
text: mailFrom,
- highlight: { emphasis: true }
+ highlight: { emphasis: true },
});
// Server information and hops
@@ -47,12 +49,12 @@
segments.push({
text: serverName,
highlight: { monospace: true },
- link: "#header-details"
+ link: "#header-details",
});
segments.push({ text: " after " });
segments.push({
- text: `${hopCount-1} hop${hopCount-1 !== 1 ? "s" : ""}`,
- link: "#email-path"
+ text: `${hopCount - 1} hop${hopCount - 1 !== 1 ? "s" : ""}`,
+ link: "#email-path",
});
}
@@ -65,22 +67,25 @@
segments.push({
text: "authenticated",
highlight: { color: "good", bold: true },
- link: "#authentication-details"
+ link: "#authentication-details",
});
segments.push({ text: " to send email on behalf of " });
- segments.push({ text: report.header_analysis?.domain_alignment?.from_domain, highlight: {monospace: true} });
+ segments.push({
+ text: report.header_analysis?.domain_alignment?.from_domain || "unknown domain",
+ highlight: { monospace: true },
+ });
} else if (spfResult && spfResult !== "none") {
segments.push({
text: "not authenticated",
highlight: { color: "danger", bold: true },
- link: "#authentication-spf"
+ link: "#authentication-spf",
});
segments.push({ text: " (failed authentication checks)" });
} else {
segments.push({
text: "not authenticated",
highlight: { color: "warning", bold: true },
- link: "#authentication-details"
+ link: "#authentication-details",
});
segments.push({ text: " (lacks proper authentication)" });
}
@@ -92,21 +97,23 @@
segments.push({
text: "failed",
highlight: { color: "danger", bold: true },
- link: "#authentication-spf"
+ link: "#authentication-spf",
+ });
+ segments.push({
+ text: ", the sending server is not authorized to send mail for this domain",
});
- segments.push({ text: ", the sending server is not authorized to send mail for this domain" });
} else if (spfResult === "softfail") {
segments.push({
text: "soft-failed",
highlight: { color: "warning", bold: true },
- link: "#authentication-spf"
+ link: "#authentication-spf",
});
segments.push({ text: ", the sending server may not be authorized" });
} else if (spfResult === "temperror" || spfResult === "permerror") {
segments.push({
text: "encountered an error",
highlight: { color: "warning", bold: true },
- link: "#authentication-spf"
+ link: "#authentication-spf",
});
segments.push({ text: ", check your SPF record configuration" });
} else if (spfResult === "none") {
@@ -114,9 +121,11 @@
segments.push({
text: "no SPF record",
highlight: { color: "danger", bold: true },
- link: "#dns-spf"
+ link: "#dns-spf",
+ });
+ segments.push({
+ text: ", you should add one to specify which servers can send email on your behalf",
});
- segments.push({ text: ", you should add one to specify which servers can send email on your behalf" });
}
}
@@ -129,13 +138,13 @@
segments.push({
text: "good",
highlight: { color: "good", bold: true },
- link: "#dns-ptr"
+ link: "#dns-ptr",
});
} else if (iprevResult.result === "fail") {
segments.push({
text: "failed",
highlight: { color: "danger", bold: true },
- link: "#dns-ptr"
+ link: "#dns-ptr",
});
segments.push({ text: " to pass the test" });
} else {
@@ -143,7 +152,7 @@
segments.push({
text: iprevResult.result,
highlight: { color: "warning", bold: true },
- link: "#dns-ptr"
+ link: "#dns-ptr",
});
}
}
@@ -152,20 +161,20 @@
const blacklists = report.blacklists;
if (blacklists && Object.keys(blacklists).length > 0) {
const allChecks = Object.values(blacklists).flat();
- const listedCount = allChecks.filter(check => check.listed).length;
+ const listedCount = allChecks.filter((check) => check.listed).length;
segments.push({ text: ". Your server is " });
if (listedCount > 0) {
segments.push({
text: `blacklisted on ${listedCount} list${listedCount !== 1 ? "s" : ""}`,
highlight: { color: "danger", bold: true },
- link: "#rbl-details"
+ link: "#rbl-details",
});
} else {
segments.push({
text: "not blacklisted",
highlight: { color: "good", bold: true },
- link: "#rbl-details"
+ link: "#rbl-details",
});
}
}
@@ -178,7 +187,7 @@
segments.push({
text: "good",
highlight: { color: "good", bold: true },
- link: "#domain-alignment"
+ link: "#domain-alignment",
});
if (!domainAlignment.aligned) {
segments.push({ text: " using organizational domain" });
@@ -187,17 +196,22 @@
segments.push({
text: "misaligned",
highlight: { color: "danger", bold: true },
- link: "#domain-alignment"
+ link: "#domain-alignment",
});
segments.push({ text: ": " });
segments.push({ text: "Return-Path", highlight: { monospace: true } });
segments.push({ text: " is set to an address of " });
- segments.push({ text: report.header_analysis?.domain_alignment?.return_path_domain, highlight: { monospace: true } });
+ segments.push({
+ text:
+ report.header_analysis?.domain_alignment?.return_path_domain ||
+ "unknown domain",
+ highlight: { monospace: true },
+ });
segments.push({ text: ", you should " });
segments.push({
text: "update it",
highlight: { bold: true },
- link: "#domain-alignment"
+ link: "#domain-alignment",
});
}
}
@@ -210,25 +224,28 @@
segments.push({
text: "don't have",
highlight: { color: "danger", bold: true },
- link: "#dns-dmarc"
+ link: "#dns-dmarc",
});
segments.push({ text: " a DMARC record, " });
- segments.push({ text: "consider adding at least a record with the '", highlight: { bold : true } });
+ segments.push({
+ text: "consider adding at least a record with the '",
+ highlight: { bold: true },
+ });
segments.push({ text: "none", highlight: { monospace: true, bold: true } });
- segments.push({ text: "' policy", highlight: { bold : true } });
+ segments.push({ text: "' policy", highlight: { bold: true } });
} else if (!dmarcRecord.valid) {
segments.push({ text: ". Your DMARC record has " });
segments.push({
text: "issues",
highlight: { color: "danger", bold: true },
- link: "#dns-dmarc"
+ link: "#dns-dmarc",
});
} else if (dmarcRecord.policy === "none") {
segments.push({ text: ". Your DMARC policy is " });
segments.push({
text: "set to 'none'",
highlight: { color: "warning", bold: true },
- link: "#dns-dmarc"
+ link: "#dns-dmarc",
});
segments.push({ text: ", which provides monitoring but no protection" });
} else if (dmarcRecord.policy === "quarantine" || dmarcRecord.policy === "reject") {
@@ -236,7 +253,7 @@
segments.push({
text: dmarcRecord.policy,
highlight: { color: "good", bold: true, monospace: true },
- link: "#dns-dmarc"
+ link: "#dns-dmarc",
});
segments.push({ text: "'" });
if (dmarcRecord.policy === "reject") {
@@ -247,17 +264,17 @@
segments.push({ text: "'" });
}
}
- } else if (dmarcResult && dmarcResult.result === "fail") {
+ } else if (dmarcResult === "fail") {
segments.push({ text: ". DMARC check " });
segments.push({
text: "failed",
highlight: { color: "danger", bold: true },
- link: "#authentication-dmarc"
+ link: "#authentication-dmarc",
});
}
// BIMI
- if (dmarcRecord.valid && dmarcRecord.policy != "none") {
+ if (dmarcRecord && dmarcRecord.valid && dmarcRecord.policy != "none") {
const bimiResult = report.authentication?.bimi;
const bimiRecord = report.dns_results?.bimi_record;
if (bimiRecord?.valid) {
@@ -268,7 +285,7 @@
link: "#dns-bimi"
});
segments.push({ text: " for brand indicator display" });
- } else if (bimiResult && bimiResult.details.indexOf("(No BIMI records found)") >= 0) {
+ } else if (bimiResult && bimiResult.details && bimiResult.details.indexOf("(No BIMI records found)") >= 0) {
segments.push({ text: ". Your domain has no " });
segments.push({
text: "BIMI record",
@@ -293,19 +310,21 @@
segments.push({ text: ". " });
segments.push({
text: "ARC chain validation",
- link: "#authentication-arc"
+ link: "#authentication-arc",
});
segments.push({ text: " " });
if (arcResult.chain_valid) {
segments.push({
text: "passed",
- highlight: { color: "good", bold: true }
+ highlight: { color: "good", bold: true },
+ });
+ segments.push({
+ text: ` with ${arcResult.chain_length} set${arcResult.chain_length !== 1 ? "s" : ""}, indicating proper email forwarding`,
});
- segments.push({ text: ` with ${arcResult.chain_length} set${arcResult.chain_length !== 1 ? "s" : ""}, indicating proper email forwarding` });
} else {
segments.push({
text: "failed",
- highlight: { color: "danger", bold: true }
+ highlight: { color: "danger", bold: true },
});
segments.push({ text: ", which may indicate issues with email forwarding" });
}
@@ -316,20 +335,25 @@
const listUnsubscribe = headers?.["list-unsubscribe"];
const listUnsubscribePost = headers?.["list-unsubscribe-post"];
- const hasNewsletterHeaders = (listUnsubscribe?.importance === "newsletter" && listUnsubscribe?.present) ||
- (listUnsubscribePost?.importance === "newsletter" && listUnsubscribePost?.present);
+ const hasNewsletterHeaders =
+ (listUnsubscribe?.importance === "newsletter" && listUnsubscribe?.present) ||
+ (listUnsubscribePost?.importance === "newsletter" && listUnsubscribePost?.present);
- if (!hasNewsletterHeaders && (listUnsubscribe?.importance === "newsletter" || listUnsubscribePost?.importance === "newsletter")) {
+ if (
+ !hasNewsletterHeaders &&
+ (listUnsubscribe?.importance === "newsletter" ||
+ listUnsubscribePost?.importance === "newsletter")
+ ) {
segments.push({ text: ". This email is " });
segments.push({
text: "missing unsubscribe headers",
highlight: { color: "warning", bold: true },
- link: "#header-details"
+ link: "#header-details",
});
segments.push({ text: " and is " });
segments.push({
text: "not suitable for marketing campaigns",
- highlight: { bold: true }
+ highlight: { bold: true },
});
}
@@ -344,7 +368,7 @@
segments.push({
text: "flagged as spam",
highlight: { color: "danger", bold: true },
- link: "#spam-details"
+ link: "#spam-details",
});
segments.push({ text: " and needs review" });
} else if (contentScore < 50) {
@@ -352,49 +376,55 @@
segments.push({
text: "needs improvement",
highlight: { color: "warning", bold: true },
- link: "#content-details"
+ link: "#content-details",
});
} else if (contentScore >= 100 && spamScore >= 100) {
segments.push({ text: "Content " });
segments.push({
text: "looks great",
highlight: { color: "good", bold: true },
- link: "#content-details"
+ link: "#content-details",
});
} else if (spamScore < 50) {
segments.push({ text: "Your " });
segments.push({
text: "spam score",
highlight: { color: "danger", bold: true },
- link: "#spam-details"
+ link: "#spam-details",
});
segments.push({ text: " is low" });
- if (report.spamassassin.tests.includes("EMPTY_MESSAGE")) {
- segments.push({ text: " (you sent an empty message, which can cause this issue, retry with some real content)", highlight: { bold: true } });
+ if (report.spamassassin?.tests?.includes("EMPTY_MESSAGE")) {
+ segments.push({
+ text: " (you sent an empty message, which can cause this issue, retry with some real content)",
+ highlight: { bold: true },
+ });
}
} else if (spamScore < 90) {
segments.push({ text: "Pay attention to your " });
segments.push({
text: "spam score",
highlight: { color: "warning", bold: true },
- link: "#spam-details"
+ link: "#spam-details",
});
- if (report.spamassassin.tests.includes("EMPTY_MESSAGE")) {
- segments.push({ text: " (you sent an empty message, which can cause this issue, retry with some real content)", highlight: { bold: true } });
+ if (report.spamassassin?.tests?.includes("EMPTY_MESSAGE")) {
+ segments.push({
+ text: " (you sent an empty message, which can cause this issue, retry with some real content)",
+ highlight: { bold: true },
+ });
}
} else if (contentScore >= 80) {
segments.push({ text: "Content " });
segments.push({
text: "looks good",
highlight: { color: "good", bold: true },
- link: "#content-details"
+ link: "#content-details",
});
} else {
segments.push({ text: "Content " });
segments.push({
text: "should be reviewed",
highlight: { color: "warning", bold: true },
- link: "#content-details"
+ link: "#content-details",
});
}
@@ -403,7 +433,7 @@
return segments;
}
- function getColorClass(color: "good" | "warning" | "danger"): string {
+ function getColorClass(color?: "good" | "warning" | "danger"): string {
switch (color) {
case "good":
return "text-success";
@@ -411,28 +441,14 @@
return "text-warning";
case "danger":
return "text-danger";
+ default:
+ return "";
}
}
const summarySegments = $derived(buildSummary());
-
-
+
+
diff --git a/web/src/lib/hey-api.ts b/web/src/lib/hey-api.ts
index e75e70a..6983e5d 100644
--- a/web/src/lib/hey-api.ts
+++ b/web/src/lib/hey-api.ts
@@ -7,8 +7,8 @@ export class NotAuthorizedError extends Error {
}
}
-async function customFetch(url: string, init: RequestInit): Promise
{
- const response = await fetch(url, init);
+async function customFetch(input: RequestInfo | URL, init?: RequestInit): Promise {
+ const response = await fetch(input, init);
if (response.status === 400) {
const json = await response.json();
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte
index f0031bb..35cf00e 100644
--- a/web/src/routes/+layout.svelte
+++ b/web/src/routes/+layout.svelte
@@ -38,7 +38,12 @@
diff --git a/web/src/routes/test/+page.ts b/web/src/routes/test/+page.ts
index d2f88f2..8f8fd5b 100644
--- a/web/src/routes/test/+page.ts
+++ b/web/src/routes/test/+page.ts
@@ -10,10 +10,11 @@ export const load: Load = async ({}) => {
try {
response = await apiCreateTest();
} catch (err) {
- error(err.response.status, err.message);
+ const errorObj = err as { response?: { status?: number }; message?: string };
+ error(errorObj.response?.status || 500, errorObj.message || "Unknown error");
}
- if (response.response.ok) {
+ if (response.response.ok && response.data) {
redirect(302, `/test/${response.data.id}`);
} else {
error(response.response.status, response.error);
diff --git a/web/src/routes/test/[test]/+page.svelte b/web/src/routes/test/[test]/+page.svelte
index 8ac67eb..7ef2b63 100644
--- a/web/src/routes/test/[test]/+page.svelte
+++ b/web/src/routes/test/[test]/+page.svelte
@@ -28,6 +28,8 @@
let fetching = $state(false);
async function fetchTest() {
+ if (!testId) return;
+
if (nbfetch > 0) {
nextfetch = Math.max(nextfetch, Math.floor(3 + nbfetch * 0.5));
}
@@ -89,8 +91,8 @@
}
}
- let lastTestId = null;
- function testChange(newTestId) {
+ let lastTestId: string | null = null;
+ function testChange(newTestId: string) {
if (lastTestId != newTestId) {
lastTestId = newTestId;
test = null;
@@ -100,7 +102,10 @@
}
$effect(() => {
- testChange(page.params.test);
+ const newTestId = page.params.test;
+ if (newTestId) {
+ testChange(newTestId);
+ }
})
onDestroy(() => {
@@ -128,9 +133,9 @@
function handleExportJSON() {
const dataStr = JSON.stringify(report, null, 2);
- const dataBlob = new Blob([dataStr], { type: 'application/json' });
+ const dataBlob = new Blob([dataStr], { type: "application/json" });
const url = URL.createObjectURL(dataBlob);
- const link = document.createElement('a');
+ const link = document.createElement("a");
link.href = url;
link.download = `report-${testId}.json`;
link.click();
@@ -140,7 +145,7 @@
@@ -283,11 +288,11 @@
@@ -348,23 +353,6 @@
}
}
- .category-section {
- margin-bottom: 2rem;
- }
-
- .category-title {
- font-size: 1.25rem;
- font-weight: 600;
- color: #495057;
- padding-bottom: 0.5rem;
- border-bottom: 2px solid #e9ecef;
- }
-
- .category-score {
- font-size: 1rem;
- font-weight: 700;
- }
-
.menu-container {
position: relative;
}