Installation:
composer require willdurand/email-reply-parser
Add to composer.json if using a monorepo or constrained environment.
First Use Case: Parse an incoming email reply to extract the original message and new content. Example:
use EmailReplyParser\Parser\EmailParser;
$parser = new EmailParser();
$email = $parser->parse($rawEmailContent);
Key Methods to Explore:
$email->getFragments() → Array of Fragment objects (original message, new content, signatures).$email->getVisibleText() → Cleaned-up text (ignores quoted replies/signatures).$fragment->isSignature() → Check if a fragment is a signature.$email = (new EmailParser())->parse($replyEmail);
$newContent = implode("\n", array_filter($email->getFragments(), fn($f) => !$f->isSignature() && !$f->isQuoted()));
$fragments = $email->getFragments();
$originalMessage = end(array_filter($fragments, fn($f) => $f->isQuoted() && !$f->isSignature()));
$signatures = array_filter($email->getFragments(), fn($f) => $f->isSignature());
use Illuminate\Support\Facades\Mail;
use EmailReplyParser\Parser\EmailParser;
Mail::raw($rawEmail, function ($message) {
$parser = new EmailParser();
$email = $parser->parse($rawEmail);
$message->subject("Re: " . $email->getVisibleText());
});
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton(EmailParser::class, fn() => new EmailParser());
}
// app/Console/Commands/ParseEmailCommand.php
use EmailReplyParser\Parser\EmailParser;
public function handle()
{
$email = (new EmailParser())->parse($this->argument('content'));
$this->info("Visible Text:\n" . $email->getVisibleText());
$this->info("\nFragments:");
foreach ($email->getFragments() as $fragment) {
$this->line("- " . ($fragment->isSignature() ? "[SIGNATURE]" : "[CONTENT]"));
}
}
// app/Http/Middleware/ParseIncomingEmail.php
public function handle($request, Closure $next)
{
if ($request->is('api/webhooks/email')) {
$email = (new EmailParser())->parse($request->getContent());
$request->merge(['parsed_email' => $email]);
}
return $next($request);
}
False Signature Detection
$fragment->isSignature() && $fragment->getContent() !== "Expected signature pattern";
getVisibleText() as a fallback if fragments are unreliable.Non-Standard Email Formats
> vs >) may break parsing.strip_tags() or a regex to normalize formatting:
$normalizedContent = preg_replace('/<[^>]*>/', '', $rawEmail);
Performance with Large Emails
Locale-Specific Quoting
» or other symbols for quoting, which the parser may not handle.EmailParser class to support custom quote markers:
$parser = new EmailParser();
$parser->setQuoteMarkers(['»', '>']);
Inspect Fragments Manually
foreach ($email->getFragments() as $i => $fragment) {
echo "Fragment $i: " . ($fragment->isSignature() ? "[SIGNATURE]" : "[CONTENT]") . "\n";
echo $fragment->getContent() . "\n\n";
}
Test with Real-World Examples
tests/emails/ directory and write assertions:
$this->assertCount(2, $email->getFragments());
$this->assertTrue($email->getFragments()[0]->isQuoted());
Handle Edge Cases
empty($email->getFragments()).try {
$email = (new EmailParser())->parse($rawEmail);
} catch (\Exception $e) {
// Fallback: return raw content or log the error
}
Custom Fragment Types
Fragment class to add metadata (e.g., isSpam(), getSentimentScore()).Post-Processing Hooks
EmailParser class to modify fragments after parsing:
class CustomEmailParser extends EmailParser {
protected function postProcessFragments(array $fragments): array {
return array_map(function ($fragment) {
if ($fragment->isQuoted()) {
$fragment->setContent(str_replace('old-term', 'new-term', $fragment->getContent()));
}
return $fragment;
}, $fragments);
}
}
Integration with Laravel Notifications
$newContent = implode("\n", array_filter($email->getFragments(), fn($f) => !$f->isQuoted()));
Notification::send($user, new CustomEmailNotification($newContent));
config() or a service container binding.How can I help you explore Laravel packages today?