New route to check blacklist only
Some checks are pending
continuous-integration/drone/push Build is running
Some checks are pending
continuous-integration/drone/push Build is running
This commit is contained in:
parent
d82dddb45e
commit
6f22d340d2
9 changed files with 586 additions and 2 deletions
|
|
@ -202,6 +202,39 @@ paths:
|
|||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/blacklist:
|
||||
post:
|
||||
tags:
|
||||
- tests
|
||||
summary: Check an IP address against DNS blacklists
|
||||
description: Tests a single IP address (IPv4 or IPv6) against configured DNS-based blacklists (RBLs) without requiring an actual email to be sent. Returns results immediately.
|
||||
operationId: checkBlacklist
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BlacklistCheckRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Blacklist check completed successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BlacklistCheckResponse'
|
||||
'400':
|
||||
description: Invalid request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'500':
|
||||
description: Internal server error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/status:
|
||||
get:
|
||||
tags:
|
||||
|
|
@ -1182,3 +1215,48 @@ components:
|
|||
example: "A"
|
||||
dns_results:
|
||||
$ref: '#/components/schemas/DNSResults'
|
||||
|
||||
BlacklistCheckRequest:
|
||||
type: object
|
||||
required:
|
||||
- ip
|
||||
properties:
|
||||
ip:
|
||||
type: string
|
||||
description: IPv4 or IPv6 address to check against blacklists
|
||||
example: "192.0.2.1"
|
||||
pattern: '^([0-9]{1,3}\.){3}[0-9]{1,3}$|^([0-9a-fA-F]{0,4}:){7}[0-9a-fA-F]{0,4}$|^::([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$|^([0-9a-fA-F]{0,4}:){1,6}:([0-9a-fA-F]{0,4}:){0,5}[0-9a-fA-F]{0,4}$'
|
||||
|
||||
BlacklistCheckResponse:
|
||||
type: object
|
||||
required:
|
||||
- ip
|
||||
- checks
|
||||
- listed_count
|
||||
- score
|
||||
- grade
|
||||
properties:
|
||||
ip:
|
||||
type: string
|
||||
description: The IP address that was checked
|
||||
example: "192.0.2.1"
|
||||
checks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/BlacklistCheck'
|
||||
description: List of blacklist check results
|
||||
listed_count:
|
||||
type: integer
|
||||
description: Number of blacklists that have this IP listed
|
||||
example: 0
|
||||
score:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
description: Blacklist score (0-100, higher is better)
|
||||
example: 100
|
||||
grade:
|
||||
type: string
|
||||
enum: [A+, A, B, C, D, E, F]
|
||||
description: Letter grade representation of the score
|
||||
example: "A+"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import (
|
|||
type EmailAnalyzer interface {
|
||||
AnalyzeEmailBytes(rawEmail []byte, testID uuid.UUID) (reportJSON []byte, err error)
|
||||
AnalyzeDomain(domain string) (dnsResults *DNSResults, score int, grade string)
|
||||
CheckBlacklistIP(ip string) (checks []BlacklistCheck, listedCount int, score int, grade string, err error)
|
||||
}
|
||||
|
||||
// APIHandler implements the ServerInterface for handling API requests
|
||||
|
|
@ -341,3 +342,41 @@ func (h *APIHandler) TestDomain(c *gin.Context) {
|
|||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// CheckBlacklist checks an IP address against DNS blacklists
|
||||
// (POST /blacklist)
|
||||
func (h *APIHandler) CheckBlacklist(c *gin.Context) {
|
||||
var request BlacklistCheckRequest
|
||||
|
||||
// Bind and validate request
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
c.JSON(http.StatusBadRequest, Error{
|
||||
Error: "invalid_request",
|
||||
Message: "Invalid request body",
|
||||
Details: stringPtr(err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Perform blacklist check using analyzer
|
||||
checks, listedCount, score, grade, err := h.analyzer.CheckBlacklistIP(request.Ip)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, Error{
|
||||
Error: "invalid_ip",
|
||||
Message: "Invalid IP address",
|
||||
Details: stringPtr(err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Build response
|
||||
response := BlacklistCheckResponse{
|
||||
Ip: request.Ip,
|
||||
Checks: checks,
|
||||
ListedCount: listedCount,
|
||||
Score: score,
|
||||
Grade: BlacklistCheckResponseGrade(grade),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,3 +119,23 @@ func (a *APIAdapter) AnalyzeDomain(domain string) (*api.DNSResults, int, string)
|
|||
|
||||
return dnsResults, score, grade
|
||||
}
|
||||
|
||||
// CheckBlacklistIP checks a single IP address against DNS blacklists
|
||||
func (a *APIAdapter) CheckBlacklistIP(ip string) ([]api.BlacklistCheck, int, int, string, error) {
|
||||
// Check the IP against all configured RBLs
|
||||
checks, listedCount, err := a.analyzer.generator.rblChecker.CheckIP(ip)
|
||||
if err != nil {
|
||||
return nil, 0, 0, "", err
|
||||
}
|
||||
|
||||
// Calculate score using the existing function
|
||||
// Create a minimal RBLResults structure for scoring
|
||||
results := &RBLResults{
|
||||
Checks: map[string][]api.BlacklistCheck{ip: checks},
|
||||
IPsChecked: []string{ip},
|
||||
ListedCount: listedCount,
|
||||
}
|
||||
score, grade := a.analyzer.generator.rblChecker.CalculateRBLScore(results)
|
||||
|
||||
return checks, listedCount, score, grade, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,28 @@ func (r *RBLChecker) CheckEmail(email *EmailMessage) *RBLResults {
|
|||
return results
|
||||
}
|
||||
|
||||
// CheckIP checks a single IP address against all configured RBLs
|
||||
func (r *RBLChecker) CheckIP(ip string) ([]api.BlacklistCheck, int, error) {
|
||||
// Validate that it's a valid IP address
|
||||
if !r.isPublicIP(ip) {
|
||||
return nil, 0, fmt.Errorf("invalid or non-public IP address: %s", ip)
|
||||
}
|
||||
|
||||
var checks []api.BlacklistCheck
|
||||
listedCount := 0
|
||||
|
||||
// Check the IP against all RBLs
|
||||
for _, rbl := range r.RBLs {
|
||||
check := r.checkIP(ip, rbl)
|
||||
checks = append(checks, check)
|
||||
if check.Listed {
|
||||
listedCount++
|
||||
}
|
||||
}
|
||||
|
||||
return checks, listedCount, nil
|
||||
}
|
||||
|
||||
// extractIPs extracts IP addresses from Received headers
|
||||
func (r *RBLChecker) extractIPs(email *EmailMessage) []string {
|
||||
var ips []string
|
||||
|
|
|
|||
|
|
@ -63,6 +63,10 @@ func DeclareRoutes(cfg *config.Config, router *gin.Engine) {
|
|||
appConfig["survey_url"] = cfg.SurveyURL.String()
|
||||
}
|
||||
|
||||
if len(cfg.Analysis.RBLs) > 0 {
|
||||
appConfig["rbls"] = cfg.Analysis.RBLs
|
||||
}
|
||||
|
||||
if appcfg, err := json.MarshalIndent(appConfig, "", " "); err != nil {
|
||||
log.Println("Unable to generate JSON config to inject in web application")
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ interface AppConfig {
|
|||
const defaultConfig: AppConfig = {
|
||||
report_retention: 0,
|
||||
survey_url: "",
|
||||
rbls: [],
|
||||
};
|
||||
|
||||
function getConfigFromScriptTag(): AppConfig | null {
|
||||
|
|
|
|||
|
|
@ -235,9 +235,13 @@
|
|||
</div>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<a href="/domain" class="btn btn-secondary btn-lg">
|
||||
<a href="/domain" class="btn btn-secondary btn-lg me-2">
|
||||
<i class="bi bi-globe me-2"></i>
|
||||
Or Test Domain Only
|
||||
Test Domain Only
|
||||
</a>
|
||||
<a href="/blacklist" class="btn btn-secondary btn-lg">
|
||||
<i class="bi bi-shield-exclamation me-2"></i>
|
||||
Check IP Blacklist
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
186
web/src/routes/blacklist/+page.svelte
Normal file
186
web/src/routes/blacklist/+page.svelte
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
import { appConfig } from "$lib/stores/config";
|
||||
|
||||
let ip = $state("");
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
function handleSubmit() {
|
||||
error = null;
|
||||
|
||||
if (!ip.trim()) {
|
||||
error = "Please enter an IP address";
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic IPv4/IPv6 validation
|
||||
const ipv4Pattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
const ipv6Pattern = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
|
||||
|
||||
if (!ipv4Pattern.test(ip.trim()) && !ipv6Pattern.test(ip.trim())) {
|
||||
error = "Please enter a valid IPv4 or IPv6 address (e.g., 192.0.2.1)";
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigate to the blacklist check page
|
||||
goto(`/blacklist/${encodeURIComponent(ip.trim())}`);
|
||||
}
|
||||
|
||||
function handleKeyPress(event: KeyboardEvent) {
|
||||
if (event.key === "Enter") {
|
||||
handleSubmit();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Blacklist Check - happyDeliver</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="display-4 fw-bold mb-3">
|
||||
<i class="bi bi-shield-exclamation me-2"></i>
|
||||
Check IP Blacklist Status
|
||||
</h1>
|
||||
<p class="lead text-muted">
|
||||
Test an IP address against multiple DNS-based blacklists (RBLs) to check its reputation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Input Form -->
|
||||
<div class="card shadow-lg border-0 mb-5">
|
||||
<div class="card-body p-5">
|
||||
<h2 class="h5 mb-4">Enter IP Address</h2>
|
||||
<div class="input-group input-group-lg mb-3">
|
||||
<span class="input-group-text bg-light">
|
||||
<i class="bi bi-hdd-network"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="192.0.2.1 or 2001:db8::1"
|
||||
bind:value={ip}
|
||||
onkeypress={handleKeyPress}
|
||||
autofocus
|
||||
/>
|
||||
<button
|
||||
class="btn btn-primary px-5"
|
||||
onclick={handleSubmit}
|
||||
disabled={!ip.trim()}
|
||||
>
|
||||
<i class="bi bi-search me-2"></i>
|
||||
Check
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
Enter an IPv4 address (e.g., 192.0.2.1) or IPv6 address (e.g., 2001:db8::1)
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Section -->
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-0 bg-light">
|
||||
<div class="card-body">
|
||||
<h3 class="h6 mb-3">
|
||||
<i class="bi bi-check-circle-fill text-success me-2"></i>
|
||||
What's Checked
|
||||
</h3>
|
||||
<ul class="list-unstyled mb-0 small">
|
||||
{#each $appConfig.rbls as rbl}
|
||||
<li class="mb-2"><i class="bi bi-arrow-right me-2"></i>{rbl}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-0 bg-light">
|
||||
<div class="card-body">
|
||||
<h3 class="h6 mb-3">
|
||||
<i class="bi bi-info-circle-fill text-primary me-2"></i>
|
||||
Why Check Blacklists?
|
||||
</h3>
|
||||
<p class="small mb-2">
|
||||
DNS-based blacklists (RBLs) are used by email servers to identify and block spam sources. Being listed can severely impact email deliverability.
|
||||
</p>
|
||||
<p class="small mb-3">
|
||||
This tool checks your IP against multiple popular RBLs to help you:
|
||||
</p>
|
||||
<ul class="list-unstyled mb-3 small">
|
||||
<li class="mb-1">
|
||||
<i class="bi bi-arrow-right me-2"></i>Monitor IP reputation
|
||||
</li>
|
||||
<li class="mb-1">
|
||||
<i class="bi bi-arrow-right me-2"></i>Identify deliverability issues
|
||||
</li>
|
||||
<li class="mb-1">
|
||||
<i class="bi bi-arrow-right me-2"></i>Take corrective action
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Info -->
|
||||
<div class="alert alert-info border-0">
|
||||
<h3 class="h6 mb-2">
|
||||
<i class="bi bi-lightbulb me-2"></i>
|
||||
Need Complete Email Analysis?
|
||||
</h3>
|
||||
<p class="small mb-2">
|
||||
For comprehensive deliverability testing including DKIM verification, content analysis, spam scoring, and more:
|
||||
</p>
|
||||
<a href="/" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-envelope-plus me-1"></i>
|
||||
Send Test Email
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.input-group-lg .form-control {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.input-group-text {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.input-group .form-control {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.input-group .form-control:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
230
web/src/routes/blacklist/[ip]/+page.svelte
Normal file
230
web/src/routes/blacklist/[ip]/+page.svelte
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { onMount } from "svelte";
|
||||
import { checkBlacklist } from "$lib/api";
|
||||
import type { BlacklistCheckResponse } from "$lib/api/types.gen";
|
||||
import { GradeDisplay, BlacklistCard } from "$lib/components";
|
||||
import { theme } from "$lib/stores/theme";
|
||||
|
||||
let ip = $derived($page.params.ip);
|
||||
let loading = $state(true);
|
||||
let error = $state<string | null>(null);
|
||||
let result = $state<BlacklistCheckResponse | null>(null);
|
||||
|
||||
async function analyzeIP() {
|
||||
loading = true;
|
||||
error = null;
|
||||
result = null;
|
||||
|
||||
if (!ip) {
|
||||
error = "IP parameter is missing";
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await checkBlacklist({
|
||||
body: { ip: ip },
|
||||
});
|
||||
|
||||
if (response.response.ok) {
|
||||
result = response.data;
|
||||
} else if (response.error) {
|
||||
error = response.error.message || "Failed to check IP address";
|
||||
}
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : "Failed to check IP address";
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
analyzeIP();
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{ip} - Blacklist Check - happyDeliver</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-lg-10 mx-auto">
|
||||
<!-- Header -->
|
||||
<div class="mb-4">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<h1 class="h2 mb-0">
|
||||
<i class="bi bi-shield-exclamation me-2"></i>
|
||||
Blacklist Analysis
|
||||
</h1>
|
||||
<a href="/blacklist" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-2"></i>
|
||||
Check Another IP
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<!-- Loading State -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body text-center py-5">
|
||||
<div class="spinner-border text-primary mb-3" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<h3 class="h5">Checking {ip}...</h3>
|
||||
<p class="text-muted mb-0">Querying DNS-based blacklists</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else if error}
|
||||
<!-- Error State -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="bi bi-exclamation-triangle text-danger" style="font-size: 4rem;"></i>
|
||||
<h3 class="h4 mt-4">Check Failed</h3>
|
||||
<p class="text-muted mb-4">{error}</p>
|
||||
<button class="btn btn-primary" onclick={analyzeIP}>
|
||||
<i class="bi bi-arrow-clockwise me-2"></i>
|
||||
Try Again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else if result}
|
||||
<!-- Results -->
|
||||
<div class="fade-in">
|
||||
<!-- Score Summary Card -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6 text-center text-md-start mb-3 mb-md-0">
|
||||
<h2 class="h2 mb-2">
|
||||
<span class="font-monospace text-truncate">{result.ip}</span>
|
||||
</h2>
|
||||
{#if result.listed_count === 0}
|
||||
<div class="alert alert-success mb-0 d-inline-block">
|
||||
<i class="bi bi-check-circle me-2"></i>
|
||||
<strong>Not Listed</strong>
|
||||
<p class="d-inline mb-0 mt-1 small">
|
||||
This IP address is not listed on any checked blacklists.
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="alert alert-danger mb-0 d-inline-block">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<strong>Listed on {result.listed_count} blacklist{result.listed_count > 1 ? "s" : ""}</strong>
|
||||
<p class="mb-0 mt-1 small">
|
||||
This IP address is listed on {result.listed_count} of {result.checks.length} checked blacklist{result.checks.length > 1 ? "s" : ""}.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="offset-md-3 col-md-3 text-center">
|
||||
<div
|
||||
class="p-2 rounded text-center summary-card"
|
||||
class:bg-light={$theme === 'light'}
|
||||
class:bg-secondary={$theme !== 'light'}
|
||||
>
|
||||
<GradeDisplay score={result.score} grade={result.grade} />
|
||||
<small class="text-muted d-block">Blacklist Score</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blacklist Results Card -->
|
||||
<BlacklistCard
|
||||
blacklists={{ [result.ip]: result.checks }}
|
||||
blacklistScore={result.score}
|
||||
blacklistGrade={result.grade}
|
||||
/>
|
||||
|
||||
<!-- Information Card -->
|
||||
<div class="card shadow-sm mt-4">
|
||||
<div class="card-body">
|
||||
<h3 class="h5 mb-3">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
What This Means
|
||||
</h3>
|
||||
{#if result.listed_count === 0}
|
||||
<p class="mb-3">
|
||||
<strong>Good news!</strong> This IP address is not currently listed on any of the
|
||||
checked DNS-based blacklists (RBLs). This indicates a good sender reputation
|
||||
and should not negatively impact email deliverability.
|
||||
</p>
|
||||
{:else}
|
||||
<p class="mb-3">
|
||||
<strong>Warning:</strong> This IP address is listed on {result.listed_count} blacklist{result.listed_count > 1 ? "s" : ""}.
|
||||
Being listed can significantly impact email deliverability as many mail servers
|
||||
use these blacklists to filter incoming mail.
|
||||
</p>
|
||||
<div class="alert alert-warning">
|
||||
<h4 class="h6 mb-2">Recommended Actions:</h4>
|
||||
<ul class="mb-0 small">
|
||||
<li>Investigate the cause of the listing (compromised system, spam complaints, etc.)</li>
|
||||
<li>Fix any security issues or stop sending practices that led to the listing</li>
|
||||
<li>Request delisting from each RBL (check their websites for removal procedures)</li>
|
||||
<li>Monitor your IP reputation regularly to prevent future listings</li>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Next Steps -->
|
||||
<div class="card shadow-sm border-primary mt-4">
|
||||
<div class="card-body">
|
||||
<h3 class="h5 mb-3">
|
||||
<i class="bi bi-lightbulb me-2"></i>
|
||||
Want Complete Email Analysis?
|
||||
</h3>
|
||||
<p class="mb-3">
|
||||
This blacklist check tests IP reputation only. For comprehensive
|
||||
deliverability testing including DKIM verification, content analysis,
|
||||
spam scoring, and DNS configuration:
|
||||
</p>
|
||||
<a href="/" class="btn btn-primary">
|
||||
<i class="bi bi-envelope-plus me-2"></i>
|
||||
Send Test Email
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.fade-in {
|
||||
animation: fadeIn 0.5s ease-in;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.summary-card:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.35rem 0.65rem;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue