Skip to content

Instantly share code, notes, and snippets.

@zach2825
Created February 12, 2026 13:34
Show Gist options
  • Select an option

  • Save zach2825/381b82600ff016529ff480ada70236e8 to your computer and use it in GitHub Desktop.

Select an option

Save zach2825/381b82600ff016529ff480ada70236e8 to your computer and use it in GitHub Desktop.
email-spams.php
<?php
namespace App\Services;
use App\Models\ContactInquiry;
use App\Models\SpamPattern;
class SpamScorer
{
public const THRESHOLD_SPAM = 70;
public const THRESHOLD_SUSPECT = 40;
private static array $disposableDomains = [
'mailinator.com', 'guerrillamail.com', 'tempmail.com', 'throwaway.email',
'yopmail.com', 'sharklasers.com', 'guerrillamailblock.com', 'grr.la',
'dispostable.com', 'mailnesia.com', 'maildrop.cc', 'discard.email',
'temp-mail.org', 'fakeinbox.com', 'trashmail.com', 'getnada.com',
'mohmal.com', '10minutemail.com', 'minutemail.com', 'tempail.com',
];
private static array $spamPhrases = [
'buy now', 'click here', 'free money', 'act now', 'limited time',
'congratulations', 'you have won', 'dear sir', 'dear madam',
'nigerian prince', 'wire transfer', 'bitcoin', 'cryptocurrency',
'make money fast', 'work from home', 'double your', 'guaranteed income',
'seo services', 'link building', 'web traffic', 'backlinks',
'casino', 'viagra', 'cialis', 'pharmacy', 'diet pills',
];
public function score(ContactInquiry $inquiry): int
{
$score = 0;
$score += $this->scoreEmail($inquiry->email);
$score += $this->scoreName($inquiry->name);
$score += $this->scoreMessage($inquiry->message);
$score += $this->scoreIpAddress($inquiry->ip_address);
$score += $this->scoreRepeatSubmissions($inquiry);
$score += $this->scoreLearnedPatterns($inquiry);
return min(100, max(0, $score));
}
public function scoreAndSave(ContactInquiry $inquiry): int
{
$score = $this->score($inquiry);
$inquiry->updateQuietly([
'spam_score' => $score,
'is_spam' => $score >= self::THRESHOLD_SPAM,
]);
return $score;
}
public function learnFromSpamReport(ContactInquiry $inquiry): void
{
$emailDomain = $this->extractDomain($inquiry->email);
if ($emailDomain) {
SpamPattern::learn('email_domain', $emailDomain, 30);
}
if ($inquiry->ip_address) {
SpamPattern::learn('ip_address', $inquiry->ip_address, 20);
}
$this->extractAndLearnPhrases($inquiry->message);
}
public function learnFromHamReport(ContactInquiry $inquiry): void
{
$emailDomain = $this->extractDomain($inquiry->email);
if ($emailDomain) {
$pattern = SpamPattern::where('type', 'email_domain')
->where('pattern', $emailDomain)
->first();
if ($pattern && $pattern->hit_count <= 1) {
$pattern->delete();
} elseif ($pattern) {
$pattern->decrement('hit_count');
}
}
if ($inquiry->ip_address) {
$pattern = SpamPattern::where('type', 'ip_address')
->where('pattern', $inquiry->ip_address)
->first();
if ($pattern && $pattern->hit_count <= 1) {
$pattern->delete();
} elseif ($pattern) {
$pattern->decrement('hit_count');
}
}
}
private function scoreEmail(string $email): int
{
$score = 0;
$domain = $this->extractDomain($email);
if (in_array($domain, self::$disposableDomains)) {
$score += 40;
}
if (preg_match('/\d{4,}/', $email)) {
$score += 10;
}
if (str_contains($email, '+')) {
$score += 5;
}
$localPart = explode('@', $email)[0] ?? '';
if (strlen($localPart) > 30) {
$score += 10;
}
if (preg_match('/^[a-z]{1,3}\d+@/i', $email)) {
$score += 15;
}
return $score;
}
private function scoreName(string $name): int
{
$score = 0;
if (preg_match('/^[a-z]+$/i', $name) && strlen($name) <= 3) {
$score += 15;
}
if (strtoupper($name) === $name && strlen($name) > 3) {
$score += 10;
}
if (preg_match('/[<>{}|\\\\]/', $name)) {
$score += 30;
}
if (preg_match('/(https?:\/\/|www\.)/i', $name)) {
$score += 40;
}
if (preg_match('/(.)\1{3,}/', $name)) {
$score += 20;
}
return $score;
}
private function scoreMessage(string $message): int
{
$score = 0;
$lowerMessage = strtolower($message);
foreach (self::$spamPhrases as $phrase) {
if (str_contains($lowerMessage, $phrase)) {
$score += 15;
}
}
$urlCount = preg_match_all('/https?:\/\//i', $message);
if ($urlCount >= 3) {
$score += 25;
} elseif ($urlCount >= 1) {
$score += 10;
}
if (preg_match_all('/[A-Z]/', $message) > strlen($message) * 0.5 && strlen($message) > 20) {
$score += 15;
}
if (strlen($message) < 15) {
$score += 10;
}
if (preg_match('/[\x{0400}-\x{04FF}\x{0600}-\x{06FF}\x{4E00}-\x{9FFF}]/u', $message) && !preg_match('/[\x{0400}-\x{04FF}\x{0600}-\x{06FF}\x{4E00}-\x{9FFF}]/u', '')) {
$score += 5;
}
return min(50, $score);
}
private function scoreIpAddress(?string $ip): int
{
if (!$ip) {
return 0;
}
$recentFromIp = ContactInquiry::where('ip_address', $ip)
->where('created_at', '>=', now()->subDay())
->count();
if ($recentFromIp > 5) {
return 30;
}
if ($recentFromIp > 2) {
return 15;
}
return 0;
}
private function scoreRepeatSubmissions(ContactInquiry $inquiry): int
{
$score = 0;
$sameEmail = ContactInquiry::where('email', $inquiry->email)
->where('id', '!=', $inquiry->id)
->where('created_at', '>=', now()->subDay())
->count();
if ($sameEmail > 3) {
$score += 30;
} elseif ($sameEmail > 1) {
$score += 15;
}
return $score;
}
private function scoreLearnedPatterns(ContactInquiry $inquiry): int
{
$score = 0;
$domain = $this->extractDomain($inquiry->email);
if ($domain) {
$pattern = SpamPattern::blacklist()
->ofType('email_domain')
->where('pattern', $domain)
->first();
if ($pattern) {
$score += min($pattern->weight * $pattern->hit_count, 50);
}
}
if ($inquiry->ip_address) {
$pattern = SpamPattern::blacklist()
->ofType('ip_address')
->where('pattern', $inquiry->ip_address)
->first();
if ($pattern) {
$score += min($pattern->weight * $pattern->hit_count, 40);
}
}
$phrasePatterns = SpamPattern::blacklist()->ofType('phrase')->get();
$lowerMessage = strtolower($inquiry->message);
foreach ($phrasePatterns as $pattern) {
if (str_contains($lowerMessage, strtolower($pattern->pattern))) {
$score += min($pattern->weight * $pattern->hit_count, 30);
}
}
return $score;
}
private function extractDomain(string $email): ?string
{
$parts = explode('@', $email);
return count($parts) === 2 ? strtolower($parts[1]) : null;
}
private function extractAndLearnPhrases(string $message): void
{
$words = str_word_count(strtolower($message), 1);
$wordCount = count($words);
if ($wordCount < 3) {
return;
}
for ($i = 0; $i < $wordCount - 1; $i++) {
$bigram = $words[$i].' '.$words[$i + 1];
if (strlen($bigram) >= 6) {
SpamPattern::learn('phrase', $bigram, 10);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment