laravel-mailbox icon indicating copy to clipboard operation
laravel-mailbox copied to clipboard

[Docs] How to create a driver ?

Open eXorus opened this issue 6 years ago • 13 comments
trafficstars

@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: dwnwhohxqaasmou jpg large

:clap: @mpociot Have a good day

eXorus avatar Jan 23 '19 05:01 eXorus

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)

barryvdh avatar Jan 23 '19 07:01 barryvdh

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 avatar Jan 23 '19 08:01 barryvdh

@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
});

mpociot avatar Jan 23 '19 08:01 mpociot

Thanks a lot I gonna try.

eXorus avatar Jan 23 '19 10:01 eXorus

It will be nice to add the steps in the docs

eXorus avatar Jan 23 '19 10:01 eXorus

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.

judgej avatar Jan 24 '19 14:01 judgej

Great job!. What about parsing emails directly from POP3 or IMAP? Is that possible?

greykoda-admin avatar Mar 28 '19 03:03 greykoda-admin

@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!

1jason1 avatar Aug 13 '20 17:08 1jason1

@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 avatar Oct 21 '20 04:10 peterjaap

@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 avatar Dec 03 '20 11:12 Norgul

@Norgul I'm not, I just followed @barryvdh his example, and he doesn't mention a route. Not sure how to proceed now.

peterjaap avatar Dec 03 '20 13:12 peterjaap

@peterjaap do a Mailbox::catchAll(function() { Log::info("Caught an email.") }) in boot() of your AppServiceProvider.

Norgul avatar Dec 03 '20 13:12 Norgul

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?

barryvdh avatar Dec 03 '20 14:12 barryvdh