opcodesio/mail-parser
Simple, fast PHP 8+ email/MIME parser with a clean API—no mailparse extension required. Parse .eml strings/files, read headers (From/To/Subject/Date/size), and extract HTML/text bodies, parts, and attachments.
composer require opcodesio/mail-parser
use Opcodes\MailParser\Message;
// Parse from string
$message = Message::fromString($rawEmailString);
// Parse from file
$message = Message::fromFile(storage_path('emails/example.eml'));
$message->getHeaders(): Retrieve all headers.$message->getHtmlPart(): Extract HTML content.$message->getAttachments(): List attachments.public function handleUpload(Request $request)
{
$rawEmail = $request->file('email')->get();
$message = Message::fromString($rawEmail);
// Extract structured data
$data = [
'subject' => $message->getSubject(),
'from' => $message->getFrom(),
'to' => $message->getTo(),
'body_html' => $message->getHtmlPart()->getContent(),
'attachments' => collect($message->getAttachments())
->map(fn($part) => [
'name' => $part->getFilename(),
'size' => $part->getSize(),
]),
];
// Store or process further
Email::create($data);
}
.eml files from a directory (e.g., for archiving or analysis).$files = Storage::disk('emails')->files('inbox/');
foreach ($files as $file) {
$message = Message::fromFile($file);
processEmail($message); // Custom logic
Storage::disk('emails')->move($file, "processed/{$message->getMessageId()}");
}
foreach ($message->getAttachments() as $attachment) {
$path = storage_path("attachments/{$attachment->getFilename()}");
file_put_contents($path, $attachment->getContent());
// Optional: Log metadata
logger()->info("Saved attachment", [
'filename' => $attachment->getFilename(),
'size' => $attachment->getSize(),
]);
}
use Opcodes\MailParser\Message;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class ParseEmailJob implements ShouldQueue
{
use Queueable;
public function handle()
{
$message = Message::fromString($this->rawEmail);
// Process and store results
}
}
Message class to add domain-specific logic.class CustomEmailMessage extends Message
{
public function isSupportTicket()
{
return str_contains($this->getSubject(), ['support', 'help']);
}
public function getSupportCategory()
{
// Custom logic to categorize tickets
}
}
Nested Multipart Messages:
.eml files.$message->getParts() recursively to inspect structure.Header Encoding:
iso-8859-1) may appear garbled. Use $message->getRawHeaders() to inspect raw values if needed.Boundary Delimiters:
Large Attachments:
fopen) may be needed.RFC Compliance:
logger()->debug('Raw email:', ['content' => $rawEmail]);
dd($message->getParts()) to debug multipart structures.$contentType = $message->getHeader('Content-Type');
logger()->debug('Content-Type:', [$contentType]);
Message repeatedly in loops.$message->getHtmlPart()) instead of loading all parts.Custom Message Classes:
Extend Opcodes\MailParser\Message to add domain-specific methods:
class AppEmail extends Message
{
public function getSenderDomain()
{
return explode('@', $this->getFrom())[1];
}
}
Override Parsing Logic: Extend the parser to handle custom content types:
class CustomParser extends \Opcodes\MailParser\Parser
{
protected function parseContent($content, $contentType)
{
if (str_contains($contentType, 'application/vnd.ms-outlook')) {
return $this->handleOutlookSpecific($content);
}
return parent::parseContent($content, $contentType);
}
}
Laravel Macros:
Add helper methods to the Message class using Laravel’s macroable support:
\Opcodes\MailParser\Message::macro('isSpam', function () {
return str_contains(strtolower($this->getSubject()), ['free', 'win']);
});
.eml files (including edge cases like empty attachments, malformed boundaries).$message = Message::fromString($rawEmail);
$this->assertEquals('Test Subject', $message->getSubject());
$this->assertCount(1, $message->getAttachments());
Message class to return predictable data:
$mockMessage = Mockery::mock(Message::class);
$mockMessage->shouldReceive('getSubject')->andReturn('Mock Subject');
How can I help you explore Laravel packages today?