zbateson/mail-mime-parser
PSR-compliant, testable MIME email parser for PHP 8.1+ as an alternative to imap* and PEAR. Parses RFC 822/2822/5322 messages from strings, resources, or PSR-7 streams; standards-compliant yet forgiving, with a cleaned-up 4.x API.
Installation
composer require zbateson/mail-mime-parser
Requires PHP 8.1+.
Basic Parsing Parse a raw email string or file:
use ZBateson\MailMimeParser\Message;
$rawEmail = file_get_contents('email.eml');
$message = Message::from($rawEmail);
Access Key Data
echo $message->getSubject(); // Subject line
echo $message->getHeaderValue('From'); // Sender email
echo $message->getTextContent(); // Plaintext body
Process incoming emails in a Laravel queue job:
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\Job;
use ZBateson\MailMimeParser\Message;
class ProcessEmailJob implements ShouldQueue
{
use Queueable, SerializesModels;
public function handle(Job $job, string $rawEmail)
{
$message = Message::from($rawEmail);
// Extract and process data
$subject = $message->getSubject();
$attachments = $message->getAttachments();
// Save to database or trigger actions
}
}
// From a file
$message = Message::from('path/to/email.eml');
// From a stream (e.g., HTTP request body)
$stream = fopen('php://temp', 'r+');
fwrite($stream, $rawEmailData);
rewind($stream);
$message = $messageParser->parse($stream, true); // Auto-closes stream
// From a PSR-7 request (Laravel HTTP requests)
$message = Message::from($request->getContent());
// Headers
$from = $message->getHeader('From')->getAddresses()[0]->getEmail();
$to = $message->getHeader('To')->getAddresses();
// Body content
$textBody = $message->getTextContent();
$htmlBody = $message->getHtmlContent();
// Attachments
foreach ($message->getAttachments() as $attachment) {
$content = $attachment->getContent();
$filename = $attachment->getHeaderValue('Content-Disposition') ?? 'attachment.bin';
$attachment->saveContent(storage_path("app/attachments/{$filename}"));
}
// Check if multipart
if ($message->isMultipart()) {
foreach ($message->getParts() as $part) {
if ($part->isText()) {
echo $part->getTextContent();
} elseif ($part->isAttachment()) {
$part->saveContent(storage_path("app/attachments/{$part->getFilename()}"));
}
}
}
Parse incoming emails (e.g., from a mail server or API):
use Illuminate\Support\Facades\Http;
$response = Http::get('https://api.example.com/emails/latest');
$rawEmail = $response->body();
$message = Message::from($rawEmail);
// Process and store
Email::create([
'subject' => $message->getSubject(),
'body' => $message->getTextContent(),
// ...
]);
Generate outgoing emails with attachments:
use ZBateson\MailMimeParser\MessageBuilder;
$builder = new MessageBuilder();
$builder->from('sender@example.com')
->to('recipient@example.com')
->subject('Hello')
->text('Plaintext body');
$attachment = $builder->createAttachment(
file_get_contents('document.pdf'),
'document.pdf',
'application/pdf'
);
$builder->embed($attachment);
$rawEmail = $builder->getMessage()->toString();
Mail::raw($rawEmail)->send();
Register the parser as a singleton in AppServiceProvider:
public function register()
{
$this->app->singleton(MailMimeParser::class, function ($app) {
return new MailMimeParser();
});
}
Create middleware to parse raw email payloads:
namespace App\Http\Middleware;
use Closure;
use ZBateson\MailMimeParser\Message;
class ParseEmailMiddleware
{
public function handle($request, Closure $next)
{
if ($request->is('api/emails')) {
$request->merge([
'parsed_email' => Message::from($request->getContent())
]);
}
return $next($request);
}
}
namespace App\Console\Commands;
use Illuminate\Console\Command;
use ZBateson\MailMimeParser\Message;
use Illuminate\Support\Facades\Storage;
class ProcessEmailsCommand extends Command
{
protected $signature = 'emails:process {directory}';
protected $description = 'Process emails in a directory';
public function handle()
{
foreach (Storage::files($this->argument('directory')) as $file) {
$message = Message::from(Storage::get($file));
// Process and store
}
}
}
namespace App\Listeners;
use ZBateson\MailMimeParser\Message;
use App\Events\EmailReceived;
class ProcessIncomingEmail
{
public function handle(EmailReceived $event)
{
$message = Message::from($event->rawEmail);
// Extract and store data
}
}
parse() when using streams.true as the second argument to auto-close:
$handle = fopen('email.eml', 'r');
$message = $mailParser->parse($handle, true); // Auto-closes
getHeaderValue() or getTextContent() with proper encoding:
$subject = mb_convert_encoding(
$message->getSubject(),
'UTF-8',
'auto'
);
related types) may not parse as expected.getParts() and recursively check each part:
$this->parsePart($message);
private function parsePart($part) {
if ($part->isMultipart()) {
foreach ($part->getParts() as $subPart) {
$this->parsePart($subPart);
}
}
}
$attachment = $message->getAttachmentPart(0);
$attachment->saveContent(storage_path('app/attachments/large_file.bin'));
X-*) may not parse as expected.getHeader() and handle manually:
$customHeader = $message->getHeader('X-Custom-Header');
if ($customHeader) {
$value = $customHeader->getValue();
}
Pass a Psr\Log\LoggerInterface to the parser:
use Psr\Log\LoggerInterface;
$logger = \Log::getMonolog();
$parser = new MailMimeParser($logger);
Dump raw headers for debugging:
$rawHeaders = $message->getRawHeaders();
dump($rawHeaders);
Check for parsing errors:
if ($message->hasErrors()) {
foreach ($message->getErrors() as $error) {
\Log::error($error);
}
}
$header = $message->getHeader('Non-Existent-Header');
if (!$header) {
// Fallback logic
}
Extend ZBateson\MailMimeParser\Parser\AbstractParserService to handle custom MIME types:
namespace App\Services;
use ZBateson\MailMimeParser\Parser\AbstractParserService;
use ZBateson\MailMimeParser\Part\PartBuilder;
class CustomParserService extends AbstractParserService
{
public function canParse(PartBuilder $part): bool
How can I help you explore Laravel packages today?