Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Check
on:
schedule:
- cron: "5 15 * * *"
workflow_dispatch:
pull_request:
push:
branches:
- main

jobs:
cs:
if: github.event_name != 'schedule'
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Run CS
uses: shopware/github-actions/extension-verifier@main
with:
action: format

check:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
version-selection: [ 'lowest', 'highest']
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Run Check
uses: shopware/github-actions/extension-verifier@main
with:
action: check
check-against: ${{ matrix.version-selection }}
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# FroshMailValidation
# FroshMailAddressTester

This plugin for Shopware 6 validates email addresses during customer registration and checkout processes to ensure the mailbox is accessible and potentially valid.
This plugin for Shopware 6 tests email address during customer registration and checkout processes to ensure the mailbox is accessible and potentially valid.

## Installation

### Via Composer

```bash
composer require frosh/mail-validation
composer require frosh/mail-address-tester
```

```bash
bin/console plugin:refresh
bin/console plugin:install --activate FroshMailValidation
bin/console plugin:install --activate FroshMailAddressTester
bin/console cache:clear
```

## Support

- **GitHub Issues**: [https://github.com/FriendsOfShopware/FroshMailValidation/issues](https://github.com/FriendsOfShopware/FroshMailValidation/issues)
- **GitHub Issues**: [https://github.com/FriendsOfShopware/FroshMailAddressTester/issues](https://github.com/FriendsOfShopware/FroshMailAddressTester/issues)

## License

Expand Down
20 changes: 10 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "frosh/mail-validation",
"name": "frosh/mail-address-tester",
"description": "Validates email addresses by checking their actual existence on the target mail server. Reduces bounces, fake addresses, and sustainably improves deliverability.",
"type": "shopware-platform-plugin",
"version": "1.0.0",
Expand All @@ -10,32 +10,32 @@
],
"require": {
"shopware/core": "~6.7",
"shyim/check-if-email-exists": "^1.0"
"shyim/check-if-email-exists": "^1.0.2"
},
"license": "MIT",
"autoload": {
"psr-4": {
"Frosh\\MailValidation\\": "src/"
"Frosh\\MailAddressTester\\": "src/"
}
},
"extra": {
"shopware-plugin-class": "Frosh\\MailValidation\\FroshMailValidation",
"shopware-plugin-class": "Frosh\\MailAddressTester\\FroshMailAddressTester",
"copyright": "FriendsOfShopware",
"label": {
"de-DE": "E-Mail-Validierung durch Überprüfung der Existenz",
"en-GB": "MailValidation by checking existence"
"de-DE": "E-Mail-Adressen-Tester",
"en-GB": "Mail address tester"
},
"description": {
"de-DE": "Validiert E-Mail-Adressen durch Prüfung der tatsächlichen Existenz beim Zielserver. Reduziert Bounces, Fake-Adressen und verbessert nachhaltig die Zustellbarkeit.",
"en-GB": "Validates email addresses by checking their actual existence on the target mail server. Reduces bounces, fake addresses, and sustainably improves deliverability."
},
"manufacturerLink": {
"de-DE": "https://github.com/FriendsOfShopware/FroshMailValidation/",
"en-GB": "https://github.com/FriendsOfShopware/FroshMailValidation/"
"de-DE": "https://github.com/FriendsOfShopware/FroshMailAddressTester/",
"en-GB": "https://github.com/FriendsOfShopware/FroshMailAddressTester/"
},
"supportLink": {
"de-DE": "https://github.com/FriendsOfShopware/FroshMailValidation/",
"en-GB": "https://github.com/FriendsOfShopware/FroshMailValidation/"
"de-DE": "https://github.com/FriendsOfShopware/FroshMailAddressTester/",
"en-GB": "https://github.com/FriendsOfShopware/FroshMailAddressTester/"
}
},
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion src/Constraint/TestEmail.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace Frosh\MailValidation\Constraint;
namespace Frosh\MailAddressTester\Constraint;

use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
Expand Down
10 changes: 5 additions & 5 deletions src/Constraint/TestEmailValidator.php
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<?php declare(strict_types=1);

namespace Frosh\MailValidation\Constraint;
namespace Frosh\MailAddressTester\Constraint;

use Frosh\MailValidation\Service\Validator;
use Frosh\MailAddressTester\Service\Tester;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

#[AutoconfigureTag('monolog.logger', ['channel' => 'frosh-mail-validation'])]
#[AutoconfigureTag('monolog.logger', ['channel' => 'frosh_mail_tester'])]
#[AutoconfigureTag(name: 'validator.constraint_validator')]
class TestEmailValidator extends ConstraintValidator
{
public function __construct(
private readonly Validator $emailValidator,
private readonly Tester $emailAddressTester,
) {
}

Expand All @@ -26,7 +26,7 @@ public function validate(mixed $value, Constraint $constraint): void
return;
}

if ($this->emailValidator->validateEmail($value)) {
if ($this->emailAddressTester->validateEmail($value)) {
return;
}

Expand Down
39 changes: 39 additions & 0 deletions src/FroshMailAddressTester.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php declare(strict_types=1);

namespace Frosh\MailAddressTester;

use Shopware\Core\Framework\Plugin;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\DelegatingLoader;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\DirectoryLoader;
use Symfony\Component\DependencyInjection\Loader\GlobFileLoader;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

class FroshMailAddressTester extends Plugin
{
public function build(ContainerBuilder $container): void
{
parent::build($container);

$locator = new FileLocator('Resources/config');

$resolver = new LoaderResolver([
new YamlFileLoader($container, $locator),
new GlobFileLoader($container, $locator),
new DirectoryLoader($container, $locator),
]);

$configLoader = new DelegatingLoader($resolver);

$confDir = \rtrim($this->getPath(), '/') . '/Resources/config';

$configLoader->load($confDir . '/{packages}/*.yaml', 'glob');
}

public function executeComposerCommands(): bool
{
return true;
}
}
13 changes: 0 additions & 13 deletions src/FroshMailValidation.php

This file was deleted.

35 changes: 32 additions & 3 deletions src/Resources/config/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,42 @@
<title>Basic Configuration</title>
<title lang="de-DE">Grundeinstellungen</title>

<input-field type="single-select">
<name>level</name>
<label>Level of test</label>
<label lang="de-DE">Level des Tests</label>
<defaultValue>simple</defaultValue>
<helpText><![CDATA[
<strong>simple</strong>: just checks if the domain has an announced mail server (MX record)
<br/>
<strong>SMTP</strong>: checks if an email would be deliverable by connecting to the mail server and simulating a mail delivery without really sending. Diese Prüfung dauert länger. Make sure the email address provided in the next field is valid, otherwise, some checks will be false-negative.
]]></helpText>
<helpText lang="de-DE"><![CDATA[
<strong>Einfach</strong>: Es wird lediglich geprüft, ob für die Domain ein bekanntgegebener Mailserver (MX-Eintrag) existiert.
<br/>
<strong>SMTP</strong>: Diese Option prüft die Zustellbarkeit einer E-Mail, indem sie eine Verbindung zum Mailserver herstellt und die Zustellung simuliert, ohne die E-Mail tatsächlich zu senden. Diese Prüfung dauert länger. Stellen Sie sicher, dass die im nächsten Feld angegebene E-Mail-Adresse gültig ist, da es sonst zu Fehlalarmen kommen kann.
]]></helpText>
<options>
<option>
<id>simple</id>
<name>simple</name>
<name lang="de-DE">einfach</name>
</option>
<option>
<id>smtp</id>
<name>smtp</name>
<name lang="de-DE">SMTP</name>
</option>
</options>
</input-field>

<input-field>
<name>verifyEmail</name>
<label>email address to be used for the validator</label>
<label>email address to be used for the smtp test</label>
<label lang="de-DE">E-Mail-Adresse für die Validierung </label>
<defaultValue>verify@example.com</defaultValue>
<helpText>This is important to be a valid mail domain. Otherwise, some checks will be false-positive.</helpText>
<helpText lang="de-DE">Es ist wichtig, dass es sich um eine gültige E-Mail-Domain handelt. Andernfalls liefern einige Prüfungen falsch-positive Ergebnisse.</helpText>
<helpText>This is important to be a valid mail domain. Otherwise, some checks will be false-negative.</helpText>
<helpText lang="de-DE">Es ist wichtig, dass es sich um eine gültige E-Mail-Domain handelt. Andernfalls liefern einige Prüfungen falsch-negative Ergebnisse.</helpText>
</input-field>
</card>
</config>
10 changes: 10 additions & 0 deletions src/Resources/config/packages/monolog.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
monolog:
channels: ['frosh_mail_tester']

handlers:
FroshMailTesterHandler:
type: rotating_file
max_files: 14
path: '%kernel.logs_dir%/frosh_mail_tester_%kernel.environment%.log'
level: error
channels: ['frosh_mail_tester']
4 changes: 2 additions & 2 deletions src/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ services:
_defaults:
autowire: true
autoconfigure: true
Frosh\MailValidation\:
Frosh\MailAddressTester\:
resource: '../../*'
exclude: '../../{Resources,FroshMailValidation.php}'
exclude: '../../{Resources,FroshMailAddressTester.php}'
111 changes: 111 additions & 0 deletions src/Service/Tester.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php declare(strict_types=1);

namespace Frosh\MailAddressTester\Service;

use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface;
use Shopware\Core\Framework\Util\Hasher;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shyim\CheckIfEmailExists\DNS;
use Shyim\CheckIfEmailExists\SMTP;
use Shyim\CheckIfEmailExists\Syntax;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class Tester
{
public function __construct(
private readonly SystemConfigService $systemConfigService,
#[Autowire('cache.app')]
private readonly CacheItemPoolInterface $cache,
private readonly LoggerInterface $froshMailAddressTesterLogger,
) {
}

public function validateEmail(string $email): bool
{
$email = \strtolower($email);

$mailValidCache = $this->getCacheItem($email);
$mailValidCacheResult = $mailValidCache->get();
if (\is_bool($mailValidCacheResult)) {
return $mailValidCacheResult;
}

$syntaxResult = new Syntax($email);
if ($syntaxResult->isValid() === false) {
return false;
}

$domain = $syntaxResult->domain;

$domainValidCache = $this->getCacheItem($domain);
// first check if the domain is already marked as invalid
if ($domainValidCache->get() === false) {
return false;
}

$mxRecords = (new DNS())->getMxRecords($domain);

if (empty($mxRecords)) {
$this->saveCache($domainValidCache, false);

$this->froshMailAddressTesterLogger->error(\sprintf('Domain %s has no mx records', $domain));

return false;
}

if ($this->systemConfigService->getString('FroshMailAddressTester.config.level') !== 'smtp') {
return true;
}

$verifyEmail = $this->systemConfigService->getString('FroshMailAddressTester.config.verifyEmail');
if ($verifyEmail !== '') {
$smtpCheck = new SMTP($verifyEmail);
} else {
$smtpCheck = new SMTP();
}

$smtpResult = $smtpCheck->check($domain, $mxRecords, $email);
$this->saveCache($domainValidCache, $smtpResult->canConnect);

if ($smtpResult->canConnect === false) {
$this->froshMailAddressTesterLogger->error($smtpResult->error);

return false;
}

$isValid = $smtpResult->isDeliverable === true && $smtpResult->isDisabled === false && $smtpResult->hasFullInbox === false;

$this->saveCache($mailValidCache, $isValid);

if ($isValid === false) {
$this->froshMailAddressTesterLogger->error(
\sprintf('Email address "%s" test failed', $email),
json_decode(json_encode($smtpResult, \JSON_THROW_ON_ERROR), true, 1, \JSON_THROW_ON_ERROR)
);
}

return $isValid;
}

private function getCacheItem(string $value): CacheItemInterface
{
$cacheKey = 'frosh_mail_tester_' . Hasher::hash($value);

return $this->cache->getItem($cacheKey);
}

private function saveCache(CacheItemInterface $item, bool $value): void
{
$cacheTime = 3600;

if ($value === true) {
$cacheTime = 86400; // one day for valid emails
}

$item->expiresAfter($cacheTime);
$item->set($value);
$this->cache->save($item);
}
}
Loading
Loading