All postsTutorials
How to Test Email in PHP with PHPUnit
The Devmailr Team6 min read
PHP apps send plenty of email — Laravel, Symfony, or plain PHP. To test that a verification or reset email really goes out, your PHPUnit suite can read it from a disposable inbox. Here’s a small Guzzle-based client and a test.
A small client (Guzzle)
tests/Devmailr.php
<?php
use GuzzleHttp\Client;
class Devmailr
{
private Client $http;
public function __construct()
{
$this->http = new Client([
'base_uri' => 'https://api.devmailr.app/api/',
'headers' => ['Authorization' => 'Bearer ' . getenv('DEVMAILR_API_KEY')],
]);
}
public function createInbox(string $prefix): array
{
$res = $this->http->post('mailboxes', ['json' => ['prefix' => $prefix]]);
return json_decode((string) $res->getBody(), true);
}
public function waitForEmail(string $id, string $query = ''): array
{
// /wait long-polls — give Guzzle a read timeout above the wait
$res = $this->http->get("mailboxes/{$id}/wait?timeout=60&{$query}", ['timeout' => 65]);
return json_decode((string) $res->getBody(), true);
}
public function deleteInbox(string $id): void
{
$this->http->delete("mailboxes/{$id}");
}
}The test (PHPUnit)
tests/SignupEmailTest.php
<?php
use PHPUnit\Framework\TestCase;
final class SignupEmailTest extends TestCase
{
public function test_signup_sends_verification_code(): void
{
$dm = new Devmailr();
$inbox = $dm->createInbox('php-' . time());
// trigger your app to email a code to $inbox['address']
$this->signUp($inbox['address']);
$email = $dm->waitForEmail($inbox['_id'], 'has_code=true');
$this->assertStringContainsString('noreply@myapp.com', $email['from']);
$this->assertMatchesRegularExpression('/^\d{6}$/', $email['extractedCodes'][0]);
$dm->deleteInbox($inbox['_id']);
}
}Magic links and reset links
php
$email = $dm->waitForEmail($inbox['_id'], 'subject=' . urlencode('reset'));
$resetLink = $email['primaryLink'];Tips
- Set
DEVMAILR_API_KEYviaphpunit.xml<env>or a CI secret. - Give Guzzle a read timeout above the wait timeout (65s above).
- Unique prefix per test (
time()oruniqid()). - Laravel? Swap the trigger for your app’s HTTP test client or a queued mailable dispatch.
Related reading
Try it in two minutes
Create a disposable prefix@devmailr.app inbox over the API, send mail to it from your app, and read it in your tests. Free plan, no card required.