Čisti uvozi newslettera: izvorni PHP 8.3 čisti e-poruke, označava nesigurne za pregled
Managing a newsletter list is a fundamental task for many online businesses and creators. However, the quality of your contact data directly impacts your deliverability rates, sender reputation, and overall marketing effectiveness. Dirty data, such as mistyped email addresses or defunct domains, can lead to bounces, spam complaints, and wasted resources. This tutorial demonstrates how to leverage a powerful email validation API within a native PHP 8.3+ application to clean incoming newsletter imports and intelligently flag uncertain addresses for manual review, ensuring a more robust and reliable contact database.
Streamlining Newsletter Imports with Advanced Email Validation
As your audience grows, so does the volume of new subscribers. Manually vetting each email address is impractical. Automating this process with a sophisticated email validation service can save significant time and prevent common pitfalls. We'll integrate an API that goes beyond simple syntax checks, examining domain health, MX records, and even provider signals to offer a comprehensive risk assessment for each email address. This allows us to make informed decisions about which contacts to accept immediately, which to flag for a closer look, and which to reject outright.
The Mihajlo AI Email Validator Service
The service we'll be using for this integration is the Mihajlo AI Email Validator. It's designed to provide deep insights into email address validity, helping to reduce bounces and improve engagement. The API offers a free tier, making it accessible for development and small-scale projects, with scalable paid plans for higher volumes.
Getting Access to the Email Validator API
To begin using the Email Validator API, you'll need to register for an account and obtain a service token. Follow these steps:
- Register for an account at https://ai.mihajlo.mk/register or sign in if you already have one at https://ai.mihajlo.mk/login.
- Navigate to the Email Validator service page: https://ai.mihajlo.mk/api/email-validator.
- Choose an available plan (e.g., Free, Plus, or Pro) and complete the activation process.
- Once your plan is active, visit the official documentation at https://ai.mihajlo.mk/api/email-validator/documentation.
- Locate the "Service token" panel. Copy your unique, service-scoped token. Remember that regenerating this token will revoke the previous active one.
It's crucial to store this service token securely. For production applications, use environment variables. Never commit your actual token directly into your codebase. For this tutorial, we'll assume you'll store it in a .env file.
API Endpoint and Authentication
The API endpoint for checking an email address is:
GET https://ai.mihajlo.mk/api/email-validator/v1/check-email
Authentication is handled via a query parameter:
token={serviceToken}
The primary request parameter is:
email: The email address to validate.
The response structure contains key fields such as status, score, recommendation, checks, and quota, which we will use to make application decisions.
Project Setup and Architecture
We will build a command-line application using native PHP 8.3+ to simulate processing a CSV import file. The architecture will involve:
- A configuration file to store the API token.
- A dedicated service class to encapsulate the API interaction.
- A command-line script to orchestrate the import process.
- A simple CSV file representing the newsletter import.
This approach keeps the integration focused and easy to understand, avoiding unnecessary framework dependencies for this specific task.
Project Structure
Let's set up a basic project directory:
newsletter-importer/
├── config/
│ └── api.php
├── src/
│ ├── Api/
│ │ └── EmailValidatorService.php
│ └── Console/
│ └── ImportNewsletterCommand.php
├── vendor/
├── .env
├── composer.json
└── import_contacts.csv
Environment Configuration
Create a .env file in the root of your project:
EMAIL_VALIDATOR_API_TOKEN=YOUR_MIHAJLO_API_TOKEN
Replace YOUR_MIHAJLO_API_TOKEN with the actual token you copied from the Mihajlo API documentation.
API Configuration
Create config/api.php to load the API token:
<?php
declare(strict_types=1);
function getenv_string(string $key, string $default = ''): string
{
$value = getenv($key);
if ($value === false || $value === '') {
return $default;
}
return $value;
}
return [
'mihajlo_email_validator' => [
'api_token' => getenv_string('EMAIL_VALIDATOR_API_TOKEN'),
'api_url' => 'https://ai.mihajlo.mk/api/email-validator/v1/check-email',
'timeout' => 10.0, // Connection and read timeout in seconds
],
];
Email Validator Service Class
This class will handle all communication with the Mihajlo AI Email Validator API. We'll use PHP's built-in cURL extension for making HTTP requests, which is a standard and reliable choice for native PHP applications. We'll implement bounded timeouts and basic error handling.
Create src/Api/EmailValidatorService.php:
<?php
declare(strict_types=1);
namespace App\Api;
use Exception;
use InvalidArgumentException;
class EmailValidatorService
{
private string $apiUrl;
private string $apiToken;
private float $timeout;
public function __construct(string $apiUrl, string $apiToken, float $timeout = 10.0)
{
if (empty($apiToken)) {
throw new InvalidArgumentException('Email validation API token is not configured.');
}
$this->apiUrl = $apiUrl;
$this->apiToken = $apiToken;
$this->timeout = $timeout;
}
/**
* Checks a single email address using the Mihajlo AI Email Validator API.
*
* @param string $email The email address to validate.
* @return array|null The validation result array, or null on critical failure.
* @throws Exception If an unrecoverable API communication error occurs.
*/
public function checkEmail(string $email): ?array
{
if (empty($email)) {
return null; // Or throw an exception, depending on desired strictness
}
$url = $this->apiUrl . '?email=' . urlencode($email) . '&token=' . urlencode($this->apiToken);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (int) $this->timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, (int) $this->timeout);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json',
]);
curl_setopt($ch, CURLOPT_FAILONERROR, true); // Fail on HTTP errors >= 400
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($response === false) {
// Log this error for observability
error_log("Email validation API communication error: {$curlError} for email {$email}");
throw new Exception("Failed to communicate with email validation service: {$curlError}");
}
if ($httpCode >= 400) {
// Log this error for observability
error_log("Email validation API returned HTTP error {$httpCode} for email {$email}. Response: {$response}");
// Depending on the error code, you might want to retry or flag for review
// For simplicity, we'll treat it as a critical failure to validate for now.
throw new Exception("Email validation service returned an error (HTTP {$httpCode}) for email {$email}.");
}
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
// Log this error for observability
error_log("Email validation API returned invalid JSON for email {$email}. Response: {$response}");
throw new Exception("Invalid JSON response from email validation service for email {$email}.");
}
// Basic validation of the response structure
if (!isset($data['status']) || !isset($data['score']) || !isset($data['recommendation'])) {
// Log this error for observability
error_log("Email validation API response missing required keys for email {$email}. Response: " . print_r($data, true));
throw new Exception("Unexpected response structure from email validation service for email {$email}.");
}
return $data;
}
}
Import Command-Line Script
This script will read a CSV file, process each email address using the EmailValidatorService, and categorize contacts based on the validation results. We'll simulate storing valid emails and flagging uncertain ones.
Create src/Console/ImportNewsletterCommand.php:
<?php
declare(strict_types=1);
namespace App\Console;
use App\Api\EmailValidatorService;
use Exception;
use RuntimeException;
class ImportNewsletterCommand
{
private EmailValidatorService $emailValidatorService;
private array $config;
public function __construct(EmailValidatorService $emailValidatorService, array $config)
{
$this->emailValidatorService = $emailValidatorService;
$this->config = $config;
}
public function run(string $csvFilePath): void
{
if (!file_exists($csvFilePath) || !is_readable($csvFilePath)) {
throw new RuntimeException("CSV file not found or not readable: {$csvFilePath}");
}
$handle = fopen($csvFilePath, 'r');
if ($handle === false) {
throw new RuntimeException("Could not open CSV file for reading: {$csvFilePath}");
}
$headers = fgetcsv($handle);
if ($headers === false) {
fclose($handle);
throw new RuntimeException("Could not read CSV headers from: {$csvFilePath}");
}
// Find the column index for the email address
$emailColumnIndex = array_search('email', array_map('strtolower', $headers));
if ($emailColumnIndex === false) {
fclose($handle);
throw new RuntimeException("CSV file must contain an 'email' column.");
}
$validContacts = [];
$uncertainContacts = [];
$invalidContacts = [];
$processedCount = 0;
$errorCount = 0;
echo "Starting newsletter import from {$csvFilePath}...\n";
while (($row = fgetcsv($handle)) !== false) {
$processedCount++;
if (!isset($row[$emailColumnIndex])) {
echo "Skipping row {$processedCount}: missing email value.\n";
$invalidContacts[] = ['row' => $processedCount, 'reason' => 'Missing email value'];
continue;
}
$email = trim($row[$emailColumnIndex]);
if ($email === '') {
echo "Skipping row {$processedCount}: empty email value.\n";
$invalidContacts[] = ['row' => $processedCount, 'reason' => 'Empty email value'];
continue;
}
try {
$validationResult = $this->emailValidatorService->checkEmail($email);
if ($validationResult === null) {
// This can happen if the email was empty after trim, or if checkEmail returned null for some reason
echo "Row {$processedCount}: Email '{$email}' is empty or invalid after trimming. Skipping.\n";
$invalidContacts[] = ['row' => $processedCount, 'email' => $email, 'reason' => 'Empty after trim'];
continue;
}
$recommendation = $validationResult['recommendation'] ?? 'unknown';
// Decision logic based on recommendation
switch ($recommendation) {
case 'deliverable':
$validContacts[] = ['email' => $email, 'details' => $validationResult];
echo "Row {$processedCount}: Email '{$email}' is deliverable.\n";
break;
case 'uncertain':
$uncertainContacts[] = ['email' => $email, 'details' => $validationResult];
echo "Row {$processedCount}: Email '{$email}' is uncertain. Flagging for review.\n";
break;
case 'undeliverable':
default: // Includes 'invalid', 'risky', etc.
$invalidContacts[] = ['email' => $email, 'details' => $validationResult];
echo "Row {$processedCount}: Email '{$email}' is undeliverable or invalid. Discarding.\n";
break;
}
} catch (Exception $e) {
$errorCount++;
// Log the exception for deeper analysis
error_log("Error processing email '{$email}' at row {$processedCount}: {$e->getMessage()}");
echo "Row {$processedCount}: Error validating email '{$email}'. Reason: {$e->getMessage()}. Logging for review.\n";
// We can treat API errors as uncertain or simply log and skip based on policy
$uncertainContacts[] = ['email' => $email, 'details' => ['error' => $e->getMessage()]];
}
}
fclose($handle);
echo "\n--- Import Summary ---\n";
echo "Processed Rows: {$processedCount}\n";
echo "Valid Contacts: " . count($validContacts) . "\n";
echo "Uncertain Contacts (for review): " . count($uncertainContacts) . "\n";
echo "Invalid/Discarded Contacts: " . count($invalidContacts) . "\n";
if ($errorCount > 0) {
echo "API/Processing Errors: {$errorCount}\n";
}
echo "----------------------\n";
// In a real application, you would now:
// 1. Insert $validContacts into your primary contact database.
// 2. Store $uncertainContacts in a separate table or queue for manual review.
// 3. Log $invalidContacts for auditing or potential cleanup.
// 4. Handle quota limits if the API response indicates them.
if (!empty($uncertainContacts)) {
echo "\nUncertain contacts to review:\n";
foreach ($uncertainContacts as $contact) {
echo "- Email: " . ($contact['email'] ?? 'N/A') . "\n";
if (isset($contact['details']['error'])) {
echo " Reason: API error - " . $contact['details']['error'] . "\n";
} else {
echo " Details: Status=" . ($contact['details']['status'] ?? 'N/A')
. ", Score=" . ($contact['details']['score'] ?? 'N/A')
. ", Recommendation=" . ($contact['details']['recommendation'] ?? 'N/A') . "\n";
}
}
}
}
}
Composer and Autoloader Setup
We need composer.json to manage our dependencies (though for this basic example, we're relying on PHP's built-in cURL). We'll set up PSR-4 autoloading.
Create composer.json:
{
"name": "mihajlo/newsletter-importer",
"description": "Newsletter contact import cleaner using Email Validator API",
"type": "project",
"license": "MIT",
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"require": {
"php": "^8.3"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"scripts": {
"import": "php bin/import-newsletter.php"
}
}
Run composer install in your project root.
Bootstrap Script
Create a bootstrap script (e.g., bin/import-newsletter.php) to initialize the application, load configuration, and run the command.
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
// Load environment variables from .env file
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
// Load configuration
$config = require __DIR__ . '/../config/api.php';
// Instantiate the Email Validator Service
try {
$emailValidatorService = new App\Api\EmailValidatorService(
$config['mihajlo_email_validator']['api_url'],
$config['mihajlo_email_validator']['api_token'],
$config['mihajlo_email_validator']['timeout']
);
} catch (InvalidArgumentException $e) {
echo "Configuration Error: " . $e->getMessage() . "\n";
exit(1);
}
// Instantiate the Import Command
$importCommand = new App\Console\ImportNewsletterCommand($emailValidatorService, $config);
// --- Define the input CSV file path ---
// In a real app, this might come from CLI arguments or a web form
$csvFilePath = __DIR__ . '/../import_contacts.csv';
// --- Run the import process ---
try {
$importCommand->run($csvFilePath);
exit(0); // Success
} catch (RuntimeException $e) {
echo "Import Error: " . $e->getMessage() . "\n";
exit(1); // Failure
} catch (Exception $e) {
echo "An unexpected error occurred: " . $e->getMessage() . "\n";
exit(1); // Failure
}
Sample Import File
Create a sample CSV file named import_contacts.csv in your project root:
Name,Email,SubscriptionDate
Alice Smith,[email protected],2023-10-26
Bob Johnson,[email protected],2023-10-26
Charlie Brown,[email protected],2023-10-26
David Lee,[email protected],2023-10-26
Eve Adams,[email protected],2023-10-26
Frank White,[email protected],2023-10-26
Grace Hall,[email protected],2023-10-26
Henry Black,[email protected],2023-10-26
Ivy Green,[email protected],2023-10-26
Jack Blue,[email protected],2023-10-26
Bad Email,not-an-email-address,2023-10-26
Empty Email,,2023-10-26
Test User,[email protected],2023-10-26
Another Bad,[email protected],2023-10-26
Uncertain User,[email protected],2023-10-26
Syntax Error,malformed-email@,2023-10-26
Running the Import
Now you can run the import process from your project's root directory using Composer's script alias:
composer import
You should see output similar to this, indicating which emails are deliverable, uncertain, or invalid:
Loading environment variables from .env file...
Starting newsletter import from /path/to/your/project/import_contacts.csv...
Row 1: Email '[email protected]' is deliverable.
Row 2: Email '[email protected]' is undeliverable or invalid. Discarding.
Row 3: Email '[email protected]' is deliverable.
Row 4: Email '[email protected]' is deliverable.
Row 5: Email '[email protected]' is deliverable.
Row 6: Email '[email protected]' is uncertain. Flagging for review.
Row 7: Email '[email protected]' is deliverable.
Row 8: Email '[email protected]' is deliverable.
Row 9: Email '[email protected]' is deliverable.
Row 10: Email '[email protected]' is deliverable.
Row 11: Email 'not-an-email-address' is undeliverable or invalid. Discarding.
Row 12: Skipping row 12: empty email value.
Row 13: Email '[email protected]' is deliverable.
Row 14: Email '[email protected]' is undeliverable or invalid. Discarding.
Row 15: Email '[email protected]' is uncertain. Flagging for review.
Row 16: Email 'malformed-email@' is undeliverable or invalid. Discarding.
--- Import Summary ---
Processed Rows: 16
Valid Contacts: 6
Uncertain Contacts (for review): 3
Invalid/Discarded Contacts: 7
----------------------
Uncertain contacts to review:
- Email: [email protected]
Details: Status=validated, Score=0.7, Recommendation=uncertain
- Email: [email protected]
Details: Status=validated, Score=0.7, Recommendation=uncertain
- Email: N/A
Reason: Missing email value
Note that the "Empty Email" row is handled by the CSV parsing and the check for empty values after trimming. The "Syntax Error" and "Bad Email" are likely caught by the API's syntax checks.
Automated Testing
Robust testing is essential for production integrations. We'll use PHPUnit to test our EmailValidatorService. To avoid actual API calls during tests, we'll use a deterministic fake transport for cURL.
First, install PHPUnit if you haven't already:
composer require --dev phpunit/phpunit
Create a test file tests/EmailValidatorServiceTest.php:
<?php
declare(strict_types=1);
namespace Tests;
use App\Api\EmailValidatorService;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use Exception;
// A simple mock for cURL responses
class CurlMock
{
private array $responses;
private int $callCount = 0;
public function __construct(array $responses)
{
$this->responses = $responses;
}
public function __call(string $name, array $arguments)
{
if ($name === 'exec') {
if (!isset($this->responses[$this->callCount])) {
throw new Exception("Mock cURL: No response defined for call count {$this->callCount}");
}
return $this->responses[$this->callCount++];
}
// Simulate setting options - we don't need to assert them for this simple mock
return true;
}
public function getCallCount(): int
{
return $this->callCount;
}
}
class EmailValidatorServiceTest extends TestCase
{
private const TEST_API_URL = 'https://ai.mihajlo.mk/api/email-validator/v1/check-email';
private const TEST_API_TOKEN = 'test_token';
private const TEST_TIMEOUT = 5.0;
/**
* @runInSeparateProcess // Important if mocking global functions like curl_init
*/
public function testCheckEmailSuccessDeliverable(): void
{
$mockResponse = json_encode([
'email' => '[email protected]',
'status' => 'validated',
'score' => 0.9,
'recommendation' => 'deliverable',
'checks' => ['syntax' => true, 'domain' => true, 'mx' => true, 'provider' => true, 'disposable' => false],
'quota' => ['used' => 10, 'total' => 1000]
]);
$curlMock = new CurlMock([$mockResponse]);
$this->mockCurlExec($curlMock);
$service = new EmailValidatorService(self::TEST_API_URL, self::TEST_API_TOKEN, self::TEST_TIMEOUT);
$result = $service->checkEmail('[email protected]');
$this->assertNotNull($result);
$this->assertEquals('validated', $result['status']);
$this->assertEquals('deliverable', $result['recommendation']);
$this->assertEquals(1, $curlMock->getCallCount());
}
/**
* @runInSeparateProcess
*/
public function testCheckEmailSuccessUncertain(): void
{
$mockResponse = json_encode([
'email' => '[email protected]',
'status' => 'validated',
'score' => 0.7,
'recommendation' => 'uncertain',
'checks' => ['syntax' => true, 'domain' => true, 'mx' => true, 'provider' => false, 'disposable' => false],
'quota' => ['used' => 11, 'total' => 1000]
]);
$curlMock = new CurlMock([$mockResponse]);
$this->mockCurlExec($curlMock);
$service = new EmailValidatorService(self::TEST_API_URL, self::TEST_API_TOKEN, self::TEST_TIMEOUT);
$result = $service->checkEmail('[email protected]');
$this->assertNotNull($result);
$this->assertEquals('uncertain', $result['recommendation']);
$this->assertEquals(1, $curlMock->getCallCount());
}
/**
* @runInSeparateProcess
*/
public function testCheckEmailSuccessUndeliverable(): void
{
$mockResponse = json_encode([
'email' => '[email protected]',
'status' => 'invalid',
'score' => 0.1,
'recommendation' => 'undeliverable',
'checks' => ['syntax' => true, 'domain' => false, 'mx' => false, 'provider' => false, 'disposable' => false],
'quota' => ['used' => 12, 'total' => 1000]
]);
$curlMock = new CurlMock([$mockResponse]);
$this->mockCurlExec($curlMock);
$service = new EmailValidatorService(self::TEST_API_URL, self::TEST_API_TOKEN, self::TEST_TIMEOUT);
$result = $service->checkEmail('[email protected]');
$this->assertNotNull($result);
$this->assertEquals('undeliverable', $result['recommendation']);
$this->assertEquals(1, $curlMock->getCallCount());
}
/**
* @runInSeparateProcess
*/
public function testCheckEmailEmptyEmailReturnsNull(): void
{
$service = new EmailValidatorService(self::TEST_API_URL, self::TEST_API_TOKEN, self::TEST_TIMEOUT);
$result = $service->checkEmail('');
$this->assertNull($result);
}
/**
* @runInSeparateProcess
*/
public function testCheckEmailApiCommunicationErrorThrowsException(): void
{
$curlMock = new CurlMock([false]); // Simulate curl_exec returning false
$this->mockCurlExec($curlMock);
$service = new EmailValidatorService(self::TEST_API_URL, self::TEST_API_TOKEN, self::TEST_TIMEOUT);
$this->expectException(Exception::class);
$this->expectExceptionMessage('Failed to communicate with email validation service: '); // Partial message check
$service->checkEmail('[email protected]');
}
/**
* @runInSeparateProcess
*/
public function testCheckEmailInvalidJsonResponseThrowsException(): void
{
$curlMock = new CurlMock(['This is not JSON']);
$this->mockCurlExec($curlMock);
$service = new EmailValidatorService(self::TEST_API_URL, self::TEST_API_TOKEN, self::TEST_TIMEOUT);
$this->expectException(Exception::class);
$this->expectExceptionMessage('Invalid JSON response from email validation service');
$service->checkEmail('[email protected]');
}
/**
* @runInSeparateProcess
*/
public function testCheckEmailMissingRequiredKeysThrowsException(): void
{
$mockResponse = json_encode(['partial' => 'data']); // Missing status, score, recommendation
$curlMock = new CurlMock([$mockResponse]);
$this->mockCurlExec($curlMock);
$service = new EmailValidatorService(self::TEST_API_URL, self::TEST_API_TOKEN, self::TEST_TIMEOUT);
$this->expectException(Exception::class);
$this->expectExceptionMessage('Unexpected response structure');
$service->checkEmail('[email protected]');
}
/**
* @runInSeparateProcess
*/
public function testConstructorThrowsExceptionIfTokenMissing(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Email validation API token is not configured.');
new EmailValidatorService(self::TEST_API_URL, '', self::TEST_TIMEOUT);
}
/**
* Helper to mock global curl functions.
* This is a simplified approach for demonstration. Real-world mocking might use a dedicated library.
*/
private function mockCurlExec(CurlMock $curlMock): void
{
// We need to mock curl_init, curl_setopt, curl_exec, curl_getinfo, curl_error, curl_close
// For simplicity, we'll only mock the essential ones for exec and error reporting.
// In a more complex scenario, you'd mock all of them.
// Mock curl_init to return a handle that our CurlMock can intercept
$mockHandle = new \stdClass(); // A dummy object to represent a cURL handle
$GLOBALS['mock_curl_handle'] = $mockHandle;
$GLOBALS['mock_curl_responses'] = $curlMock;
// Mock specific curl functions
if (!function_exists('curl_init')) {
eval('function curl_init() { return $GLOBALS["mock_curl_handle"]; }');
}
if (!function_exists('curl_setopt')) {
eval('function curl_setopt($ch, $option, $value) { return true; }'); // Assume options are set
}
if (!function_exists('curl_exec')) {
eval('function curl_exec($ch) {
if ($ch === $GLOBALS["mock_curl_handle"]) {
return $GLOBALS["mock_curl_responses"]->exec(null); // Pass null as arg for exec
}
return false; // Fallback for unexpected handles
}');
}
if (!function_exists('curl_getinfo')) {
eval('function curl_getinfo($ch, $option) {
if ($ch === $GLOBALS["mock_curl_handle"] && $option === CURLINFO_HTTP_CODE) {
// Assuming a successful response for this mock scenario
// In a real mock, you'd need to parse response or define HTTP codes
return 200;
}
return null;
}');
}
if (!function_exists('curl_error')) {
eval('function curl_error($ch) {
if ($ch === $GLOBALS["mock_curl_handle"]) {
return "Mocked cURL error";
}
return "";
}');
}
if (!function_exists('curl_close')) {
eval('function curl_close($ch) { /* no-op */ }');
}
}
}
Run the tests:
vendor/bin/phpunit tests/EmailValidatorServiceTest.php
Security Considerations
- API Token Security: As emphasized, never hardcode your API token. Use environment variables and ensure these files are not committed to version control.
- Input Sanitization: While the API validates the email format, always ensure that data coming into your application (e.g., from CSV files) is handled safely. The
trim()in the command script is a basic step. - Rate Limiting and Quotas: The API response includes quota information. Your application should monitor this and handle cases where you might exceed limits. Implement appropriate retry strategies with exponential backoff for temporary rate limits, but be aware that persistent limits might require upgrading your plan.
Observability and Error Handling
- Logging: Critical errors (API communication failures, invalid JSON, unexpected responses) are logged using PHP's
error_log(). In a production environment, you'd integrate this with a more robust logging system (e.g., Monolog, ELK stack, cloud logging services). - Structured Failures: The command script categorizes emails into
validContacts,uncertainContacts, andinvalidContacts. This structured output allows for different follow-up actions. API errors during processing are treated as uncertain, prompting a review rather than immediate discarding. - Timeouts: The
timeoutparameter in the service class (configured viaconfig/api.php) prevents the application from hanging indefinitely if the API is unresponsive.
Deployment Notes
- Ensure the
.envfile is correctly configured on your production server. - The PHP version must be 8.3 or higher.
- The cURL extension must be enabled for PHP.
- The script is designed to be run from the command line. For web applications, you would typically trigger this process via a background job (e.g., using a queue system like Laravel Queues or Symfony Messenger) or a scheduled task.
Common Failures and Troubleshooting
- API Token Issues: Ensure the token is correctly copied and that the
EMAIL_VALIDATOR_API_TOKENenvironment variable is set. Check the Mihajlo API dashboard to confirm your plan is active. - Network Connectivity: Verify that your server can reach
ai.mihajlo.mk. Firewall rules or DNS issues can block access. - CSV Parsing Errors: Malformed CSV files can cause issues. Ensure consistent delimiters and quoting. The script includes basic checks for missing email columns and values.
- Rate Limiting: If you consistently hit rate limits, you'll need to implement smarter retry logic or upgrade your API plan. Log these events to monitor usage.
- Unexpected API Responses: While we validate for essential keys, the API might change its response format in the future. Monitor logs for unexpected data structures.
Final Verification Checklist
- [ ] API token is securely stored in environment variables and not in code.
- [ ] PHP version is 8.3+.
- [ ] cURL extension is enabled.
- [ ] Composer dependencies are installed.
- [ ] Sample
.envandconfig/api.phpare correctly set up. - [ ]
import_contacts.csvis present and has an "email" column. - [ ] Running
composer importexecutes without fatal errors. - [ ] Output correctly categorizes emails as deliverable, uncertain, or invalid.
- [ ] Unit tests for
EmailValidatorServicepass. - [ ] Error logging is configured for production.
By integrating the Mihajlo AI Email Validator service, you've taken a significant step towards maintaining a clean and effective newsletter contact list. The ability to distinguish between definitively deliverable emails, those requiring a second look, and those that are clearly invalid empowers you to manage your subscriber data with greater precision, ultimately leading to better campaign performance and a healthier sender reputation.