laravel-mailbox
laravel-mailbox copied to clipboard
[Docs] How to create a driver ?
@mpociot you did a very great job with Laravel Mailbox. By building https://github.com/mailcare/mailcare and maintaining https://github.com/php-mime-mail-parser/php-mime-mail-parser, I already dreamt to create this connector for laravel, you did it with a very simple way, easy to use, .... so nice.
Do you have a doc to create a custom driver I would like to try to add the driver mailcare ?
With mailcare (only incoming emails) I can call a webhook like MailGun or SendGrid does:

:clap: @mpociot Have a good day
If you send the raw MIME, I think it's probaby just:
- Create a Driver like https://github.com/beyondcode/laravel-mailbox/blob/master/src/Drivers/SendGrid.php which registers a controller in the prefix, with
mailparse - Create a Request that gets the full mime message (if available) from the Request: https://github.com/beyondcode/laravel-mailbox/blob/master/src/Http/Requests/SendGridRequest.php
- Create the controller you register with the Driver, and use the Request you created to call the Mailbox with the Mime message: https://github.com/beyondcode/laravel-mailbox/blob/master/src/Http/Controllers/SendGridController.php
- Finally register the Driver in https://github.com/beyondcode/laravel-mailbox/blob/master/src/MailboxManager.php
(But not tested)
Although I'm not really sure why there is 1 driver, as the Driver only registers, it would be just as easy to allow multiple drivers, right? Eg. both Sendgrid and Mailgun combined.
@barryvdh I thought about that too but figured it might be nicer to not expose unnecessary driver API endpoints if it's not needed. And I do not know if you would actually use two different services to catch incoming emails.
Regarding creating your own driver, @barryvdh explained all you need to do.
To register the driver, you can do this in your AppServiceProvider register method:
Mailbox::extend('mailparse', function($app) {
// return a "DriverInterface" implementation
});
Thanks a lot I gonna try.
It will be nice to add the steps in the docs
Just as an aside, we are developing a service that needs to route emails from multiple sources after parsing the contents. We may have multiple IMAP boxes, web hooks, a script running behind a local mail server, etc. It does feel like it could get complicated quickly if this package handled multiple drivers, so perhaps this kind of thing is best suited to a single driver with a multi-source collector sat behind it. That single driver could also accept metadata, such as which source system the email arrived from (and that could maybe be injected into the email headers).
But I'm thinking aloud. Merging together email sources is a very real need, but I think that is another layer outside of this specific package.
Great job!. What about parsing emails directly from POP3 or IMAP? Is that possible?
@mpociot In your Mailbox::extend example, whats gets added to register a new driver. Do you mind extending your example a bit more please.
Thanks!
@barryvdh @mpociot I've tried adding a custom driver for Zapier (as a poor man's alternative to hooking up IMAP/POP properly), but it won't match a route.
The $matchedRoutes array inside callMailboxes is empty. Maybe Barry's explanation is missing a step where we set up how to match a route?
I've created this so far;
app/Http/Controllers/Mailbox/ZapierController.php
<?php
namespace App\Http\Controllers\Mailbox;
use App\Http\Requests\Mailbox\ZapierRequest;
use BeyondCode\Mailbox\Facades\Mailbox;
use Illuminate\Routing\Controller;
class ZapierController extends Controller
{
public function __construct()
{
$this->middleware('laravel-mailbox');
}
public function __invoke(ZapierRequest $request)
{
Mailbox::callMailboxes($request->email());
}
}
app/Mailbox/Drivers/Zapier.php
<?php
namespace App\Mailbox\Drivers;
use App\Http\Controllers\Mailbox\ZapierController;
use BeyondCode\Mailbox\Drivers\DriverInterface;
use Illuminate\Support\Facades\Route;
/**
* Class Zapier
* @package App\Mailbox\Drivers
*/
class Zapier implements DriverInterface
{
public function register()
{
Route::prefix(config('mailbox.path'))->group(function () {
Route::post('/zapier', ZapierController::class);
});
}
}
app/Providers/AppServiceProvider.php
...
public function register()
{
Mailbox::extend('zapier', function($app) {
return new \App\Mailbox\Drivers\Zapier;
});
}
...
app/Http/Requests/Mailbox/ZapierRequest.php
<?php
namespace App\Http\Requests\Mailbox;
use BeyondCode\Mailbox\InboundEmail;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Validator;
class ZapierRequest extends FormRequest
{
public function validator()
{
return Validator::make($this->all(), [
'raw' => 'required'
]);
}
public function email()
{
/** @var InboundEmail $modelClass */
$modelClass = config('mailbox.model');
return $modelClass::fromMessage($this->createMime());
}
private function createMime()
{
$raw = \Arr::dot($this->get('raw'));
list($toName, $toEmail) = explode(' <', $raw['payload.headers.To']);
$toEmail = trim($toEmail, '> ');
list($fromName, $fromEmail) = explode(' <', $raw['payload.headers.From']);
$fromEmail = trim($fromEmail, '> ');
$message = new \Swift_Message();
$message->setTo([$toEmail], [$toName]);
$message->setFrom([$fromEmail], [$fromName]);
$message->setContentType('text/html');
$message->setBody($this->get('body_html'));
$message->setSubject($this->get('subject'));
return $message->toString();
}
}
.env
...
MAILBOX_DRIVER=zapier
MAILBOX_HTTP_USERNAME=username
MAILBOX_HTTP_PASSWORD=password
...
Unfortunately Zapier doesn't offer the option to send the complete raw MIME content, so I had to use Swift_Message to re-create it. Not all fields are included, obviously but this serves my use case. It could be cleaner as well, but for now I'm just trying to get it to work.
@peterjaap where are you matching a route exactly? You need to provide one of Mailbox::to/from/cc/bcc/fallback/catchAll methods.
Continuing on that, I've submitted a PR where extending drivers is done through config, not with overriding classes. It might help.
@Norgul I'm not, I just followed @barryvdh his example, and he doesn't mention a route. Not sure how to proceed now.
@peterjaap do a Mailbox::catchAll(function() { Log::info("Caught an email.") }) in boot() of your AppServiceProvider.
So is your hook reaching the controller? (Eg. debug in the __invoke in your controller). In that case, the email probably doesn't match. Like Norgul says, add a catchAll. Maybe the from/to emailaddress are not what you expected?