├── .gitignore ├── LICENSE ├── composer.json ├── lib └── Verifalia │ ├── Security │ ├── TotpTokenProvider.php │ ├── AuthenticationProvider.php │ ├── UsernamePasswordAuthenticationProvider.php │ ├── ClientCertificateAuthenticationProvider.php │ └── BearerAuthenticationProvider.php │ ├── Exceptions │ └── VerifaliaException.php │ ├── Internal │ ├── Rest │ │ ├── InvocationOptions.php │ │ └── MultiplexedRestClient.php │ └── ParserUtils.php │ ├── Common │ ├── ListingCursor.php │ ├── Direction.php │ └── ListingOptions.php │ ├── EmailValidations │ ├── ValidationProgress.php │ ├── Validation.php │ ├── ExportedEntriesFormat.php │ ├── ValidationStatus.php │ ├── LineEndingMode.php │ ├── ValidationEntryClassification.php │ ├── DeduplicationMode.php │ ├── ValidationRequestEntry.php │ ├── CompletionCallback.php │ ├── QualityLevelName.php │ ├── ValidationRequest.php │ ├── ValidationRequestBase.php │ ├── WaitOptions.php │ ├── ValidationOverview.php │ ├── FileValidationRequest.php │ ├── ValidationEntry.php │ ├── ValidationEntryStatus.php │ └── EmailValidationsRestClient.php │ ├── Credits │ ├── Balance.php │ └── CreditsRestClient.php │ ├── VerifaliaRestClientOptions.php │ └── VerifaliaRestClient.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | composer.lock 4 | composer.phar 5 | test.php -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2005-2024 Cobisi Research - https://verifalia.com/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "verifalia/sdk", 3 | "description": "Verifalia provides a simple HTTPS-based API for validating email addresses and checking whether they are deliverable or not. This library allows to easily integrate with Verifalia and verify email addresses in real-time.", 4 | "homepage": "https://verifalia.com/", 5 | "keywords": [ 6 | "verifalia", 7 | "email", 8 | "validation", 9 | "verification", 10 | "free", 11 | "service", 12 | "saas", 13 | "list", 14 | "hygiene", 15 | "cleaning", 16 | "mx", 17 | "smtp", 18 | "mailbox", 19 | "scrub", 20 | "email checker" 21 | ], 22 | "authors": [ 23 | { 24 | "name": "Rudy Chiappetta" 25 | }, 26 | { 27 | "name": "John Grounz" 28 | }, 29 | { 30 | "name": "Efran Cobisi" 31 | }, 32 | { 33 | "name": "Stefano Tatah" 34 | }, 35 | { 36 | "name": "Germano Mosconi" 37 | }, 38 | { 39 | "name": "Alex Soffiateli" 40 | } 41 | ], 42 | "autoload": { 43 | "psr-4": { 44 | "Verifalia\\": "lib/Verifalia" 45 | } 46 | }, 47 | "require": { 48 | "php": ">=7", 49 | "guzzlehttp/guzzle": "^6|^7", 50 | "ext-json": "*" 51 | }, 52 | "support": { 53 | "issues": "https://github.com/verifalia/verifalia-php-sdk/issues" 54 | }, 55 | "license": "MIT" 56 | } -------------------------------------------------------------------------------- /lib/Verifalia/Security/TotpTokenProvider.php: -------------------------------------------------------------------------------- 1 | cursor = $cursor; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /lib/Verifalia/Common/Direction.php: -------------------------------------------------------------------------------- 1 | progress` field. 33 | * @see Validation 34 | */ 35 | class ValidationProgress 36 | { 37 | /** 38 | * @var double The percentage of completed entries, expressed as a decimal ranging from 0 to 1. 39 | */ 40 | public $percentage; 41 | 42 | /** 43 | * @var ?DateInterval An estimated time span required to complete the entire job, if available. 44 | */ 45 | public $estimatedTimeRemaining; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/Verifalia/Common/ListingOptions.php: -------------------------------------------------------------------------------- 1 | overview = $overview; 47 | $this->entries = $entries; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/Verifalia/EmailValidations/ExportedEntriesFormat.php: -------------------------------------------------------------------------------- 1 | days > 0) 51 | { 52 | return $interval->format("%d.H:I:S"); 53 | } 54 | 55 | return $interval->format("H:I:S"); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /lib/Verifalia/EmailValidations/ValidationRequestEntry.php: -------------------------------------------------------------------------------- 1 | inputData = $inputData; 55 | $this->custom = $custom; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /lib/Verifalia/EmailValidations/CompletionCallback.php: -------------------------------------------------------------------------------- 1 | url = $url; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /lib/Verifalia/Credits/Balance.php: -------------------------------------------------------------------------------- 1 | creditPacks = $creditPacks; 61 | $this->freeCredits = $freeCredits; 62 | $this->freeCreditsResetIn = $freeCreditsResetIn; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /lib/Verifalia/EmailValidations/QualityLevelName.php: -------------------------------------------------------------------------------- 1 | restClient = $restClient; 46 | } 47 | 48 | /** 49 | * Returns the current credits balance for the Verifalia account. 50 | * 51 | * @return Balance The credits balance for the Verifalia account. 52 | * @throws VerifaliaException 53 | */ 54 | public function getBalance(): Balance 55 | { 56 | $response = $this->restClient->invoke(MultiplexedRestClient::HTTP_METHOD_GET, "credits/balance"); 57 | $statusCode = $response->getStatusCode(); 58 | $body = $response->getBody(); 59 | 60 | switch ($statusCode) { 61 | case MultiplexedRestClient::HTTP_STATUS_OK: { 62 | $balance = json_decode($body); 63 | 64 | // Mapping 65 | 66 | if (!empty($balance->freeCreditsResetIn)) { 67 | $balance->freeCreditsResetIn = ParserUtils::timeSpanStringToDateInterval($balance->freeCreditsResetIn); 68 | } 69 | 70 | return new Balance($balance->creditPacks, 71 | $balance->freeCredits, 72 | $balance->freeCreditsResetIn); 73 | } 74 | 75 | default: 76 | throw new VerifaliaException("Unexpected HTTP status code $statusCode. Body: $body"); 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/Verifalia/EmailValidations/ValidationRequest.php: -------------------------------------------------------------------------------- 1 | entries = array(); 53 | 54 | if (is_array($entries)) { 55 | for ($x = 0; $x < count($entries); $x++) { 56 | $this->addEntry($entries[$x]); 57 | } 58 | } else { 59 | $this->addEntry($entries); 60 | } 61 | } 62 | 63 | /** 64 | * @param string|ValidationRequestEntry $entry 65 | * @return void 66 | */ 67 | private function addEntry($entry) 68 | { 69 | if (is_string($entry)) { 70 | $this->entries[] = new ValidationRequestEntry($entry); 71 | } else if ($entry instanceof ValidationRequestEntry) { 72 | $this->entries[] = $entry; 73 | } else { 74 | throw new InvalidArgumentException('Invalid input entry: it must be either a string representing the email address or a ValidationRequestEntry instance.'); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /lib/Verifalia/Security/UsernamePasswordAuthenticationProvider.php: -------------------------------------------------------------------------------- 1 | username = $username; 54 | $this->password = $password; 55 | } 56 | 57 | public function authenticate(MultiplexedRestClient $restClient, &$requestOptions) 58 | { 59 | $requestOptions = array_merge($requestOptions, [ 60 | RequestOptions::AUTH => [ 61 | $this->username, 62 | $this->password 63 | ] 64 | ]); 65 | } 66 | 67 | /** 68 | * @throws VerifaliaException 69 | */ 70 | public function handleUnauthorizedRequest(MultiplexedRestClient $restClient) 71 | { 72 | throw new VerifaliaException("Can't authenticate to Verifalia using the provided username and password: please check your credentials and retry."); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/Verifalia/Security/ClientCertificateAuthenticationProvider.php: -------------------------------------------------------------------------------- 1 | certificate = $certificate; 58 | } 59 | 60 | public function authenticate(MultiplexedRestClient $restClient, &$requestOptions) 61 | { 62 | $requestOptions = array_merge($requestOptions, [ 63 | RequestOptions::CERT => $this->certificate 64 | ]); 65 | } 66 | 67 | /** 68 | * @throws VerifaliaException 69 | */ 70 | public function handleUnauthorizedRequest(MultiplexedRestClient $restClient) 71 | { 72 | throw new VerifaliaException("Can't authenticate to Verifalia using the provided username and password: please check your credentials and retry."); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/Verifalia/VerifaliaRestClientOptions.php: -------------------------------------------------------------------------------- 1 | submissionWaitTime = 0; 72 | self::$noWait->pollWaitTime = 0; 73 | } 74 | 75 | /** 76 | * Initializes new configuration settings for waiting on the completion of an email validation job. 77 | * 78 | * @param callable|null $progress A callable which eventually receives completion progress updates for an email 79 | * validation job. It accepts a ValidationOverview, with the current email validation job overview. 80 | * @see ValidationOverview 81 | */ 82 | public function __construct(callable $progress = null) 83 | { 84 | $this->progress = $progress; 85 | } 86 | 87 | function waitForNextPoll($validationOverview) 88 | { 89 | // Observe the ETA if we have one, otherwise a delay given the formula: max(5, min(30, 2^(log(noOfEntries, 10) - 1))) 90 | 91 | $delay = max(5, min(30, pow(2, log10($validationOverview->noOfEntries) - 1))); 92 | 93 | if (!empty($validationOverview->progress)) 94 | { 95 | if (!empty($validationOverview->progress->estimatedTimeRemaining)) 96 | { 97 | $delay = $validationOverview->progress->estimatedTimeRemaining->s; 98 | $delay += $validationOverview->progress->estimatedTimeRemaining->i * 60; 99 | $delay += $validationOverview->progress->estimatedTimeRemaining->h * 3600; 100 | $delay += $validationOverview->progress->estimatedTimeRemaining->d * 3600 * 24; 101 | 102 | // TODO: Follow the ETA more precisely 103 | 104 | $delay = max(5, min(30, $delay)); 105 | } 106 | } 107 | 108 | sleep($delay); 109 | } 110 | } 111 | 112 | // Invokes the static constructor for this class (sort of) 113 | 114 | WaitOptions::__constructStatic(); 115 | } 116 | -------------------------------------------------------------------------------- /lib/Verifalia/EmailValidations/ValidationOverview.php: -------------------------------------------------------------------------------- 1 | contentType = $contentType; 96 | 97 | if (is_string($file)) { 98 | $this->file = fopen($file, 'rb'); 99 | 100 | // Guess the content type, if no value has been specified 101 | 102 | if ($contentType == null) { 103 | $this->contentType = $this->tryGuessContentTypeFromFileExtension(pathinfo($file, PATHINFO_EXTENSION)); 104 | } 105 | } 106 | else { 107 | $this->file = $file; 108 | } 109 | 110 | if ($this->contentType == null) { 111 | throw new VerifaliaException("Can't determine the MIME content type of the file from its extension: specify the content type manually, please."); 112 | } 113 | } 114 | 115 | private function tryGuessContentTypeFromFileExtension(string $extension) 116 | { 117 | switch ($extension) 118 | { 119 | case "txt": return "text/plain"; 120 | case "csv": return "text/csv"; 121 | case "tsv": 122 | case "tab": return "text/tab-separated-values"; 123 | case "xls": return "application/vnd.ms-excel"; 124 | case "xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml"; 125 | default: return null; 126 | } 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /lib/Verifalia/EmailValidations/ValidationEntry.php: -------------------------------------------------------------------------------- 1 | punycode algorithm. 71 | * To get the domain part without any ASCII encoding, use `emailAddressDomainPart`. 72 | */ 73 | public $asciiEmailAddressDomainPart; 74 | 75 | /** 76 | * @var ?string Gets the local part of the email address, without comments and folding white spaces. 77 | */ 78 | public $emailAddressLocalPart; 79 | 80 | /** 81 | * @var ?string Gets the domain part of the email address, without comments and folding white spaces. 82 | * If the ASCII-only (punycode) version of the domain part is needed, use `asciiEmailAddressDomainPart`. 83 | */ 84 | public $emailAddressDomainPart; 85 | 86 | /** 87 | * @var ?bool If true, the email address has an international domain name. 88 | */ 89 | public $hasInternationalDomainName; 90 | 91 | /** 92 | * @var ?bool If true, the email address has an international mailbox name. 93 | */ 94 | public $hasInternationalMailboxName; 95 | 96 | /** 97 | * @var ?bool If true, the email address comes from a disposable email address (DEA) provider. 98 | * Learn more about 99 | * disposable email addresses. 100 | */ 101 | public $isDisposableEmailAddress; 102 | 103 | /** 104 | * @var ?bool If true, the email address comes from a free email address provider (e.g. gmail, yahoo, outlook / hotmail, ...). 105 | */ 106 | public $isFreeEmailAddress; 107 | 108 | /** 109 | * @var ?bool If true, the local part of the email address is a well-known role account. 110 | */ 111 | public $isRoleAccount; 112 | 113 | /** 114 | * @var string The validation status for this entry. 115 | * @see ValidationEntryStatus for a list of the validation statuses supported by this SDK. 116 | */ 117 | public $status; 118 | 119 | /** 120 | * @var string The classification for the status of this email address. Standard values include `Deliverable`, 121 | * `Undeliverable`, `Risky` and `Unknown`. 122 | * @see ValidationEntryClassification for a list of the classifications supported by this SDK. 123 | */ 124 | public $classification; 125 | 126 | /** 127 | * @var ?int The position of the character in the email address that led to the syntax validation failure. 128 | * Returns null if there is no syntax failure. 129 | */ 130 | public $syntaxFailureIndex; 131 | 132 | /** 133 | * @var ?int The zero-based index of the first occurrence of this email address in the parent `Validation`. 134 | * This information is relevant when the status for this entry is `Duplicate`; duplicated items only provide 135 | * details such as this index and any possible `custom` values. 136 | * @see Validation 137 | */ 138 | public $duplicateOf; 139 | 140 | /** 141 | * @var ?string[] The potential corrections for the input data, in the event Verifalia identified potential 142 | * typos during the verification process. 143 | */ 144 | public $suggestions; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/Verifalia/Security/BearerAuthenticationProvider.php: -------------------------------------------------------------------------------- 1 | username = $username; 55 | $this->password = $password; 56 | // $this->totpTokenProvider = $totpTokenProvider; 57 | } 58 | 59 | /** 60 | * @throws VerifaliaException 61 | */ 62 | public function authenticate(MultiplexedRestClient $restClient, &$requestOptions) 63 | { 64 | if ($this->accessToken === null) { 65 | $authData = array( 66 | 'username' => $this->username, 67 | 'password' => $this->password 68 | ); 69 | 70 | $options = new InvocationOptions(); 71 | $options->skipAuthentication = true; 72 | 73 | $authResponse = $restClient->invoke(MultiplexedRestClient::HTTP_METHOD_POST, 74 | 'auth/tokens', 75 | null, 76 | $authData, 77 | $options); 78 | 79 | if ($authResponse->getStatusCode() == MultiplexedRestClient::HTTP_STATUS_OK) { 80 | $this->accessToken = json_decode($authResponse->getBody())->accessToken; 81 | 82 | // TODO: Add support for MFA 83 | 84 | // // Decode the JWT token, see https://www.converticacommerce.com/support-maintenance/security/php-one-liner-decode-jwt-json-web-tokens/ 85 | // 86 | // $claims = json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $this->accessToken)[1])))); 87 | // 88 | // // Handle the multi-factor auth (MFA) request, if needed 89 | // 90 | // if (property_exists($claims, 'verifalia:mfa')) { 91 | // $totpAuthData = $this->provideAdditionalAuthFactor($restClient); 92 | // $this->accessToken = $totpAuthData->accessToken; 93 | // } 94 | } 95 | else { 96 | throw new VerifaliaException('Invalid credentials used while attempting to retrieve a bearer auth token.'); 97 | } 98 | } 99 | 100 | $this->addBearerAuth($requestOptions); 101 | } 102 | 103 | public function handleUnauthorizedRequest(MultiplexedRestClient $restClient) 104 | { 105 | $this->accessToken = null; 106 | } 107 | 108 | private function addBearerAuth(&$requestOptions) { 109 | $requestOptions = array_merge($requestOptions, [ 110 | RequestOptions::HEADERS => [ 111 | 'Authorization' => 'Bearer '.$this->accessToken 112 | ] 113 | ]); 114 | } 115 | 116 | /** 117 | * @throws VerifaliaException 118 | */ 119 | private function provideAdditionalAuthFactor(MultiplexedRestClient $restClient) { 120 | if ($this->totpTokenProvider === null) { 121 | throw new VerifaliaException('A multi-factor authentication is required but no token provider has been provided.'); 122 | } 123 | 124 | for ($idxAttempt = 0; $idxAttempt < self::MAX_NO_OF_ATTEMPTS; $idxAttempt++) { 125 | // Retrieve the one-time token from the configured device 126 | 127 | $totp = $this->totpTokenProvider->provideTotpToken(); 128 | 129 | // Validates the provided token against the Verifalia API 130 | 131 | try { 132 | $options = new InvocationOptions(); 133 | $options->skipAuthentication = true; 134 | $options->requestOptions = []; 135 | 136 | $this->addBearerAuth($options->requestOptions); 137 | 138 | $authResponse = $restClient->invoke(MultiplexedRestClient::HTTP_METHOD_POST, 139 | 'auth/totp/verifications', 140 | null, 141 | [ 142 | 'passCode' => $totp 143 | ], 144 | $options); 145 | 146 | if ($authResponse->getStatusCode() == MultiplexedRestClient::HTTP_STATUS_OK) { 147 | return json_decode($authResponse->getBody()); 148 | } 149 | } 150 | catch(VerifaliaException $ex) { 151 | // Having an authorization issue is allowed here, as we are working on an TOTP token validation attempt. 152 | // We will re-throw a VerifaliaException below in the even all the configured TOTP validation attempts fail. 153 | } 154 | } 155 | 156 | throw new VerifaliaException("Invalid TOTP token provided after ".self::MAX_NO_OF_ATTEMPTS." attempt(s): aborting the authentication."); 157 | } 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /lib/Verifalia/Internal/Rest/MultiplexedRestClient.php: -------------------------------------------------------------------------------- 1 | shuffledBaseUris = $baseUris; 67 | shuffle($this->shuffledBaseUris); 68 | 69 | $this->authenticationProvider = $authenticationProvider; 70 | } 71 | 72 | /** 73 | * Invokes the specified REST resource. 74 | * 75 | * @param string $method 76 | * @param string $relativePath 77 | * @param string|array|null $query 78 | * @param ?mixed $data 79 | * @param InvocationOptions|null $options 80 | * @return ResponseInterface 81 | * @throws VerifaliaException 82 | */ 83 | public function invoke(string $method, string $relativePath, $query = null, $data = null, InvocationOptions $options = null): ResponseInterface 84 | { 85 | $errors = []; 86 | 87 | // Cycle among the base URIs 88 | 89 | foreach ($this->shuffledBaseUris as $baseUri) { 90 | $underlyingClient = new Client([ 91 | 'base_uri' => $baseUri . self::DEFAULT_API_VERSION . '/', 92 | ]); 93 | 94 | $requestOptions = [ 95 | RequestOptions::ALLOW_REDIRECTS => false, 96 | RequestOptions::HEADERS => [ 97 | 'Accept' => 'application/json', 98 | 'Accept-Encoding' => 'gzip', 99 | 'User-Agent' => 'verifalia-rest-client/php/' . self::PACKAGE_VERSION . '/' . phpversion() 100 | ], 101 | RequestOptions::HTTP_ERRORS => false 102 | ]; 103 | 104 | if ($options === null || !$options->skipAuthentication) { 105 | $this->authenticationProvider->authenticate($this, $requestOptions); 106 | } 107 | 108 | if ($query !== null) { 109 | $requestOptions[RequestOptions::QUERY] = $query; 110 | } 111 | 112 | if ($method === self::HTTP_METHOD_POST || $method === self::HTTP_METHOD_PUT) { 113 | if ($data !== null) { 114 | $requestOptions[RequestOptions::JSON] = $data; 115 | } 116 | } 117 | 118 | // Additional options 119 | 120 | if ($options !== null && $options->requestOptions !== null) { 121 | $requestOptions = array_merge($requestOptions, $options->requestOptions); 122 | } 123 | 124 | // Execute the request against the current API endpoint 125 | 126 | $response = null; 127 | 128 | try { 129 | $response = $underlyingClient->request( 130 | $method, 131 | $relativePath, 132 | $requestOptions 133 | ); 134 | } catch (Throwable $e) { 135 | // Records the error and continue cycling, hoping the next endpoint will handle the request 136 | 137 | $errors[] = $e; 138 | continue; 139 | } 140 | 141 | // Records an error (and continue cycling) in the event the status code is a 5xx 142 | 143 | if ($response->getStatusCode() >= 500 && $response->getStatusCode() <= 599) { 144 | $errors[] = new VerifaliaException('Status code is ' . $response->getStatusCode()); 145 | continue; 146 | } 147 | 148 | // If the request is unauthorized, give the authentication provider a chance to remediate (on a subsequent attempt) 149 | 150 | if ($response->getStatusCode() == self::HTTP_STATUS_UNAUTHORIZED) { 151 | $this->authenticationProvider->handleUnauthorizedRequest($this); 152 | 153 | $errors[] = new VerifaliaException("Can't authenticate to Verifalia using the provided credentials (will retry in the next attempt)."); 154 | continue; 155 | } 156 | 157 | // Fails on the first occurrence of an HTTP 403 status code 158 | 159 | if ($response->getStatusCode() === self::HTTP_STATUS_FORBIDDEN) { 160 | throw new VerifaliaException('Authentication error (HTTP ' . $response->getStatusCode() . '): ' . $response->getBody()); 161 | } 162 | 163 | return $response; 164 | } 165 | 166 | // We have iterated all the base URIs at this point, so we should report the issue 167 | 168 | throw new VerifaliaException('All the endpoints are unreachable. ' . join(',', $errors)); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/Verifalia/VerifaliaRestClient.php: -------------------------------------------------------------------------------- 1 | 'your-username-here', 77 | * 'password' => 'your-password-here' 78 | * ]); 79 | * 80 | * While authenticating with your Verifalia main account credentials is possible, it is strongly advised 81 | * to create one or more users (formerly known as sub-accounts) with just the required permissions, for improved 82 | * security. To create a new user or manage existing ones, please visit https://verifalia.com/client-area#/users 83 | * 84 | * #### X.509 client-certificate authentication 85 | * 86 | * Here is an example showing how to initialize a `VerifaliaRestClient` instance using an X.590 client-certificate 87 | * for a user: 88 | * 89 | * $verifalia = new VerifaliaRestClient([ 90 | * 'certificate' => '/home/gfring/Documents/lospollos.full.pem' 91 | * ]); 92 | * 93 | * #### Other options 94 | * Configuration settings include the following options: 95 | * 96 | * - username: The username of the Verifalia user to authenticate with. 97 | * - password: The password of the Verifalia user to authenticate with. 98 | * - baseUris: The base URIs of the Verifalia API. 99 | * - authentication-provider: An implementation of `AuthenticationProvider` used to authenticate against the 100 | * Verifalia API. 101 | * 102 | * @param array $options VerifaliaRestClient configuration settings. 103 | * @see VerifaliaRestClientOptions for a list of available options. 104 | */ 105 | public function __construct(array $options) 106 | { 107 | // Check the provided options 108 | 109 | if ($options === null) { 110 | throw new InvalidArgumentException('options is null'); 111 | } 112 | 113 | // Custom base URIs 114 | 115 | if (array_key_exists(VerifaliaRestClientOptions::BASE_URIS, $options)) { 116 | $baseUris = $options[VerifaliaRestClientOptions::BASE_URIS]; 117 | } 118 | 119 | // Authentication settings 120 | 121 | if (array_key_exists(VerifaliaRestClientOptions::AUTHENTICATION_PROVIDER, $options)) { 122 | $authenticationProvider = $options[VerifaliaRestClientOptions::AUTHENTICATION_PROVIDER]; 123 | } 124 | else { 125 | if (array_key_exists(VerifaliaRestClientOptions::CERTIFICATE, $options)) { 126 | $authenticationProvider = new ClientCertificateAuthenticationProvider( 127 | $options[VerifaliaRestClientOptions::CERTIFICATE] 128 | ); 129 | 130 | // Default base URIs for client-certificate authentication 131 | 132 | if (empty($baseUris)) { 133 | $baseUris = self::DEFAULT_CCA_BASE_URIS; 134 | } 135 | } else { 136 | if (!array_key_exists(VerifaliaRestClientOptions::USERNAME, $options)) { 137 | throw new InvalidArgumentException("username is null or empty: please visit https://verifalia.com/client-area to set up a new user, if you don't have one."); 138 | } 139 | 140 | if (!array_key_exists(VerifaliaRestClientOptions::PASSWORD, $options)) { 141 | throw new InvalidArgumentException("password is null or empty: please visit https://verifalia.com/client-area to set up a new user, if you don't have one."); 142 | } 143 | 144 | $authenticationProvider = new UsernamePasswordAuthenticationProvider( 145 | $options[VerifaliaRestClientOptions::USERNAME], 146 | $options[VerifaliaRestClientOptions::PASSWORD] 147 | ); 148 | } 149 | } 150 | 151 | if (empty($baseUris)) { 152 | $baseUris = self::DEFAULT_BASE_URIS; 153 | } 154 | 155 | // Set up the underlying REST client 156 | 157 | $restClient = new MultiplexedRestClient( 158 | $baseUris, 159 | $authenticationProvider 160 | ); 161 | 162 | $this->credits = new CreditsRestClient($restClient); 163 | $this->emailValidations = new EmailValidationsRestClient($restClient); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lib/Verifalia/EmailValidations/ValidationEntryStatus.php: -------------------------------------------------------------------------------- 1 | RFC 5336 and support and 201 | * announce both the 8BITMIME and the UTF8SMTP protocol extensions. 202 | */ 203 | const SERVER_DOES_NOT_SUPPORT_INTERNATIONAL_MAILBOXES = 'ServerDoesNotSupportInternationalMailboxes'; 204 | 205 | /** 206 | * The external mail exchanger accepts fake, nonexistent, email addresses; therefore the provided email address 207 | * MAY be nonexistent too. 208 | */ 209 | const SERVER_IS_CATCH_ALL = 'ServerIsCatchAll'; 210 | 211 | /** 212 | * The mail exchanger responsible for the email address under test is temporarily unavailable. 213 | */ 214 | const SERVER_TEMPORARILY_UNAVAILABLE = 'ServerTemporaryUnavailable'; 215 | 216 | /** 217 | * A socket connection error occurred while connecting to the mail exchanger which serves the email address 218 | * domain. 219 | */ 220 | const SMTP_CONNECTION_FAILURE = 'SmtpConnectionFailure'; 221 | 222 | /** 223 | * A timeout has occurred while connecting to the mail exchanger which serves the email address domain. 224 | */ 225 | const SMTP_CONNECTION_TIMEOUT = 'SmtpConnectionTimeout'; 226 | 227 | /** 228 | * The mail exchanger responsible for the email address under test replied one or more non-standard SMTP replies 229 | * which caused the SMTP session to be aborted. 230 | */ 231 | const SMTP_DIALOG_ERROR = 'SmtpDialogError'; 232 | 233 | /** 234 | * The email address has been successfully validated. 235 | */ 236 | const SUCCESS = 'Success'; 237 | 238 | /** 239 | * The domain literal of the email address couldn't accept messages from the Internet. 240 | */ 241 | const UNACCEPTABLE_DOMAIN_LITERAL = 'UnacceptableDomainLiteral'; 242 | 243 | /** 244 | * The number of parenthesis used to open comments is not equal to the one used to close them. 245 | */ 246 | const UNBALANCED_COMMENT_PARENTHESIS = 'UnbalancedCommentParenthesis'; 247 | 248 | /** 249 | * An unexpected quoted pair sequence has been found within a quoted word. 250 | */ 251 | const UNEXPECTED_QUOTED_PAIR_SEQUENCE = 'UnexpectedQuotedPairSequence'; 252 | 253 | /** 254 | * One or more unhandled exceptions have been thrown during the verification process and something went wrong on 255 | * the Verifalia side. 256 | */ 257 | const UNHANDLED_EXCEPTION = 'UnhandledException'; 258 | 259 | /** 260 | * A quoted pair within a quoted word is not closed properly. 261 | */ 262 | const UNMATCHED_QUOTED_PAIR = 'UnmatchedQuotedPair'; 263 | 264 | /** 265 | * The system assigned a user-defined classification because the input data met the criteria specified in a 266 | * custom classification override rule. 267 | */ 268 | const OVERRIDE_MATCH = 'OverrideMatch'; 269 | } 270 | 271 | } -------------------------------------------------------------------------------- /lib/Verifalia/EmailValidations/EmailValidationsRestClient.php: -------------------------------------------------------------------------------- 1 | restClient = $restClient; 53 | } 54 | 55 | /** 56 | * Submits a new email validation for processing. By default, this method **waits** for the completion of the email 57 | * validation job. You can pass a `$waitOptions` parameter to request a different waiting behavior. 58 | * 59 | * @param string|string[]|ValidationRequestEntry[]|ValidationRequest|ValidationRequestEntry|FileValidationRequest $entries 60 | * One or more email addresses to validate, provided either as a string, or an array of strings or a request instance. 61 | * @param ?WaitOptions $waitOptions Defines the options that specify how to wait for the completion of the email 62 | * validation job. It can be set to `null` to wait for completion using the default options provided by the SDK, 63 | * or set to an instance of `WaitOptions` for advanced scenarios and progress tracking. 64 | * @return ?Validation An object describing the validation job. 65 | * @throws VerifaliaException 66 | */ 67 | public function submit($entries, WaitOptions $waitOptions = null) 68 | { 69 | // Builds the input json structure 70 | 71 | $jsonData = array(); 72 | 73 | if ($entries instanceof FileValidationRequest) { 74 | $validation = $entries; 75 | 76 | // File-specific settings 77 | 78 | if ($validation->startingRow !== null & $validation->startingRow !== 0) { 79 | $jsonData['startingRow'] = $validation->startingRow; 80 | } 81 | 82 | if ($validation->endingRow !== null) { 83 | $jsonData['endingRow'] = $validation->endingRow; 84 | } 85 | 86 | if ($validation->column !== null & $validation->column !== 0) { 87 | $jsonData['column'] = $validation->column; 88 | } 89 | 90 | if ($validation->sheet !== null & $validation->sheet !== 0) { 91 | $jsonData['sheet'] = $validation->sheet; 92 | } 93 | 94 | if ($validation->lineEnding !== null) { 95 | $jsonData['lineEnding'] = $validation->lineEnding; 96 | } 97 | } 98 | else { 99 | if ($entries instanceof ValidationRequest) { 100 | $validation = $entries; 101 | } else { 102 | $validation = new ValidationRequest($entries); 103 | } 104 | 105 | $mappedEntries = array(); 106 | 107 | foreach ($validation->entries as $entry) { 108 | $mappedEntry = array('inputData' => $entry->inputData); 109 | 110 | if ($entry->custom !== null) { 111 | $mappedEntry['custom'] = $entry->custom; 112 | } 113 | 114 | $mappedEntries[] = $mappedEntry; 115 | } 116 | 117 | $jsonData['entries'] = $mappedEntries; 118 | } 119 | 120 | // Common settings 121 | 122 | if ($validation->name !== null) { 123 | $jsonData['name'] = $validation->name; 124 | } 125 | 126 | if ($validation->deduplication !== null) { 127 | $jsonData['deduplication'] = $validation->deduplication; 128 | } 129 | 130 | if ($validation->quality !== null) { 131 | $jsonData['quality'] = $validation->quality; 132 | } 133 | 134 | if ($validation->priority !== null) { 135 | $jsonData['priority'] = $validation->priority; 136 | } 137 | 138 | if ($validation->retention !== null) { 139 | $jsonData['retention'] = ParserUtils::dateIntervalToTimeSpanString($validation->retention); 140 | } 141 | 142 | if ($validation->completionCallback !== null) { 143 | $callback = array("url" => $validation->completionCallback->url); 144 | 145 | if ($validation->completionCallback->version !== null) { 146 | $callback["version"] = $validation->completionCallback->version; 147 | } 148 | 149 | if ($validation->completionCallback->skipServerCertificateValidation !== null) { 150 | $callback["skipServerCertificateValidation"] = $validation->completionCallback->skipServerCertificateValidation; 151 | } 152 | 153 | $jsonData['callback'] = $callback; 154 | } 155 | 156 | if ($waitOptions === null) { 157 | $waitOptions = WaitOptions::$default; 158 | } 159 | 160 | // Sends the request to the Verifalia servers 161 | 162 | if ($entries instanceof FileValidationRequest) { 163 | // File imports require a multi-part form submission 164 | 165 | $invocationOptions = new InvocationOptions(); 166 | $invocationOptions->requestOptions = [ 167 | 'multipart' => [ 168 | [ 169 | 'name' => 'inputFile', 170 | 'contents' => $entries->file, 171 | 'filename' => 'dummy', 172 | 'headers' => ['Content-Type' => $entries->contentType] 173 | ], 174 | [ 175 | 'name' => 'settings', 176 | 'contents' => json_encode($jsonData, JSON_FORCE_OBJECT), 177 | 'headers' => ['Content-Type' => 'application/json'] 178 | ], 179 | ] 180 | ]; 181 | 182 | $response = $this->restClient->invoke( 183 | MultiplexedRestClient::HTTP_METHOD_POST, 184 | "email-validations", 185 | [ 186 | 'waitTime' => $waitOptions->submissionWaitTime 187 | ], 188 | null, 189 | $invocationOptions 190 | ); 191 | } 192 | else { 193 | $response = $this->restClient->invoke( 194 | MultiplexedRestClient::HTTP_METHOD_POST, 195 | "email-validations", 196 | [ 197 | 'waitTime' => $waitOptions->submissionWaitTime 198 | ], 199 | $jsonData 200 | ); 201 | } 202 | 203 | // Handles the response 204 | 205 | $statusCode = $response->getStatusCode(); 206 | $body = $response->getBody(); 207 | 208 | switch ($statusCode) { 209 | case MultiplexedRestClient::HTTP_STATUS_OK: 210 | case MultiplexedRestClient::HTTP_STATUS_ACCEPTED: { 211 | $partialValidation = $this->buildPartialValidation(json_decode($body)); 212 | 213 | // Returns immediately if the validation has been completed or if we should not wait for it 214 | 215 | if ($waitOptions === WaitOptions::$noWait || $partialValidation->overview->status === ValidationStatus::COMPLETED) { 216 | return $this->retrieveValidationFromPartialValidation($partialValidation); 217 | } 218 | 219 | return $this->waitValidationForCompletion($partialValidation->overview, $waitOptions); 220 | } 221 | 222 | case MultiplexedRestClient::HTTP_STATUS_PAYMENT_REQUIRED: 223 | throw new VerifaliaException("Verifalia was unable to accept your request because of low account credit. Body: $body"); 224 | 225 | default: 226 | throw new VerifaliaException("Unexpected HTTP status code $statusCode. Body: $body"); 227 | } 228 | } 229 | 230 | /** 231 | * Returns an email validation job that was previously submitted for processing. By default, this method **waits** 232 | * for the completion of the email validation job, if applicable. You can pass a `$waitOptions` parameter to 233 | * request a different waiting behavior. 234 | * 235 | * @param string $id The unique identifier of the validation job to retrieve. 236 | * @param ?WaitOptions $waitOptions Defines the options that specify how to wait for the completion of the email 237 | * validation job. It can be set to `null` to wait for completion using the default options provided by the SDK, 238 | * or set to an instance of `WaitOptions` for advanced scenarios and progress tracking. 239 | * @return ?Validation An object describing the validation job. 240 | * @throws VerifaliaException 241 | * @throws Exception 242 | */ 243 | public function get(string $id, WaitOptions $waitOptions = null) 244 | { 245 | if ($waitOptions === null) { 246 | $waitOptions = WaitOptions::$default; 247 | } 248 | 249 | $response = $this->restClient->invoke(MultiplexedRestClient::HTTP_METHOD_GET, 250 | "email-validations/$id", 251 | [ 252 | 'waitTime' => $waitOptions->pollWaitTime 253 | ]); 254 | 255 | $statusCode = $response->getStatusCode(); 256 | $body = $response->getBody(); 257 | 258 | switch ($statusCode) { 259 | case MultiplexedRestClient::HTTP_STATUS_OK: 260 | case MultiplexedRestClient::HTTP_STATUS_ACCEPTED: { 261 | $partialValidation = $this->buildPartialValidation(json_decode($body)); 262 | 263 | // Returns immediately if the validation has been completed or if we should not wait for it 264 | 265 | if ($waitOptions === WaitOptions::$noWait || $partialValidation->overview->status === ValidationStatus::COMPLETED) { 266 | return $this->retrieveValidationFromPartialValidation($partialValidation); 267 | } 268 | 269 | return $this->waitValidationForCompletion($partialValidation->overview, $waitOptions); 270 | } 271 | 272 | case MultiplexedRestClient::HTTP_STATUS_NOT_FOUND: 273 | case MultiplexedRestClient::HTTP_STATUS_GONE: 274 | return null; 275 | 276 | default: 277 | throw new VerifaliaException("Unexpected HTTP status code $statusCode. Body: $body"); 278 | } 279 | } 280 | 281 | /** 282 | * @param string $id The unique identifier of the validation job to retrieve. 283 | * @param string $format The requested file format. See `ExportedEntriesFormat` for a list of the supported file formats. 284 | * @return string|null A binary string with the data exported in the requested format. 285 | * @see ExportedEntriesFormat for a list of the supported file formats. 286 | */ 287 | public function exportEntries(string $id, string $format) 288 | { 289 | $invocationOptions = new InvocationOptions(); 290 | $invocationOptions->requestOptions = [ 291 | RequestOptions::HEADERS => [ 292 | 'Accept' => $format 293 | ] 294 | ]; 295 | 296 | $response = $this->restClient->invoke(MultiplexedRestClient::HTTP_METHOD_GET, 297 | "email-validations/$id/entries", 298 | null, 299 | null, 300 | $invocationOptions); 301 | 302 | $statusCode = $response->getStatusCode(); 303 | $body = $response->getBody(); 304 | 305 | switch ($statusCode) { 306 | case MultiplexedRestClient::HTTP_STATUS_OK: { 307 | return $body->getContents(); 308 | } 309 | 310 | case MultiplexedRestClient::HTTP_STATUS_NOT_FOUND: 311 | case MultiplexedRestClient::HTTP_STATUS_GONE: 312 | return null; 313 | 314 | default: 315 | throw new VerifaliaException("Unexpected HTTP status code $statusCode. Body: $body"); 316 | } 317 | } 318 | 319 | /** 320 | * @throws VerifaliaException 321 | */ 322 | private function waitValidationForCompletion($validationOverview, WaitOptions $waitOptions) 323 | { 324 | $resultOverview = $validationOverview; 325 | 326 | do { 327 | // Fires a progress, since we are not yet completed 328 | 329 | if ($waitOptions->progress !== null) { 330 | call_user_func($waitOptions->progress, $resultOverview); 331 | } 332 | 333 | // Wait for the next polling schedule 334 | 335 | $waitOptions->waitForNextPoll($resultOverview); 336 | 337 | // Fetch the job from the API 338 | 339 | $result = $this->get($validationOverview->id, $waitOptions); 340 | 341 | if ($result === null) { 342 | // A null result means the validation has been deleted (or is expired) between a poll and the next one 343 | 344 | return null; 345 | } 346 | 347 | $resultOverview = $result->overview; 348 | 349 | // Returns immediately if the validation has been completed 350 | 351 | if ($resultOverview->status === ValidationStatus::COMPLETED) { 352 | return $result; 353 | } 354 | } while (true); 355 | } 356 | 357 | /** 358 | * @throws VerifaliaException 359 | * @throws Exception 360 | */ 361 | private function retrieveValidationFromPartialValidation($partialValidation): Validation 362 | { 363 | $allEntries = array(); 364 | 365 | if (property_exists($partialValidation, 'entries')) { 366 | $currentSegment = $partialValidation->entries; 367 | 368 | while ($currentSegment !== null && $currentSegment->data !== null) { 369 | foreach($currentSegment->data as $entry){ 370 | $entry->index = (int) $entry->index; 371 | 372 | if (!empty($entry->completedOn)) 373 | { 374 | $entry->completedOn = new DateTime($entry->completedOn); 375 | } 376 | 377 | if (!empty($entry->hasInternationalDomainName)) 378 | { 379 | $entry->hasInternationalDomainName = (bool) $entry->hasInternationalDomainName; 380 | } 381 | 382 | if (!empty($entry->hasInternationalMailboxName)) 383 | { 384 | $entry->hasInternationalMailboxName = (bool) $entry->hasInternationalMailboxName; 385 | } 386 | 387 | if (!empty($entry->isDisposableEmailAddress)) 388 | { 389 | $entry->isDisposableEmailAddress = (bool) $entry->isDisposableEmailAddress; 390 | } 391 | 392 | if (!empty($entry->isFreeEmailAddress)) 393 | { 394 | $entry->isFreeEmailAddress = (bool) $entry->isFreeEmailAddress; 395 | } 396 | 397 | if (!empty($entry->isRoleAccount)) 398 | { 399 | $entry->isRoleAccount = (bool) $entry->isRoleAccount; 400 | } 401 | 402 | if (!empty($entry->syntaxFailureIndex)) 403 | { 404 | $entry->syntaxFailureIndex = (int) $entry->syntaxFailureIndex; 405 | } 406 | 407 | if (!empty($entry->duplicateOf)) 408 | { 409 | $entry->duplicateOf = (int) $entry->duplicateOf; 410 | } 411 | 412 | $allEntries[] = $entry; 413 | } 414 | 415 | if (!property_exists($currentSegment, 'meta') || !property_exists($currentSegment->meta, 'isTruncated') || $currentSegment->meta->isTruncated === false) { 416 | break; 417 | } 418 | 419 | $currentSegment = $this->listEntriesSegmented( 420 | $partialValidation->overview->id, 421 | new ListingCursor($currentSegment->meta->cursor) 422 | ); 423 | } 424 | } 425 | 426 | return new Validation($partialValidation->overview, $allEntries); 427 | } 428 | 429 | /** 430 | * @throws VerifaliaException 431 | */ 432 | private function listEntriesSegmented(string $id, ListingCursor $cursor) 433 | { 434 | // Generate the additional parameters, where needed 435 | 436 | $cursorParamName = $cursor->direction === Direction::FORWARD 437 | ? "cursor" 438 | : "cursor:prev"; 439 | 440 | $query = [ 441 | $cursorParamName => $cursor->cursor 442 | ]; 443 | 444 | if ($cursor->limit > 0) { 445 | $query["limit"] = $cursor->limit; 446 | } 447 | 448 | $response = $this->restClient->invoke( 449 | MultiplexedRestClient::HTTP_METHOD_GET, 450 | "/email-validations/$id/entries", 451 | $query 452 | ); 453 | 454 | $statusCode = $response->getStatusCode(); 455 | $body = $response->getBody(); 456 | 457 | if ($statusCode === MultiplexedRestClient::HTTP_STATUS_OK) { 458 | return json_decode($body)->data; 459 | } 460 | 461 | throw new VerifaliaException("Unexpected HTTP response: $statusCode $body"); 462 | } 463 | 464 | /** 465 | * Deletes an email validation job that was previously submitted for processing. 466 | * 467 | * @param string $id The unique identifier of the email validation job to be deleted. 468 | * @return void 469 | * @throws VerifaliaException 470 | */ 471 | public function delete(string $id) 472 | { 473 | // Sends the request to the Verifalia servers 474 | 475 | $response = $this->restClient->invoke(MultiplexedRestClient::HTTP_METHOD_DELETE, "email-validations/$id"); 476 | $statusCode = $response->getStatusCode(); 477 | 478 | if ($statusCode !== MultiplexedRestClient::HTTP_STATUS_OK) { 479 | $body = $response->getBody(); 480 | 481 | throw new VerifaliaException("Unexpected HTTP status code $statusCode. Body: $body"); 482 | } 483 | } 484 | 485 | /** 486 | * @throws Exception 487 | */ 488 | private function buildPartialValidation($partialValidation) 489 | { 490 | $partialValidation->overview->createdOn = new DateTime($partialValidation->overview->createdOn); 491 | 492 | if (!empty($partialValidation->overview->submittedOn)) 493 | { 494 | $partialValidation->overview->submittedOn = new DateTime($partialValidation->overview->submittedOn); 495 | } 496 | 497 | if (!empty($partialValidation->overview->completedOn)) 498 | { 499 | $partialValidation->overview->completedOn = new DateTime($partialValidation->overview->completedOn); 500 | } 501 | 502 | if (!empty($partialValidation->overview->priority)) 503 | { 504 | $partialValidation->overview->priority = (int) ($partialValidation->overview->priority); 505 | } 506 | 507 | $partialValidation->overview->noOfEntries = (int) ($partialValidation->overview->noOfEntries); 508 | $partialValidation->overview->retention = ParserUtils::timeSpanStringToDateInterval($partialValidation->overview->retention); 509 | 510 | if (!empty($partialValidation->overview->progress)) 511 | { 512 | if (!empty($partialValidation->overview->progress->estimatedTimeRemaining)) 513 | { 514 | $partialValidation->overview->progress->estimatedTimeRemaining = ParserUtils::timeSpanStringToDateInterval($partialValidation->overview->progress->estimatedTimeRemaining); 515 | } 516 | } 517 | 518 | return $partialValidation; 519 | } 520 | } 521 | } 522 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Verifalia API](https://img.shields.io/badge/Verifalia%20API-v2.5-green) 2 | [![Packagist](https://img.shields.io/packagist/v/verifalia/sdk.svg)](http://packagist.org/packages/verifalia/sdk) 3 | 4 | Verifalia API - PHP SDK and helper library 5 | ========================================== 6 | 7 | This SDK library integrates with [Verifalia](https://verifalia.com) and allows to [verify email addresses](https://verifalia.com) 8 | in **PHP v7.0 and higher**. 9 | 10 | [Verifalia](https://verifalia.com/) is an online service that provides email verification and mailing list cleaning; it helps businesses reduce 11 | their bounce rate, protect their sender reputation, and ensure their email campaigns reach the intended recipients. 12 | Verifalia can [verify email addresses](https://verifalia.com/) in real-time and in bulk, using its API or client area; it also 13 | offers various features and settings to customize the verification process according to the user’s needs. 14 | 15 | Verifalia's email verification process consists of several steps, each taking fractions of a second: it checks the **formatting 16 | and syntax** (RFC 1123, RFC 2821, RFC 2822, RFC 3490, RFC 3696, RFC 4291, RFC 5321, RFC 5322, and RFC 5336) of each email address, 17 | the **domain and DNS records**, the **mail exchangers**, and the **mailbox existence**, with support for internationalized domains 18 | and mailboxes. It also detects risky email types, such as **catch-all**, **disposable**, or **spam traps** / **honeypots**. 19 | 20 | Verifalia provides detailed and **accurate reports** for each email verification: it categorizes each email address as `Deliverable`, 21 | `Undeliverable`, `Risky`, or `Unknown`, and assigns one of its exclusive set of over 40 [status codes](https://verifalia.com/developers#email-validations-status-codes). 22 | It also explains the undeliverability reason and provides **comprehensive verification details**. The service allows the user to choose the desired 23 | quality level, the waiting timeout, the deduplication preferences, the data retention settings, and the callback preferences 24 | for each verification. 25 | 26 | Of course, Verifalia never sends emails to the contacts or shares the user's data with anyone. 27 | 28 | To learn more about Verifalia please see [https://verifalia.com](https://verifalia.com/) 29 | 30 | ## Table of contents 31 | 32 | * [Getting started](#getting-started) 33 | * [Naming conventions](#naming-conventions-) 34 | * [Authentication](#authentication) 35 | * [Authenticating via Basic Auth](#authenticating-via-basic-auth) 36 | * [Authenticating via bearer token](#authenticating-via-bearer-token) 37 | * [Authenticating via X.509 client certificate (TLS mutual authentication)](#authenticating-via-x509-client-certificate-tls-mutual-authentication) 38 | * [Validating email addresses](#validating-email-addresses) 39 | * [How to validate / verify an email address](#how-to-validate--verify-an-email-address) 40 | * [How to validate / verify a list of email addresses](#how-to-validate--verify-a-list-of-email-addresses) 41 | * [How to import and submit a file for validation](#how-to-import-and-submit-a-file-for-validation) 42 | * [Processing options](#processing-options) 43 | * [Quality level](#quality-level) 44 | * [Deduplication mode](#deduplication-mode) 45 | * [Data retention](#data-retention) 46 | * [Wait options](#wait-options) 47 | * [Avoid waiting](#avoid-waiting) 48 | * [Progress tracking](#progress-tracking) 49 | * [Completion callbacks](#completion-callbacks) 50 | * [Retrieving jobs](#retrieving-jobs) 51 | * [Exporting email verification results in different output formats](#exporting-email-verification-results-in-different-output-formats) 52 | * [Don't forget to clean up, when you are done](#dont-forget-to-clean-up-when-you-are-done) 53 | * [Managing credits](#managing-credits-) 54 | * [Getting the credits balance](#getting-the-credits-balance-) 55 | * [Changelog / What's new](#changelog--whats-new) 56 | * [v3.1](#v31) 57 | * [v3.0](#v30) 58 | 59 | ## Getting started 60 | 61 | The most efficient way to add the Verifalia email verification library into your PHP project is by using [composer](https://getcomposer.org), 62 | which will automatically download and install the required files [from Packagist](http://packagist.org/packages/verifalia/sdk). With composer installed, 63 | run the following command from your project's root directory: 64 | 65 | ```bash 66 | php composer.phar require verifalia/sdk 67 | ``` 68 | 69 | For Windows users, the alternative command to run is: 70 | 71 | ```batch 72 | composer require verifalia/sdk 73 | ``` 74 | 75 | ### Naming conventions ### 76 | 77 | > This package follows the `PSR-4` convention names for its classes, meaning you can even load them easily with your own 78 | > autoloader. 79 | 80 | ### Authentication 81 | 82 | First things first: authentication to the Verifalia API is performed by way of either 83 | the credentials of your root Verifalia account or of one of your users (previously 84 | known as sub-accounts): if you don't have a Verifalia account, just [register for a free one](https://verifalia.com/sign-up). For security reasons, 85 | it is always advisable to [create and use a dedicated user](https://verifalia.com/client-area#/users/new) for accessing the API, as doing so will allow to assign 86 | only the specific needed permissions to it. 87 | 88 | Learn more about authenticating to the Verifalia API at [https://verifalia.com/developers#authentication](https://verifalia.com/developers#authentication) 89 | 90 | #### Authenticating via Basic Auth 91 | 92 | The most straightforward method for authenticating against the Verifalia API involves using a username and password pair. 93 | These credentials can be applied during the creation of a new instance of the `VerifaliaRestClient` class, serving as the 94 | initial step for all interactions with the Verifalia API: the provided username and password will be automatically 95 | transmitted to the API using the HTTP Basic Auth method. 96 | 97 | ```php 98 | use Verifalia\VerifaliaRestClient; 99 | use Verifalia\VerifaliaRestClientOptions; 100 | 101 | $verifalia = new VerifaliaRestClient([ 102 | VerifaliaRestClientOptions::USERNAME => 'your-username-here', 103 | VerifaliaRestClientOptions::PASSWORD => 'your-password-here' 104 | ]); 105 | ``` 106 | 107 | #### Authenticating via bearer token 108 | 109 | Bearer authentication offers higher security over HTTP Basic Auth, as the latter requires sending the actual credentials 110 | on each API call, while the former only requires it on a first, dedicated authentication request. On the other side, the 111 | first authentication request needed by Bearer authentication takes a non-negligible time. 112 | 113 | > ⚠️ If you need to perform only a single request, **using HTTP Basic Auth** (see above) **provides the same degree of security and is 114 | > also faster**. 115 | 116 | ```php 117 | use Verifalia\VerifaliaRestClient; 118 | use Verifalia\VerifaliaRestClientOptions; 119 | use Verifalia\Security\BearerAuthenticationProvider; 120 | 121 | $verifalia = new VerifaliaRestClient([ 122 | VerifaliaRestClientOptions::AUTHENTICATION_PROVIDER => 123 | new BearerAuthenticationProvider('your-username-here', 'your-password-here') 124 | ]); 125 | ``` 126 | 127 | #### Authenticating via X.509 client certificate (TLS mutual authentication) 128 | 129 | In addition to the aforementioned authentication methods, this SDK also supports using a cryptographic X.509 client 130 | certificate to authenticate against the Verifalia API, through the TLS protocol. This method, also 131 | called mutual TLS authentication (mTLS) or two-way authentication, offers the highest degree of 132 | security, as only a cryptographically-derived key (and not the actual credentials) is sent over 133 | the wire on each request. [What is X.509 TLS client-certificate authentication?](https://verifalia.com/help/sub-accounts/what-is-x509-tls-client-certificate-authentication) 134 | 135 | ```php 136 | use Verifalia\VerifaliaRestClient; 137 | use Verifalia\VerifaliaRestClientOptions; 138 | 139 | $verifalia = new VerifaliaRestClient([ 140 | VerifaliaRestClientOptions::CERTIFICATE => '/home/gfring/Documents/pollos.pem' 141 | ]); 142 | ``` 143 | 144 | ## Validating email addresses 145 | 146 | Every operation related to verifying / validating email addresses is performed through the `emailValidations` field 147 | exposed by the instance of the `VerifaliaRestClient` class you created above. The property exposes some useful functions: 148 | in the next few paragraphs we are looking at the most used ones, so it is strongly advisable to explore the library and 149 | look at the embedded help for other opportunities. 150 | 151 | **The library automatically waits for the completion of email verification jobs**: if needed, it is possible to adjust 152 | the wait options and have more control over the entire underlying polling process. Please refer to the [Wait options](#wait-options) 153 | section below for additional details. 154 | 155 | ### How to validate / verify an email address 156 | 157 | To validate an email address from a PHP application you can invoke the `submit()` method: it accepts one or more email 158 | addresses and any eventual verification options you wish to pass to Verifalia, including the expected results quality, 159 | deduplication preferences, processing priority. 160 | 161 | > Note In the event you need to verify a list of email addresses, it is advisable to submit them all at once through the 162 | > `submit()` method (see the next sections), instead of iterating over the source set and submitting the addresses one 163 | > by one. Not only the all-at-once method would be faster, it would also allow to detect and mark duplicated items - a 164 | > feature which is unavailable while verifying the email addresses one by one. 165 | 166 | In the following example, we verify an email address with this library, using the default options: 167 | 168 | ```php 169 | use Verifalia\VerifaliaRestClient; 170 | 171 | $verifalia = new VerifaliaRestClient(...); // See above 172 | 173 | // Verifies an email address 174 | 175 | $job = $verifalia->emailValidations->submit('batman@gmail.com'); 176 | 177 | // Print some results 178 | 179 | $entry = $job->entries[0]; 180 | 181 | echo 'Classification: ' . $entry->classification; 182 | echo 'Status: ' . $entry->status; 183 | 184 | // Output: 185 | // Classification: Deliverable 186 | // Status: Success 187 | ``` 188 | 189 | Once `submit()` completes successfully, the resulting verification job 190 | is guaranteed to be completed and its results' data (e.g. its `entries` field) to be available for use. 191 | 192 | As you may expect, each entry may include various additional details about the verified email address: 193 | 194 | | Attribute | Description | 195 | |-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 196 | | `asciiEmailAddressDomainPart` | Gets the domain part of the email address, converted to ASCII if needed and with comments and folding white spaces stripped off. | 197 | | `classification` | A string with the classification for this entry; see the `ValidationEntyClassification` class for a list of the values supported at the time this SDK has been released. | 198 | | `completedOn` | The date this entry has been completed, if available. | 199 | | `custom` | A custom, optional string which is passed back upon completing the validation. To pass back and forth a custom value, use the `custom` field of `ValidationRequestEntry`. | 200 | | `duplicateOf` | The zero-based index of the first occurrence of this email address in the parent `Validation`, in the event the `status` field for this entry is `Duplicate`; duplicated items do not expose any result detail apart from this and the eventual `custom` values. | 201 | | `index` | The index of this entry within its `Validation` container; this property is mostly useful in the event the API returns a filtered view of the items. | 202 | | `inputData` | The input string being validated. | 203 | | `emailAddress` | Gets the email address, without any eventual comment or folding white space. Returns null if the input data is not a syntactically invalid e-mail address. | 204 | | `emailAddressDomainPart` | Gets the domain part of the email address, without comments and folding white spaces. | 205 | | `emailAddressLocalPart` | Gets the local part of the email address, without comments and folding white spaces. | 206 | | `hasInternationalDomainName` | If true, the email address has an international domain name. | 207 | | `hasInternationalMailboxName` | If true, the email address has an international mailbox name. | 208 | | `isDisposableEmailAddress` | If true, the email address comes from a disposable email address (DEA) provider. What is a disposable email address? | 209 | | `isFreeEmailAddress` | If true, the email address comes from a free email address provider (e.g. gmail, yahoo, outlook / hotmail, ...). | 210 | | `isRoleAccount` | If true, the local part of the email address is a well-known role account. | 211 | | `status` | The status for this entry; see the `ValidationEntryStatus` class for a list of the values supported at the time this SDK has been released. | 212 | | `suggestions` | The potential corrections for the input data, in the event Verifalia identified potential typos during the verification process. | 213 | | `syntaxFailureIndex` | The position of the character in the email address that eventually caused the syntax validation to fail. | 214 | 215 | Here is another example, showing some of the additional result details provided by Verifalia: 216 | 217 | ```php 218 | use Verifalia\VerifaliaRestClient; 219 | 220 | $verifalia = new VerifaliaRestClient(...); // See above 221 | 222 | // Verifies an email address 223 | 224 | $job = $verifalia->emailValidations->submit('bat[man@gmal.com'); 225 | 226 | // Print some results 227 | 228 | $entry = $job->entries[0]; 229 | 230 | echo 'Classification: ' . $entry->classification . "\n"; 231 | echo 'Status: ' . $entry->status . "\n"; 232 | echo 'Syntax failure index: ' . $entry->syntaxFailureIndex . "\n"; 233 | 234 | if (!empty(entry->suggestions)) { 235 | echo "Suggestions\n"; 236 | 237 | foreach ($entry->suggestions as $suggestion) { 238 | echo '- ' . $suggestion . "\n"; 239 | } 240 | } 241 | 242 | // Output: 243 | // Classification: Undeliverable 244 | // Status: InvalidCharacterInSequence 245 | // Syntax failure index: 3 246 | // Suggestions: 247 | // - batman@gmail.com 248 | ``` 249 | 250 | ### How to validate / verify a list of email addresses 251 | 252 | To verify a list of email addresses you can still call the `submit()` function, which also accepts an array of strings 253 | with the email addresses to verify: 254 | 255 | ```php 256 | use Verifalia\VerifaliaRestClient; 257 | 258 | $verifalia = new VerifaliaRestClient(...); // See above 259 | 260 | // Verifies the list of email addresses 261 | 262 | $job = $verifalia->emailValidations->submit([ 263 | 'batman@gmail.com', 264 | 'steve.vai@best.music', 265 | 'samantha42@yahoo.de', 266 | ]); 267 | 268 | // Print some results 269 | 270 | foreach ($job->entries as $entry) { 271 | echo $entry->emailAddress . ' => ' . $entry->classification; 272 | } 273 | 274 | // Output: 275 | // batman@gmail.com => Deliverable 276 | // steve.vai@best.music => Undeliverable 277 | // samantha42@yahoo.de => Deliverable 278 | ``` 279 | 280 | ### How to import and submit a file for validation 281 | 282 | This library includes support for submitting and validating files with email addresses, including: 283 | 284 | - **plain text files** (.txt), with one email address per line; 285 | - **comma-separated values** (.csv), **tab-separated values** (.tsv) and other delimiter-separated values files; 286 | - **Microsoft Excel spreadsheets** (.xls and .xlsx). 287 | 288 | To submit and validate files, one can still use the `submit()` function mentioned 289 | above, passing either the full name of the file to submit to Verifalia or a resource stream. Along with that, it is also possible to specify the eventual starting 290 | and ending rows to process, the column, the sheet index, the line ending and the 291 | delimiter - depending of course on the nature of the submitted file (see 292 | `FileValidationRequest` in the source to learn more). 293 | 294 | Here is how to submit and verify an Excel file, for example: 295 | 296 | ```php 297 | $job = $verifalia->emailValidations->submit(new FileValidationRequest('that-file.xlsx')); 298 | ``` 299 | 300 | For more advanced options, just set the relevant properties in the `FileValidationRequest` instance: 301 | 302 | ```php 303 | $request = new FileValidationRequest('that-file.xlsx'); 304 | $request->sheet 3; 305 | $request->startingRow = 1; 306 | $request->column = 5; 307 | $request->quality = QualityLevelName::HIGH; 308 | 309 | $job = $verifalia->emailValidations->submit($request); 310 | ``` 311 | 312 | And here is another example, showing how to submit a resource stream instance and specifying the 313 | MIME content type of the file, which is automatically determined from the file extension in 314 | the event you pass file name instead: 315 | 316 | ```php 317 | $stream = fopen('my-list.txt', 'rb'); 318 | $job = $verifalia->emailValidations->submit(new FileValidationRequest($stream, 'text/plain')); 319 | ``` 320 | 321 | ### Processing options 322 | 323 | While submitting one or more email addresses for verification, it is possible to specify several 324 | options which affect the behavior of the Verifalia processing engine as well as the verification flow 325 | from the API consumer standpoint. 326 | 327 | #### Quality level 328 | 329 | Verifalia offers three distinct quality levels - namely, _Standard_, _High_ and _Extreme_ - which rule out how the email 330 | verification engine should deal with temporary undeliverability issues, with slower mail exchangers and other potentially transient 331 | problems which can affect the quality of the verification results. The `ValidationRequest` class accepts a `quality` field which allows 332 | to specify the desired quality level; here is an example showing how to verify an email address using 333 | the _High_ quality level: 334 | 335 | ```php 336 | $request = new ValidationRequest('batman@gmail.com'); 337 | $request->quality = QualityLevelName::HIGH; 338 | 339 | $job = $verifalia->emailValidations->submit($request); 340 | ``` 341 | 342 | #### Deduplication mode 343 | 344 | The `submit()` method can also accept and verify multiple email addresses in bulk, and allows to specify how to 345 | deal with duplicated entries pertaining to the same input set; Verifalia supports a _Safe_ deduplication 346 | mode, which strongly adheres to the old IETF standards, and a _Relaxed_ mode which is more in line with 347 | what can be found in the majority of today's mail exchangers configurations. 348 | 349 | In the next example, we show how to import and verify a list of email addresses and mark duplicated 350 | entries using the _Relaxed_ deduplication mode: 351 | 352 | ```php 353 | $request = new ValidationRequest([ 354 | 'batman@gmail.com', 355 | 'steve.vai@best.music', 356 | 'samantha42@yahoo.de', 357 | ]); 358 | $request->deduplication = DeduplicationMode::RELAXED; 359 | 360 | $job = $verifalia->emailValidations->submit($request); 361 | ``` 362 | 363 | #### Data retention 364 | 365 | Verifalia automatically deletes completed email verification jobs according to the data retention 366 | policy defined at the account level, which can be eventually overridden at the user level: one can 367 | use the [Verifalia clients area](https://verifalia.com/client-area) to configure these settings. 368 | 369 | It is also possible to specify a per-job data retention policy which govern the time to live of a submitted 370 | email verification job; to do that, set the `retention` field of the `ValidationRequest` instance accordingly. 371 | 372 | Here is how, for instance, one can set a data retention policy of 10 minutes while verifying 373 | an email address: 374 | 375 | ```php 376 | $request = new ValidationRequest('batman@gmail.com'); 377 | $request->retention = DateInterval::createFromDateString('10 minutes'); 378 | 379 | $job = $verifalia->emailValidations->submit($request); 380 | ``` 381 | 382 | ### Wait options 383 | 384 | **By default, the `submit()` method submits an email verification job to Verifalia and waits 385 | for its completion**; the entire process may require some time to complete depending on the plan of the 386 | Verifalia account, the number of email addresses the submission contains, the specified quality level 387 | and other network factors including the latency of the mail exchangers under test. 388 | 389 | In waiting for the completion of a given email verification job, the library automatically polls the 390 | underlying Verifalia API until the results are ready; by default, it tries to take advantage of the long 391 | polling mode introduced with the Verifalia API v2.4, which allows to minimize the number of requests 392 | and get the verification results faster. 393 | 394 | #### Avoid waiting 395 | 396 | In certain scenarios (in a microservice architecture, for example), however, it may be preferable to avoid 397 | waiting for a job completion and ask the Verifalia API, instead, to just queue it: in that case, the library 398 | would just return the job overview (and not its verification results) and it will be necessary to retrieve 399 | the verification results using the `get()` method. 400 | 401 | To do that, it is possible to specify `WaitOptions::$noWait` as the value for the `waitOptions` parameter 402 | of the `submit()` method, as shown in the next example: 403 | 404 | ```php 405 | $verifalia = new VerifaliaRestClient(...); // See above 406 | $request = new ValidationRequest(...) // See above; 407 | 408 | $job = $verifalia->emailValidations->submit($request, WaitOptions::$noWait); 409 | 410 | echo 'Status: ' . $job->overview->status; 411 | 412 | // Status: InProgress 413 | ``` 414 | 415 | #### Progress tracking 416 | 417 | For jobs with a large number of email addresses, it could be useful to track progress as they are processed 418 | by the Verifalia email verification engine; to do that, it is possible to create an instance of the 419 | `WaitOptions` class and provide a callable which eventually receives progress notifications through the 420 | `progress` field. 421 | 422 | Here is how to define a progress notification handler which displays the progress percentage of a submitted 423 | job to the console window: 424 | 425 | ```php 426 | use Verifalia\EmailValidations\ValidationOverview; 427 | use Verifalia\EmailValidations\WaitOptions; 428 | 429 | $verifalia = new VerifaliaRestClient(...); // See above 430 | $request = new ValidationRequest(...) // See above; 431 | 432 | $waitOptions = new WaitOptions(function (ValidationOverview $overview) { 433 | echo 'Job status: ' . $overview->status; 434 | 435 | if ($overview->progress !== null) { 436 | echo 'Progress: ' . $overview->progress->percentage . '%'; 437 | } 438 | }); 439 | 440 | $job = $verifalia->emailValidations->submit($request, $waitOptions); 441 | ``` 442 | 443 | ### Completion callbacks 444 | 445 | Along with each email validation job, it is possible to specify a URL which 446 | Verifalia will invoke (POST) once the job completes: this URL must use the HTTPS or HTTP 447 | scheme and be publicly accessible over the Internet. 448 | To learn more about completion callbacks, please see https://verifalia.com/developers#email-validations-completion-callback 449 | 450 | To specify a completion callback URL, pass a `ValidationRequest` instance to the `submit()` method and set its `completionCallback` 451 | field accordingly, as shown in the example below: 452 | 453 | ```php 454 | $verifalia = new VerifaliaRestClient(...); // See above 455 | $request = new ValidationRequest(...) // See above; 456 | 457 | $request->completionCallback = new CompletionCallback('https://your-website-here/foo/bar'); 458 | 459 | $job = $verifalia->emailValidations->submit($request, $waitOptions); 460 | ``` 461 | 462 | Note that completion callbacks are invoked asynchronously, and it could take up to several seconds for your callback URL 463 | to get invoked. 464 | 465 | ### Retrieving jobs 466 | 467 | It is possible to retrieve a job through the `get()` method, which returns a `Validation` instance for the desired email 468 | verification job. While doing that, the library automatically waits for the completion of the job, and it is possible to 469 | adjust this behavior by passing to the aforementioned methods a `waitOptions` parameter, in the exactly same fashion as 470 | described for the `submit()` method; please see the [Wait options](#wait-options) section for additional details. 471 | 472 | Here is an example showing how to retrieve a job, given its identifier: 473 | 474 | ```php 475 | $job = $verifalia->emailValidations->get('ec415ecd-0d0b-49c4-a5f0-f35c182e40ea'); 476 | ``` 477 | 478 | ### Exporting email verification results in different output formats 479 | 480 | This library also allows to export the entries of a completed email validation 481 | job in different output formats through the `exportEntries()` function, with the goal of generating a human-readable representation 482 | of the verification results. 483 | 484 | > **WARNING**: While the output schema (columns / labels / data format) is fairly 485 | > complete, you should always consider it as subject to change: use the `get()` 486 | > function instead if you need to rely on a stable output schema. 487 | 488 | Here is an example showing how to export a given email verification job as a comma-separated values (CSV) file: 489 | 490 | ```php 491 | // Exports the validated entries for the job in the CSV format 492 | 493 | $export = $verifalia->emailValidations->exportEntries('722c2fd8-8837-449f-ad24-0330c597c993', ExportedEntriesFormat::CSV); 494 | 495 | // Stores the binary string into a file 496 | 497 | file_put_contents("my-list.csv", $export); 498 | ``` 499 | 500 | ### Don't forget to clean up, when you are done 501 | 502 | Verifalia automatically deletes completed jobs after a configurable 503 | data-retention policy (see the related section) but it is strongly advisable that 504 | you delete your completed jobs as soon as possible, for privacy and security reasons. To do that, you can invoke the 505 | `delete()` method passing the job Id you wish to get rid of: 506 | 507 | ```php 508 | $verifalia->emailValidations->delete('ec415ecd-0d0b-49c4-a5f0-f35c182e40ea'); 509 | ``` 510 | 511 | Once deleted, a job is gone and there is no way to retrieve its email validation results. 512 | 513 | ## Managing credits ## 514 | 515 | To manage the Verifalia credits for your account you can use the `credits` property exposed by the `VerifaliaRestClient` 516 | instance created above. 517 | 518 | ### Getting the credits balance ### 519 | 520 | One of the most common tasks you may need to perform on your account is retrieving the available number of free daily 521 | credits and credit packs. To do that, you can use the `getBalance()` method, which returns a `Balance` object, as shown 522 | in the next example: 523 | 524 | ```php 525 | $balance = $verifalia 526 | ->credits 527 | ->getBalance(); 528 | 529 | echo 'Credit packs: ' . $balance->creditPacks . "\n"; 530 | echo 'Free daily credits: ' . $balance->freeCredits . "\n"; 531 | echo 'Free daily credits will reset in ' . $balance->freeCreditsResetIn . "\n"; 532 | 533 | // Prints out something like: 534 | // Credit packs: 956.332 535 | // Free daily credits: 128.66 536 | // Free daily credits will reset in 09:08:23 537 | ``` 538 | 539 | To add credit packs to your Verifalia account visit [https://verifalia.com/client-area#/credits/add](https://verifalia.com/client-area#/credits/add). 540 | 541 | ## Changelog / What's new 542 | 543 | This section lists the changelog for the current major version of the library: for older versions, 544 | please see the [project releases](https://github.com/verifalia/verifalia-php-sdk/releases). 545 | 546 | ### v3.1 547 | 548 | Released on November 13th, 2024 549 | 550 | - Added support for importing and checking mailing list files in multiple file formats, including CSV, TSV, Excel (.xls and .xlsx) and plain text files (.txt) 551 | - Added support for exporting completed email verification entries in multiple file formats, including CSV, TSV and Excel (.xls and .xlsx) 552 | - Extended support for GuzzleHTTP to version 7.* 553 | - Fixed an issue with the parsing of the waiting time hint while waiting for job completion 554 | - Improved documentation 555 | 556 | ### v3.0 557 | 558 | Released on February 1st, 2024 559 | 560 | - Added support for Verifalia API v2.5 561 | - Added support for classification override rules 562 | - Added support for AI-powered suggestions 563 | - Added support for client-certificate authentication 564 | - Added support for bearer authentication 565 | - Added support for completion callbacks 566 | - Added support for submission and polling wait time 567 | - Added PHPDoc annotations 568 | - Improved sleeping time coercion while waiting for job completion 569 | - Breaking change: minimum PHP version requirement increased to **PHP 7.0** 570 | - Breaking change: the `submit()` method now, by default, waits for the email verification job to complete 571 | - Breaking change: renamed `IAuthenticator` interface to `AuthenticationProvider` --------------------------------------------------------------------------------