Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Advanced Email Validation Bot</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .dropzone { | |
| border: 2px dashed #4b5563; | |
| border-radius: 0.5rem; | |
| transition: all 0.3s ease; | |
| } | |
| .dropzone.active { | |
| border-color: #3b82f6; | |
| background-color: rgba(59, 130, 246, 0.05); | |
| } | |
| .progress-bar { | |
| transition: width 0.3s ease; | |
| } | |
| .tooltip { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .tooltip .tooltip-text { | |
| visibility: hidden; | |
| width: 200px; | |
| background-color: #1f2937; | |
| color: #fff; | |
| text-align: center; | |
| border-radius: 6px; | |
| padding: 5px; | |
| position: absolute; | |
| z-index: 1; | |
| bottom: 125%; | |
| left: 50%; | |
| margin-left: -100px; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .tooltip:hover .tooltip-text { | |
| visibility: visible; | |
| opacity: 1; | |
| } | |
| .status-badge { | |
| padding: 0.25rem 0.5rem; | |
| border-radius: 0.25rem; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| } | |
| .status-valid { | |
| background-color: #d1fae5; | |
| color: #065f46; | |
| } | |
| .status-invalid { | |
| background-color: #fee2e2; | |
| color: #b91c1c; | |
| } | |
| .status-duplicate { | |
| background-color: #fef3c7; | |
| color: #92400e; | |
| } | |
| .status-typo { | |
| background-color: #dbeafe; | |
| color: #1e40af; | |
| } | |
| .status-unknown { | |
| background-color: #e5e7eb; | |
| color: #4b5563; | |
| } | |
| .status-undeliverable { | |
| background-color: #f5f5f4; | |
| color: #57534e; | |
| } | |
| .status-mx { | |
| background-color: #ede9fe; | |
| color: #5b21b6; | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-in; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .insight-card { | |
| transition: all 0.3s ease; | |
| } | |
| .insight-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8 max-w-6xl"> | |
| <div class="text-center mb-8"> | |
| <h1 class="text-3xl font-bold text-gray-800 mb-2">Advanced Email Validation Bot</h1> | |
| <p class="text-gray-600">Upload your Excel file to validate and clean email addresses</p> | |
| </div> | |
| <div class="bg-white rounded-lg shadow-md p-6 mb-8"> | |
| <div id="upload-section"> | |
| <div class="dropzone p-8 text-center cursor-pointer" id="dropzone"> | |
| <div class="flex flex-col items-center justify-center"> | |
| <i class="fas fa-file-excel text-4xl text-blue-500 mb-4"></i> | |
| <h3 class="text-lg font-medium text-gray-700 mb-2">Drag & Drop your Excel file here</h3> | |
| <p class="text-gray-500 mb-4">or</p> | |
| <label for="file-upload" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md cursor-pointer transition duration-300"> | |
| <i class="fas fa-upload mr-2"></i> Browse Files | |
| </label> | |
| <input id="file-upload" type="file" accept=".xlsx, .xls, .csv" class="hidden"> | |
| </div> | |
| </div> | |
| <div class="mt-4 text-sm text-gray-500"> | |
| <p>Supported formats: .xlsx, .xls, .csv</p> | |
| <p>File should contain at least one column with email addresses</p> | |
| </div> | |
| </div> | |
| <div id="processing-section" class="hidden"> | |
| <div class="flex items-center mb-4"> | |
| <div class="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center mr-3"> | |
| <i class="fas fa-cog text-blue-500 animate-spin"></i> | |
| </div> | |
| <h3 class="text-lg font-medium text-gray-700">Processing your file...</h3> | |
| </div> | |
| <div class="mb-4"> | |
| <div class="flex justify-between text-sm text-gray-600 mb-1"> | |
| <span>Progress</span> | |
| <span id="progress-percent">0%</span> | |
| </div> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> | |
| <div id="progress-bar" class="progress-bar bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4"> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="text-sm text-gray-500 mb-1">Emails Processed</div> | |
| <div class="text-2xl font-bold text-gray-800" id="processed-count">0</div> | |
| </div> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="text-sm text-gray-500 mb-1">Valid Emails</div> | |
| <div class="text-2xl font-bold text-green-600" id="valid-count">0</div> | |
| </div> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="text-sm text-gray-500 mb-1">Issues Found</div> | |
| <div class="text-2xl font-bold text-red-600" id="issues-count">0</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="results-section" class="hidden"> | |
| <div class="flex items-center mb-4"> | |
| <div class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center mr-3"> | |
| <i class="fas fa-check text-green-500"></i> | |
| </div> | |
| <h3 class="text-lg font-medium text-gray-700">Validation Complete</h3> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> | |
| <div class="bg-white border border-gray-200 p-4 rounded-lg shadow-sm"> | |
| <div class="text-sm text-gray-500 mb-1">Total Emails</div> | |
| <div class="text-2xl font-bold text-gray-800" id="total-emails">0</div> | |
| </div> | |
| <div class="bg-white border border-gray-200 p-4 rounded-lg shadow-sm"> | |
| <div class="text-sm text-gray-500 mb-1">Valid Emails</div> | |
| <div class="text-2xl font-bold text-green-600" id="final-valid">0</div> | |
| </div> | |
| <div class="bg-white border border-gray-200 p-4 rounded-lg shadow-sm"> | |
| <div class="text-sm text-gray-500 mb-1">Invalid Emails</div> | |
| <div class="text-2xl font-bold text-red-600" id="final-invalid">0</div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6"> | |
| <div class="bg-white border border-gray-200 p-4 rounded-lg shadow-sm"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="text-sm text-gray-500">Duplicates</div> | |
| <div class="text-lg font-bold text-amber-600" id="duplicate-count">0</div> | |
| </div> | |
| <div class="text-xs text-gray-400">Emails appearing multiple times</div> | |
| </div> | |
| <div class="bg-white border border-gray-200 p-4 rounded-lg shadow-sm"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="text-sm text-gray-500">Typos Found</div> | |
| <div class="text-lg font-bold text-blue-600" id="typo-count">0</div> | |
| </div> | |
| <div class="text-xs text-gray-400">Potential email address typos</div> | |
| </div> | |
| <div class="bg-white border border-gray-200 p-4 rounded-lg shadow-sm"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="text-sm text-gray-500">Syntax Errors</div> | |
| <div class="text-lg font-bold text-red-600" id="syntax-count">0</div> | |
| </div> | |
| <div class="text-xs text-gray-400">Invalid email formats</div> | |
| </div> | |
| <div class="bg-white border border-gray-200 p-4 rounded-lg shadow-sm"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="text-sm text-gray-500">MX Issues</div> | |
| <div class="text-lg font-bold text-purple-600" id="mx-count">0</div> | |
| </div> | |
| <div class="text-xs text-gray-400">Domains without mail servers</div> | |
| </div> | |
| <div class="bg-white border border-gray-200 p-4 rounded-lg shadow-sm"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="text-sm text-gray-500">Undeliverable</div> | |
| <div class="text-lg font-bold text-gray-600" id="undeliverable-count">0</div> | |
| </div> | |
| <div class="text-xs text-gray-400">Emails that may bounce</div> | |
| </div> | |
| <div class="bg-white border border-gray-200 p-4 rounded-lg shadow-sm"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="text-sm text-gray-500">Unknown Status</div> | |
| <div class="text-lg font-bold text-gray-500" id="unknown-count">0</div> | |
| </div> | |
| <div class="text-xs text-gray-400">Could not verify deliverability</div> | |
| </div> | |
| </div> | |
| <!-- MX Response Insights Section --> | |
| <div class="mb-6"> | |
| <h4 class="font-medium text-gray-700 mb-4 flex items-center"> | |
| <i class="fas fa-server mr-2 text-blue-500"></i> MX Record Analysis | |
| </h4> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <div class="bg-white border border-blue-100 rounded-lg p-4 insight-card"> | |
| <div class="flex items-center mb-2"> | |
| <div class="w-10 h-10 bg-blue-50 rounded-full flex items-center justify-center mr-3"> | |
| <i class="fas fa-check-circle text-blue-500"></i> | |
| </div> | |
| <h5 class="font-medium text-gray-800">Valid MX Records</h5> | |
| </div> | |
| <div class="text-3xl font-bold text-blue-600 mb-2" id="valid-mx-count">0</div> | |
| <div class="text-sm text-gray-600"> | |
| <span id="valid-mx-percent">0</span>% of emails have domains with properly configured mail servers | |
| </div> | |
| </div> | |
| <div class="bg-white border border-purple-100 rounded-lg p-4 insight-card"> | |
| <div class="flex items-center mb-2"> | |
| <div class="w-10 h-10 bg-purple-50 rounded-full flex items-center justify-center mr-3"> | |
| <i class="fas fa-exclamation-triangle text-purple-500"></i> | |
| </div> | |
| <h5 class="font-medium text-gray-800">MX Issues Detected</h5> | |
| </div> | |
| <div class="text-3xl font-bold text-purple-600 mb-2" id="invalid-mx-count">0</div> | |
| <div class="text-sm text-gray-600"> | |
| These domains cannot receive emails due to missing or incorrect MX records | |
| </div> | |
| <div class="mt-2"> | |
| <div class="text-xs font-medium text-gray-500 mb-1">Top Problematic Domains:</div> | |
| <div class="text-xs text-gray-600" id="top-mx-issues"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Deliverability Insights Section --> | |
| <div class="mb-6"> | |
| <h4 class="font-medium text-gray-700 mb-4 flex items-center"> | |
| <i class="fas fa-paper-plane mr-2 text-green-500"></i> Email Deliverability Insights | |
| </h4> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div class="bg-white border border-green-100 rounded-lg p-4 insight-card"> | |
| <div class="flex items-center mb-2"> | |
| <div class="w-10 h-10 bg-green-50 rounded-full flex items-center justify-center mr-3"> | |
| <i class="fas fa-check-circle text-green-500"></i> | |
| </div> | |
| <h5 class="font-medium text-gray-800">High Deliverability</h5> | |
| </div> | |
| <div class="text-3xl font-bold text-green-600 mb-2" id="deliverable-count">0</div> | |
| <div class="text-sm text-gray-600"> | |
| <span id="deliverable-percent">0</span>% of emails are confirmed to be deliverable | |
| </div> | |
| </div> | |
| <div class="bg-white border border-red-100 rounded-lg p-4 insight-card"> | |
| <div class="flex items-center mb-2"> | |
| <div class="w-10 h-10 bg-red-50 rounded-full flex items-center justify-center mr-3"> | |
| <i class="fas fa-times-circle text-red-500"></i> | |
| </div> | |
| <h5 class="font-medium text-gray-800">Undeliverable</h5> | |
| </div> | |
| <div class="text-3xl font-bold text-red-600 mb-2" id="undeliverable-insight-count">0</div> | |
| <div class="text-sm text-gray-600"> | |
| These emails will likely bounce or be rejected by receiving servers | |
| </div> | |
| </div> | |
| <div class="bg-white border border-gray-200 rounded-lg p-4 insight-card"> | |
| <div class="flex items-center mb-2"> | |
| <div class="w-10 h-10 bg-gray-50 rounded-full flex items-center justify-center mr-3"> | |
| <i class="fas fa-question-circle text-gray-500"></i> | |
| </div> | |
| <h5 class="font-medium text-gray-800">Unknown Status</h5> | |
| </div> | |
| <div class="text-3xl font-bold text-gray-600 mb-2" id="unknown-deliverability-count">0</div> | |
| <div class="text-sm text-gray-600"> | |
| Could not verify deliverability due to server restrictions or timeouts | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-4 bg-white border border-yellow-100 rounded-lg p-4 insight-card"> | |
| <div class="flex items-center mb-2"> | |
| <div class="w-10 h-10 bg-yellow-50 rounded-full flex items-center justify-center mr-3"> | |
| <i class="fas fa-chart-line text-yellow-500"></i> | |
| </div> | |
| <h5 class="font-medium text-gray-800">Overall Deliverability Score</h5> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-full bg-gray-200 rounded-full h-4 mr-4"> | |
| <div id="deliverability-score-bar" class="h-4 rounded-full" style="width: 0%; background: linear-gradient(90deg, #10b981 0%, #f59e0b 50%, #ef4444 100%);"></div> | |
| </div> | |
| <div class="text-2xl font-bold" id="deliverability-score">0</div> | |
| <span class="ml-1 text-gray-500">/100</span> | |
| </div> | |
| <div class="mt-2 text-sm text-gray-600"> | |
| <span id="deliverability-rating" class="font-medium">Poor</span> - | |
| <span id="deliverability-description">This list has significant deliverability issues that need attention</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <div class="flex justify-between items-center mb-2"> | |
| <h4 class="font-medium text-gray-700">Email Details</h4> | |
| <div class="text-sm text-gray-500"> | |
| <span id="showing-count">0</span> of <span id="total-count">0</span> emails shown | |
| </div> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full bg-white border border-gray-200 rounded-lg overflow-hidden"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th> | |
| <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th> | |
| <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Details</th> | |
| <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">MX Response</th> | |
| </tr> | |
| </thead> | |
| <tbody id="email-table-body" class="divide-y divide-gray-200"> | |
| <!-- Email rows will be inserted here --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="flex flex-col sm:flex-row justify-between gap-4"> | |
| <button id="download-report" class="flex-1 bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-md flex items-center justify-center transition duration-300"> | |
| <i class="fas fa-file-excel mr-2"></i> Download Cleaned Excel File | |
| </button> | |
| <button id="download-summary" class="flex-1 bg-gray-100 hover:bg-gray-200 text-gray-700 px-6 py-3 rounded-md flex items-center justify-center transition duration-300"> | |
| <i class="fas fa-file-alt mr-2"></i> Download Full Report (PDF) | |
| </button> | |
| <button id="start-over" class="flex-1 bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 px-6 py-3 rounded-md flex items-center justify-center transition duration-300"> | |
| <i class="fas fa-redo mr-2"></i> Start Over | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white rounded-lg shadow-md p-6 mb-8 hidden" id="error-section"> | |
| <div class="flex items-center mb-4"> | |
| <div class="w-8 h-8 rounded-full bg-red-100 flex items-center justify-center mr-3"> | |
| <i class="fas fa-exclamation-triangle text-red-500"></i> | |
| </div> | |
| <h3 class="text-lg font-medium text-gray-700">Error Processing File</h3> | |
| </div> | |
| <div class="text-red-600 mb-4" id="error-message"></div> | |
| <button id="try-again" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md transition duration-300"> | |
| <i class="fas fa-redo mr-2"></i> Try Again | |
| </button> | |
| </div> | |
| <div class="bg-white rounded-lg shadow-md p-6"> | |
| <h3 class="text-lg font-medium text-gray-700 mb-4">How It Works</h3> | |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-4"> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mb-3"> | |
| <i class="fas fa-file-upload text-blue-500"></i> | |
| </div> | |
| <h4 class="font-medium text-gray-800 mb-1">1. Upload File</h4> | |
| <p class="text-sm text-gray-600">Upload your Excel file containing email addresses</p> | |
| </div> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center mb-3"> | |
| <i class="fas fa-check-circle text-purple-500"></i> | |
| </div> | |
| <h4 class="font-medium text-gray-800 mb-1">2. Validation</h4> | |
| <p class="text-sm text-gray-600">We check each email for syntax, typos, duplicates, and deliverability</p> | |
| </div> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center mb-3"> | |
| <i class="fas fa-chart-pie text-green-500"></i> | |
| </div> | |
| <h4 class="font-medium text-gray-800 mb-1">3. Analysis</h4> | |
| <p class="text-sm text-gray-600">Get detailed statistics about your email list quality</p> | |
| </div> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="w-10 h-10 bg-amber-100 rounded-full flex items-center justify-center mb-3"> | |
| <i class="fas fa-download text-amber-500"></i> | |
| </div> | |
| <h4 class="font-medium text-gray-800 mb-1">4. Download</h4> | |
| <p class="text-sm text-gray-600">Download cleaned file with validation results and suggestions</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM elements | |
| const dropzone = document.getElementById('dropzone'); | |
| const fileUpload = document.getElementById('file-upload'); | |
| const uploadSection = document.getElementById('upload-section'); | |
| const processingSection = document.getElementById('processing-section'); | |
| const resultsSection = document.getElementById('results-section'); | |
| const errorSection = document.getElementById('error-section'); | |
| const emailTableBody = document.getElementById('email-table-body'); | |
| // Progress elements | |
| const progressBar = document.getElementById('progress-bar'); | |
| const progressPercent = document.getElementById('progress-percent'); | |
| const processedCount = document.getElementById('processed-count'); | |
| const validCount = document.getElementById('valid-count'); | |
| const issuesCount = document.getElementById('issues-count'); | |
| // Result elements | |
| const totalEmails = document.getElementById('total-emails'); | |
| const finalValid = document.getElementById('final-valid'); | |
| const finalInvalid = document.getElementById('final-invalid'); | |
| const duplicateCount = document.getElementById('duplicate-count'); | |
| const typoCount = document.getElementById('typo-count'); | |
| const syntaxCount = document.getElementById('syntax-count'); | |
| const mxCount = document.getElementById('mx-count'); | |
| const undeliverableCount = document.getElementById('undeliverable-count'); | |
| const unknownCount = document.getElementById('unknown-count'); | |
| const showingCount = document.getElementById('showing-count'); | |
| const totalCount = document.getElementById('total-count'); | |
| // MX Insights elements | |
| const validMxCount = document.getElementById('valid-mx-count'); | |
| const validMxPercent = document.getElementById('valid-mx-percent'); | |
| const invalidMxCount = document.getElementById('invalid-mx-count'); | |
| const topMxIssues = document.getElementById('top-mx-issues'); | |
| // Deliverability Insights elements | |
| const deliverableCount = document.getElementById('deliverable-count'); | |
| const deliverablePercent = document.getElementById('deliverable-percent'); | |
| const undeliverableInsightCount = document.getElementById('undeliverable-insight-count'); | |
| const unknownDeliverabilityCount = document.getElementById('unknown-deliverability-count'); | |
| const deliverabilityScoreBar = document.getElementById('deliverability-score-bar'); | |
| const deliverabilityScore = document.getElementById('deliverability-score'); | |
| const deliverabilityRating = document.getElementById('deliverability-rating'); | |
| const deliverabilityDescription = document.getElementById('deliverability-description'); | |
| // Buttons | |
| const downloadReport = document.getElementById('download-report'); | |
| const downloadSummary = document.getElementById('download-summary'); | |
| const startOver = document.getElementById('start-over'); | |
| const tryAgain = document.getElementById('try-again'); | |
| // Data storage | |
| let emailData = []; | |
| let processedEmails = []; | |
| let workbook = null; | |
| let domainStats = {}; | |
| // Common typos mapping | |
| const commonTypos = { | |
| 'gmai.com': 'gmail.com', | |
| 'gmail.con': 'gmail.com', | |
| 'gmail.co': 'gmail.com', | |
| 'gmail.cm': 'gmail.com', | |
| 'gmial.com': 'gmail.com', | |
| 'gmal.com': 'gmail.com', | |
| 'gamil.com': 'gmail.com', | |
| 'gnail.com': 'gmail.com', | |
| 'gmsil.com': 'gmail.com', | |
| 'gmaill.com': 'gmail.com', | |
| 'gmail..com': 'gmail.com', | |
| 'gmail.comm': 'gmail.com', | |
| 'gmail.om': 'gmail.com', | |
| 'gmil.com': 'gmail.com', | |
| 'gmaul.com': 'gmail.com', | |
| 'gmaio.com': 'gmail.com', | |
| 'gmaiil.com': 'gmail.com', | |
| 'gmeil.com': 'gmail.com', | |
| 'gmil.com': 'gmail.com', | |
| 'gmaile.com': 'gmail.com', | |
| 'gmaim.com': 'gmail.com', | |
| 'hotnail.com': 'hotmail.com', | |
| 'homail.com': 'hotmail.com', | |
| 'hotmal.com': 'hotmail.com', | |
| 'hotmaill.com': 'hotmail.com', | |
| 'hotmail..com': 'hotmail.com', | |
| 'yahoo.co': 'yahoo.com', | |
| 'yaho.com': 'yahoo.com', | |
| 'yahho.com': 'yahoo.com', | |
| 'yahoocom': 'yahoo.com', | |
| 'yahooo.com': 'yahoo.com', | |
| 'yahoo.comm': 'yahoo.com', | |
| 'outlok.com': 'outlook.com', | |
| 'outllok.com': 'outlook.com', | |
| 'outlook..com': 'outlook.com' | |
| }; | |
| // MX response messages | |
| const mxResponses = { | |
| valid: "Valid MX records found. Domain can receive emails.", | |
| invalid: "No MX records found. Domain cannot receive emails.", | |
| timeout: "MX lookup timed out. Could not verify mail servers.", | |
| restricted: "MX lookup restricted by DNS settings.", | |
| temporary: "Temporary MX lookup failure. Try again later." | |
| }; | |
| // Deliverability messages | |
| const deliverabilityMessages = { | |
| deliverable: "Server confirmed email exists and can receive messages.", | |
| undeliverable: "Server confirmed email does not exist or cannot receive messages.", | |
| unknown: "Could not verify deliverability due to server restrictions.", | |
| risky: "Email appears valid but may have deliverability issues.", | |
| catchall: "Domain accepts all emails (catch-all), making verification less reliable." | |
| }; | |
| // Event listeners | |
| fileUpload.addEventListener('change', handleFileSelect); | |
| dropzone.addEventListener('dragover', handleDragOver); | |
| dropzone.addEventListener('dragleave', handleDragLeave); | |
| dropzone.addEventListener('drop', handleDrop); | |
| downloadReport.addEventListener('click', downloadCleanedFile); | |
| downloadSummary.addEventListener('click', downloadSummaryReport); | |
| startOver.addEventListener('click', resetForm); | |
| tryAgain.addEventListener('click', resetForm); | |
| // Functions | |
| function handleFileSelect(e) { | |
| const files = e.target.files; | |
| if (files.length) { | |
| processFile(files[0]); | |
| } | |
| } | |
| function handleDragOver(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| dropzone.classList.add('active'); | |
| } | |
| function handleDragLeave(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| dropzone.classList.remove('active'); | |
| } | |
| function handleDrop(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| dropzone.classList.remove('active'); | |
| const files = e.dataTransfer.files; | |
| if (files.length) { | |
| processFile(files[0]); | |
| } | |
| } | |
| function processFile(file) { | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| try { | |
| const data = new Uint8Array(e.target.result); | |
| workbook = XLSX.read(data, { type: 'array' }); | |
| // Extract all emails from the workbook | |
| emailData = extractEmailsFromWorkbook(workbook); | |
| if (emailData.length === 0) { | |
| showError("No email addresses found in the file. Please upload a file with at least one column containing email addresses."); | |
| return; | |
| } | |
| // Initialize domain stats | |
| initDomainStats(emailData); | |
| // Show processing section | |
| uploadSection.classList.add('hidden'); | |
| processingSection.classList.remove('hidden'); | |
| resultsSection.classList.add('hidden'); | |
| errorSection.classList.add('hidden'); | |
| // Start processing emails | |
| processEmails(emailData); | |
| } catch (error) { | |
| showError("Error reading the file. Please make sure it's a valid Excel file."); | |
| console.error(error); | |
| } | |
| }; | |
| reader.onerror = function() { | |
| showError("Error reading the file. Please try again."); | |
| }; | |
| reader.readAsArrayBuffer(file); | |
| } | |
| function extractEmailsFromWorkbook(workbook) { | |
| const emails = []; | |
| const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | |
| // Process each sheet | |
| workbook.SheetNames.forEach(sheetName => { | |
| const worksheet = workbook.Sheets[sheetName]; | |
| const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); | |
| // Process each row and cell | |
| jsonData.forEach(row => { | |
| if (Array.isArray(row)) { | |
| row.forEach(cell => { | |
| if (typeof cell === 'string') { | |
| // Check if the cell looks like an email | |
| if (emailRegex.test(cell.trim())) { | |
| emails.push({ | |
| original: cell.trim(), | |
| cleaned: cell.trim().toLowerCase(), | |
| rowData: row // Store the entire row for later | |
| }); | |
| } | |
| } | |
| }); | |
| } | |
| }); | |
| }); | |
| return emails; | |
| } | |
| function initDomainStats(emails) { | |
| domainStats = {}; | |
| // Count frequency of each domain | |
| emails.forEach(email => { | |
| const domain = email.cleaned.split('@')[1]; | |
| if (!domainStats[domain]) { | |
| domainStats[domain] = { | |
| count: 0, | |
| validMx: 0, | |
| invalidMx: 0, | |
| deliverable: 0, | |
| undeliverable: 0, | |
| unknown: 0 | |
| }; | |
| } | |
| domainStats[domain].count++; | |
| }); | |
| } | |
| function processEmails(emails) { | |
| let processed = 0; | |
| let valid = 0; | |
| let issues = 0; | |
| let duplicates = 0; | |
| let typos = 0; | |
| let syntaxErrors = 0; | |
| let mxErrors = 0; | |
| let undeliverable = 0; | |
| let unknown = 0; | |
| let deliverable = 0; | |
| const totalEmails = emails.length; | |
| const emailFrequency = {}; | |
| const processedEmailsList = []; | |
| // Count frequency for duplicates | |
| emails.forEach(email => { | |
| const cleanEmail = email.cleaned; | |
| emailFrequency[cleanEmail] = (emailFrequency[cleanEmail] || 0) + 1; | |
| }); | |
| // Process each email | |
| emails.forEach((email, index) => { | |
| setTimeout(() => { | |
| const result = validateEmail(email, emailFrequency); | |
| // Update domain stats | |
| const domain = email.cleaned.split('@')[1]; | |
| if (result.hasMxError) { | |
| domainStats[domain].invalidMx++; | |
| } else { | |
| domainStats[domain].validMx++; | |
| if (result.status === 'Valid') { | |
| domainStats[domain].deliverable++; | |
| deliverable++; | |
| } else if (result.isUndeliverable) { | |
| domainStats[domain].undeliverable++; | |
| } else { | |
| domainStats[domain].unknown++; | |
| } | |
| } | |
| processedEmailsList.push({ | |
| ...email, | |
| ...result | |
| }); | |
| processed++; | |
| // Update counters based on result | |
| if (result.status === 'Valid') valid++; | |
| else issues++; | |
| if (result.isDuplicate) duplicates++; | |
| if (result.hasTypo) typos++; | |
| if (result.hasSyntaxError) syntaxErrors++; | |
| if (result.hasMxError) mxErrors++; | |
| if (result.isUndeliverable) undeliverable++; | |
| if (result.status === 'Unknown') unknown++; | |
| // Update progress | |
| const progress = Math.round((processed / totalEmails) * 100); | |
| progressBar.style.width = `${progress}%`; | |
| progressPercent.textContent = `${progress}%`; | |
| processedCount.textContent = processed; | |
| validCount.textContent = valid; | |
| issuesCount.textContent = issues; | |
| // When all emails are processed | |
| if (processed === totalEmails) { | |
| showResults({ | |
| totalEmails, | |
| valid, | |
| invalid: issues, | |
| duplicates, | |
| typos, | |
| syntaxErrors, | |
| mxErrors, | |
| undeliverable, | |
| unknown, | |
| deliverable, | |
| processedEmails: processedEmailsList | |
| }); | |
| } | |
| }, index * 10); // Small delay to allow UI updates | |
| }); | |
| } | |
| function validateEmail(email, emailFrequency) { | |
| const result = { | |
| status: 'Valid', | |
| isDuplicate: false, | |
| hasTypo: false, | |
| hasSyntaxError: false, | |
| hasMxError: false, | |
| isUndeliverable: false, | |
| suggestion: null, | |
| details: 'Email appears to be valid', | |
| mxResponse: mxResponses.valid, | |
| deliverability: deliverabilityMessages.deliverable | |
| }; | |
| // Check for duplicates | |
| if (emailFrequency[email.cleaned] > 1) { | |
| result.isDuplicate = true; | |
| result.status = 'Duplicate'; | |
| result.details = 'This email appears multiple times in your list'; | |
| return result; | |
| } | |
| // Check for syntax errors | |
| if (!isValidEmailSyntax(email.original)) { | |
| result.hasSyntaxError = true; | |
| result.status = 'Invalid Syntax'; | |
| result.details = 'Email format is invalid (missing @, invalid characters, etc.)'; | |
| result.mxResponse = 'N/A - Invalid email format'; | |
| result.deliverability = 'N/A - Invalid email format'; | |
| return result; | |
| } | |
| // Check for typos | |
| const typoCheck = checkForTypos(email.cleaned); | |
| if (typoCheck.hasTypo) { | |
| result.hasTypo = true; | |
| result.status = 'Typo'; | |
| result.suggestion = typoCheck.suggestion; | |
| result.details = `Possible typo detected. Did you mean ${typoCheck.suggestion}?`; | |
| } | |
| // Simulate MX check (in a real app, this would be an API call) | |
| const domain = email.cleaned.split('@')[1]; | |
| const mxCheckResult = simulateMxCheck(domain); | |
| if (!mxCheckResult.valid) { | |
| result.hasMxError = true; | |
| result.status = 'Invalid MX'; | |
| result.details = 'Domain does not have valid mail server records'; | |
| result.mxResponse = mxCheckResult.message; | |
| result.deliverability = 'N/A - No mail servers found'; | |
| return result; | |
| } else { | |
| result.mxResponse = mxCheckResult.message; | |
| } | |
| // Simulate deliverability check (in a real app, this would be an API call) | |
| const deliverabilityResult = simulateDeliverabilityCheck(email.cleaned); | |
| if (deliverabilityResult.status === 'undeliverable') { | |
| result.isUndeliverable = true; | |
| result.status = 'Undeliverable'; | |
| result.details = 'Email address appears to be invalid or not accepting mail'; | |
| result.deliverability = deliverabilityResult.message; | |
| } else if (deliverabilityResult.status === 'unknown') { | |
| result.status = 'Unknown'; | |
| result.details = 'Could not verify deliverability'; | |
| result.deliverability = deliverabilityResult.message; | |
| } else if (deliverabilityResult.status === 'risky') { | |
| result.deliverability = deliverabilityResult.message; | |
| } else if (deliverabilityResult.status === 'catchall') { | |
| result.deliverability = deliverabilityResult.message; | |
| } | |
| return result; | |
| } | |
| function isValidEmailSyntax(email) { | |
| // More comprehensive regex for email validation | |
| const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; | |
| return regex.test(email); | |
| } | |
| function checkForTypos(email) { | |
| const result = { | |
| hasTypo: false, | |
| suggestion: null | |
| }; | |
| const [localPart, domain] = email.split('@'); | |
| // Check for common domain typos | |
| if (commonTypos[domain]) { | |
| result.hasTypo = true; | |
| result.suggestion = `${localPart}@${commonTypos[domain]}`; | |
| return result; | |
| } | |
| // Check for doubled dots | |
| if (domain.includes('..')) { | |
| const correctedDomain = domain.replace(/\.+/g, '.'); | |
| result.hasTypo = true; | |
| result.suggestion = `${localPart}@${correctedDomain}`; | |
| return result; | |
| } | |
| // Check for common TLD typos | |
| const tld = domain.split('.').pop(); | |
| const commonTldTypos = { | |
| // Common "com" typos (keyboard proximity, missing letters, repeats) | |
| 'con': 'com', 'cpm': 'com', 'cim': 'com', 'col': 'com', 'cok': 'com', | |
| 'co': 'com', 'cm': 'com', 'comm': 'com', 'vom': 'com', 'ocm': 'com', | |
| 'cmo': 'com', 'comn': 'com', 'coom': 'com', 'commm': 'com', 'c0m': 'com', | |
| 'čom': 'com', // Accented character typo | |
| 'kom': 'com', // Phonetic error (non-English keyboards) | |
| // Common "net" typos | |
| 'netl': 'net', 'ner': 'net', 'nte': 'net', 'ent': 'net', 'nett': 'net', | |
| 'nettt': 'net', 'n3t': 'net', 'nét': 'net', // Accented typo | |
| // Common "org" typos | |
| 'orgn': 'org', 'og': 'org', 'ogr': 'org', 'or': 'org', 'rg': 'org', | |
| 'orgi': 'org', 'orq': 'org', '0rg': 'org', 'örg': 'org', // Accented/QWERTY errors | |
| // Common "gov" typos | |
| 'gouv': 'gov', 'gob': 'gov', 'gv': 'gov', 'govv': 'gov', 'gove': 'gov', | |
| 'goev': 'gov', 'g0v': 'gov', | |
| // Common "edu" typos | |
| 'edu.': 'edu', 'eddu': 'edu', 'ed': 'edu', 'eduu': 'edu', '3du': 'edu', | |
| // Other generic TLDs | |
| 'intl': 'int', 'itn': 'int', 'inr': 'int', 'iint': 'int', // .int typos | |
| 'mil.': 'mil', 'mll': 'mil', 'miil': 'mil', // .mil typos | |
| 'biz': 'biz', 'bizz': 'biz', 'bzi': 'biz', // .biz typos (prevents overcorrection) | |
| 'infoo': 'info', 'infor': 'info', 'infi': 'info', // .info typos | |
| // Trailing characters (e.g., accidental punctuation) | |
| 'org.': 'org', 'com.': 'com', 'net.': 'net', 'edu..': 'edu', | |
| // Country TLD overcorrections (use with caution!) | |
| 'uk': 'com', // Common mistake for .uk vs .com | |
| 'cm': 'com', // Cameroon’s TLD often mistyped as .com | |
| 'om': 'com', // Oman’s TLD mistyped as .com | |
| }; | |
| if (commonTldTypos[tld]) { | |
| const correctedDomain = domain.replace(new RegExp(`${tld}$`), commonTldTypos[tld]); | |
| result.hasTypo = true; | |
| result.suggestion = `${localPart}@${correctedDomain}`; | |
| return result; | |
| } | |
| return result; | |
| } | |
| function simulateMxCheck(domain) { | |
| // In a real implementation, this would make a DNS query | |
| // For simulation, we'll randomly fail for some domains | |
| const badDomains = ['example.com', 'test.com', 'invalid.com', 'fakedomain.com']; | |
| if (badDomains.includes(domain)) { | |
| return { | |
| valid: false, | |
| message: mxResponses.invalid | |
| }; | |
| } | |
| // Randomly simulate other MX issues | |
| const random = Math.random(); | |
| if (random < 0.05) { | |
| return { | |
| valid: false, | |
| message: mxResponses.timeout | |
| }; | |
| } else if (random < 0.1) { | |
| return { | |
| valid: false, | |
| message: mxResponses.restricted | |
| }; | |
| } else if (random < 0.15) { | |
| return { | |
| valid: false, | |
| message: mxResponses.temporary | |
| }; | |
| } | |
| return { | |
| valid: true, | |
| message: mxResponses.valid | |
| }; | |
| } | |
| function simulateDeliverabilityCheck(email) { | |
| // In a real implementation, this would make an SMTP check | |
| // For simulation, we'll randomly mark some as undeliverable | |
| const random = Math.random(); | |
| if (random < 0.1) { | |
| return { | |
| status: 'undeliverable', | |
| message: deliverabilityMessages.undeliverable | |
| }; | |
| } else if (random < 0.2) { | |
| return { | |
| status: 'unknown', | |
| message: deliverabilityMessages.unknown | |
| }; | |
| } else if (random < 0.3) { | |
| return { | |
| status: 'risky', | |
| message: deliverabilityMessages.risky | |
| }; | |
| } else if (random < 0.4) { | |
| return { | |
| status: 'catchall', | |
| message: deliverabilityMessages.catchall | |
| }; | |
| } | |
| return { | |
| status: 'deliverable', | |
| message: deliverabilityMessages.deliverable | |
| }; | |
| } | |
| function showResults(stats) { | |
| processingSection.classList.add('hidden'); | |
| resultsSection.classList.remove('hidden'); | |
| // Update summary stats | |
| totalEmails.textContent = stats.totalEmails; | |
| finalValid.textContent = stats.valid; | |
| finalInvalid.textContent = stats.invalid; | |
| duplicateCount.textContent = stats.duplicates; | |
| typoCount.textContent = stats.typos; | |
| syntaxCount.textContent = stats.syntaxErrors; | |
| mxCount.textContent = stats.mxErrors; | |
| undeliverableCount.textContent = stats.undeliverable; | |
| unknownCount.textContent = stats.unknown; | |
| // Calculate MX stats | |
| const validMx = stats.totalEmails - stats.mxErrors; | |
| const validMxPct = Math.round((validMx / stats.totalEmails) * 100); | |
| validMxCount.textContent = validMx; | |
| validMxPercent.textContent = validMxPct; | |
| invalidMxCount.textContent = stats.mxErrors; | |
| // Show top MX issues | |
| const problematicDomains = Object.entries(domainStats) | |
| .filter(([domain, stats]) => stats.invalidMx > 0) | |
| .sort((a, b) => b[1].invalidMx - a[1].invalidMx) | |
| .slice(0, 5); | |
| if (problematicDomains.length > 0) { | |
| topMxIssues.innerHTML = problematicDomains | |
| .map(([domain, stats]) => | |
| `<div class="flex justify-between py-1"> | |
| <span class="truncate">${domain}</span> | |
| <span class="font-medium">${stats.invalidMx} issues</span> | |
| </div>` | |
| ).join(''); | |
| } else { | |
| topMxIssues.textContent = "No significant MX issues detected"; | |
| } | |
| // Update deliverability stats | |
| deliverableCount.textContent = stats.deliverable; | |
| const deliverablePct = Math.round((stats.deliverable / stats.totalEmails) * 100); | |
| deliverablePercent.textContent = deliverablePct; | |
| undeliverableInsightCount.textContent = stats.undeliverable; | |
| unknownDeliverabilityCount.textContent = stats.unknown; | |
| // Calculate deliverability score (0-100) | |
| const deliverabilityScoreValue = Math.min(100, Math.max(0, | |
| (stats.deliverable / stats.totalEmails) * 80 + | |
| ((stats.totalEmails - stats.undeliverable) / stats.totalEmails) * 20 | |
| )); | |
| deliverabilityScoreBar.style.width = `${deliverabilityScoreValue}%`; | |
| deliverabilityScore.textContent = Math.round(deliverabilityScoreValue); | |
| // Set deliverability rating | |
| if (deliverabilityScoreValue >= 80) { | |
| deliverabilityRating.textContent = "Excellent"; | |
| deliverabilityRating.className = "font-medium text-green-600"; | |
| deliverabilityDescription.textContent = "This list has excellent deliverability potential"; | |
| } else if (deliverabilityScoreValue >= 60) { | |
| deliverabilityRating.textContent = "Good"; | |
| deliverabilityRating.className = "font-medium text-blue-600"; | |
| deliverabilityDescription.textContent = "This list has good deliverability with minor issues"; | |
| } else if (deliverabilityScoreValue >= 40) { | |
| deliverabilityRating.textContent = "Fair"; | |
| deliverabilityRating.className = "font-medium text-yellow-600"; | |
| deliverabilityDescription.textContent = "This list may have deliverability issues that need attention"; | |
| } else { | |
| deliverabilityRating.textContent = "Poor"; | |
| deliverabilityRating.className = "font-medium text-red-600"; | |
| deliverabilityDescription.textContent = "This list has significant deliverability issues that need attention"; | |
| } | |
| // Show first 100 emails in the table | |
| const emailsToShow = stats.processedEmails.slice(0, 100); | |
| showingCount.textContent = emailsToShow.length; | |
| totalCount.textContent = stats.totalEmails; | |
| emailTableBody.innerHTML = ''; | |
| emailsToShow.forEach(email => { | |
| const row = document.createElement('tr'); | |
| row.className = 'fade-in'; | |
| // Status badge | |
| let statusClass = ''; | |
| if (email.status === 'Valid') statusClass = 'status-valid'; | |
| else if (email.status === 'Duplicate') statusClass = 'status-duplicate'; | |
| else if (email.status === 'Typo') statusClass = 'status-typo'; | |
| else if (email.status === 'Invalid Syntax') statusClass = 'status-invalid'; | |
| else if (email.status === 'Invalid MX') statusClass = 'status-mx'; | |
| else if (email.status === 'Undeliverable') statusClass = 'status-undeliverable'; | |
| else statusClass = 'status-unknown'; | |
| // Tooltip for details | |
| const tooltipId = `tooltip-${Math.random().toString(36).substr(2, 9)}`; | |
| row.innerHTML = ` | |
| <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-800">${email.original}</td> | |
| <td class="px-4 py-3 whitespace-nowrap"> | |
| <span class="status-badge ${statusClass}">${email.status}</span> | |
| </td> | |
| <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-600"> | |
| <div class="tooltip"> | |
| <i class="fas fa-info-circle text-gray-400"></i> | |
| <span class="tooltip-text">${email.details}${email.suggestion ? `<br><br>Suggestion: ${email.suggestion}` : ''}</span> | |
| </div> | |
| ${email.suggestion ? `<span class="ml-2">${email.suggestion}</span>` : ''} | |
| </td> | |
| <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-600"> | |
| <div class="tooltip"> | |
| <i class="fas fa-server text-gray-400"></i> | |
| <span class="tooltip-text">${email.mxResponse}</span> | |
| </div> | |
| <span class="ml-2">${email.deliverability}</span> | |
| </td> | |
| `; | |
| emailTableBody.appendChild(row); | |
| }); | |
| // Store processed emails for download | |
| processedEmails = stats.processedEmails; | |
| workbook = addValidationResultsToWorkbook(workbook, processedEmails); | |
| } | |
| function addValidationResultsToWorkbook(workbook, processedEmails) { | |
| // Create a map of original emails to their validation results | |
| const emailMap = {}; | |
| processedEmails.forEach(email => { | |
| emailMap[email.original] = { | |
| status: email.status, | |
| suggestion: email.suggestion || '', | |
| mxResponse: email.mxResponse, | |
| deliverability: email.deliverability | |
| }; | |
| }); | |
| // Process each sheet | |
| workbook.SheetNames.forEach(sheetName => { | |
| const worksheet = workbook.Sheets[sheetName]; | |
| const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); | |
| // Add headers for new columns if they don't exist | |
| if (jsonData.length > 0) { | |
| const headers = jsonData[0]; | |
| if (!headers.includes('Email Status')) { | |
| headers.push('Email Status'); | |
| headers.push('Suggestion'); | |
| headers.push('MX Response'); | |
| headers.push('Deliverability'); | |
| } | |
| } | |
| // Process each row | |
| for (let i = 1; i < jsonData.length; i++) { | |
| const row = jsonData[i]; | |
| if (!Array.isArray(row)) continue; | |
| // Find email in row | |
| let emailInRow = null; | |
| for (let j = 0; j < row.length; j++) { | |
| const cell = row[j]; | |
| if (typeof cell === 'string' && isValidEmailSyntax(cell.trim())) { | |
| emailInRow = cell.trim(); | |
| break; | |
| } | |
| } | |
| // Add validation results | |
| if (emailInRow && emailMap[emailInRow]) { | |
| // Make sure row has enough columns | |
| while (row.length < jsonData[0].length - 4) { | |
| row.push(''); | |
| } | |
| row.push(emailMap[emailInRow].status); | |
| row.push(emailMap[emailInRow].suggestion); | |
| row.push(emailMap[emailInRow].mxResponse); | |
| row.push(emailMap[emailInRow].deliverability); | |
| } else { | |
| // For rows without email or invalid email | |
| while (row.length < jsonData[0].length - 4) { | |
| row.push(''); | |
| } | |
| row.push('No valid email'); | |
| row.push(''); | |
| row.push(''); | |
| row.push(''); | |
| } | |
| } | |
| // Convert back to worksheet | |
| const newWorksheet = XLSX.utils.aoa_to_sheet(jsonData); | |
| workbook.Sheets[sheetName] = newWorksheet; | |
| }); | |
| return workbook; | |
| } | |
| function downloadCleanedFile() { | |
| if (!workbook) return; | |
| // Convert workbook to binary | |
| const wbout = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }); | |
| // Create blob and download | |
| const blob = new Blob([wbout], { type: 'application/octet-stream' }); | |
| saveAs(blob, 'cleaned_emails.xlsx'); | |
| } | |
| function downloadSummaryReport() { | |
| // In a real implementation, this would generate a PDF report | |
| // For this demo, we'll create a more detailed text file | |
| let reportText = `EMAIL VALIDATION REPORT\n`; | |
| reportText += `================================\n`; | |
| reportText += `Generated on: ${new Date().toLocaleString()}\n\n`; | |
| // Summary stats | |
| reportText += `SUMMARY STATISTICS\n`; | |
| reportText += `------------------\n`; | |
| reportText += `Total Emails Processed: ${totalEmails.textContent}\n`; | |
| reportText += `Valid Emails: ${finalValid.textContent} (${Math.round((finalValid.textContent / totalEmails.textContent) * 100)}%)\n`; | |
| reportText += `Invalid Emails: ${finalInvalid.textContent} (${Math.round((finalInvalid.textContent / totalEmails.textContent) * 100)}%)\n\n`; | |
| // Detailed issues | |
| reportText += `DETAILED ISSUES\n`; | |
| reportText += `---------------\n`; | |
| reportText += `- Duplicates: ${duplicateCount.textContent}\n`; | |
| reportText += `- Typos Found: ${typoCount.textContent}\n`; | |
| reportText += `- Syntax Errors: ${syntaxCount.textContent}\n`; | |
| reportText += `- MX Record Issues: ${mxCount.textContent}\n`; | |
| reportText += `- Undeliverable: ${undeliverableCount.textContent}\n`; | |
| reportText += `- Unknown Status: ${unknownCount.textContent}\n\n`; | |
| // MX Analysis | |
| reportText += `MX RECORD ANALYSIS\n`; | |
| reportText += `------------------\n`; | |
| reportText += `Emails with valid MX records: ${validMxCount.textContent} (${validMxPercent.textContent}%)\n`; | |
| reportText += `Emails with MX issues: ${invalidMxCount.textContent}\n\n`; | |
| // Top MX issues | |
| const topMxDomains = document.querySelectorAll('#top-mx-issues div'); | |
| if (topMxDomains.length > 0) { | |
| reportText += `Top Domains with MX Issues:\n`; | |
| topMxDomains.forEach(domain => { | |
| reportText += ` - ${domain.textContent.trim()}\n`; | |
| }); | |
| reportText += `\n`; | |
| } | |
| // Deliverability Analysis | |
| reportText += `DELIVERABILITY ANALYSIS\n`; | |
| reportText += `-----------------------\n`; | |
| reportText += `Confirmed Deliverable: ${deliverableCount.textContent} (${deliverablePercent.textContent}%)\n`; | |
| reportText += `Undeliverable: ${undeliverableInsightCount.textContent}\n`; | |
| reportText += `Unknown Status: ${unknownDeliverabilityCount.textContent}\n\n`; | |
| reportText += `Overall Deliverability Score: ${deliverabilityScore.textContent}/100\n`; | |
| reportText += `Rating: ${deliverabilityRating.textContent}\n`; | |
| reportText += `Description: ${deliverabilityDescription.textContent}\n\n`; | |
| // Recommendations | |
| reportText += `RECOMMENDATIONS\n`; | |
| reportText += `---------------\n`; | |
| if (parseInt(mxCount.textContent) > 0) { | |
| reportText += `1. Address MX record issues for ${mxCount.textContent} emails to improve deliverability\n`; | |
| } | |
| if (parseInt(typoCount.textContent) > 0) { | |
| reportText += `2. Review ${typoCount.textContent} emails with potential typos for correction\n`; | |
| } | |
| if (parseInt(duplicateCount.textContent) > 0) { | |
| reportText += `3. Remove ${duplicateCount.textContent} duplicate emails to maintain list quality\n`; | |
| } | |
| if (parseInt(undeliverableCount.textContent) > 0) { | |
| reportText += `4. Remove or verify ${undeliverableCount.textContent} undeliverable emails to improve sender reputation\n`; | |
| } | |
| const blob = new Blob([reportText], { type: 'text/plain;charset=utf-8' }); | |
| saveAs(blob, 'email_validation_report.txt'); | |
| } | |
| function showError(message) { | |
| uploadSection.classList.add('hidden'); | |
| processingSection.classList.add('hidden'); | |
| resultsSection.classList.add('hidden'); | |
| errorSection.classList.remove('hidden'); | |
| document.getElementById('error-message').textContent = message; | |
| } | |
| function resetForm() { | |
| uploadSection.classList.remove('hidden'); | |
| processingSection.classList.add('hidden'); | |
| resultsSection.classList.add('hidden'); | |
| errorSection.classList.add('hidden'); | |
| // Reset progress | |
| progressBar.style.width = '0%'; | |
| progressPercent.textContent = '0%'; | |
| processedCount.textContent = '0'; | |
| validCount.textContent = '0'; | |
| issuesCount.textContent = '0'; | |
| // Reset file input | |
| fileUpload.value = ''; | |
| // Clear data | |
| emailData = []; | |
| processedEmails = []; | |
| workbook = null; | |
| domainStats = {}; | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=sqibhe/email-validation" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |