Skip to content

Emails

For sending emails this module provides standardised ways to:

  • send single emails (password reset email, order confirmation email, user defined mail)
  • send bulk emails (event canceled email, user defined mail)
  • send test emails

All emails are created from templates, so called template mailables. We will call them just mailables in all further documentation. Every kind of email has it’s own mailable (PasswordResetMailabe, UserFreeMailable etc.).

Creating mailables

A mailable consists of:

  1. a human readable label and description
  2. a mailable data class
  3. a default subject and body provided by a default template
  4. a placeholder provider class
  5. static attachments (optional)
  6. dynamic attachments (optional)

A new mailable class is created by extending the abstract TemplateMailable. Each mailable has a $label and a $description. Their value should be a translation string. You can use the static methods getLabel() and getDescription to display a translated label and description of the mailable to your users.

<?php

namespace JohnIt\Bc\Core\Domain\Mailables;

use JohnIt\Bc\Core\Domain\Mailables\Contracts\TemplateMailable;

class PasswordResetMailable extends TemplateMailable
{
    public static string $label = 'bc-core-domain::user.mails.resetPassword.name';

    public static string $description = 'bc-core-domain::user.mails.resetPassword.description';
}

Mailable data

A MailableData is the data source of a mailable. It is used for generating placeholder values and it is also passed to the attachments of a mailable. The MailableData is passed as second parameter to the constructor of a mailable.

<?php

namespace JohnIt\Bc\Core\Domain\DataTransferObjects;

use Illuminate\Support\Facades\Password;
use JohnIt\Bc\Core\Domain\Mailables\Contracts\MailableData;
use JohnIt\Bc\Core\Domain\Models\User;

class PasswordResetMailableData extends MailableData
{
    public readonly string $token;

    public function __construct(
        public readonly string $email,
        public readonly string $name,
        ?string $token = null
    ) {
        $this->token = $token ?? Password::createToken(User::where('email', $email)->firstOrFail());
    }

    public static function makeDummy(): static
    {
        return new static(
            name: 'Max Mustermann',
            email: 'max.mustermann@john-it.com',
            token: 'b1063f85cfcf5b1113a81524036b941cc4f7d9e7d7b24c9d1f28ed02aa3d041f',
        );
    }
}

Each mailable data must provide a static makeDummy factory method to generate data for test emails. To generate dummy mails a mailable must know which MailableData to use. So the fully qualyfied class name must be configured in MailableTemplate::$dataClass:

<?php

namespace JohnIt\Bc\Core\Domain\Mailables;

use JohnIt\Bc\Core\Domain\Mailables\Contracts\TemplateMailable;
use JohnIt\Bc\Core\Domain\DataTransferObjects\PasswordResetMailableData;

class PasswordResetMailable extends TemplateMailable
{
    public static string $label = 'bc-core-domain::user.mails.resetPassword.name';

    public static string $description = 'bc-core-domain::user.mails.resetPassword.description';

    public static string $dataClass = PasswordResetMailableData::class;
}

Placeholder provider

Subject and body of a mailable can contain placeholders to personalize emails. This placeholders are generated using a placeholder provider. The placeholder provider consumes the data provided by the mailable data to generate the placeholder values.

<?php

namespace JohnIt\Bc\Core\Domain\Placeholders;

use JohnIt\Bc\Core\Domain\DataTransferObjects\PasswordResetMailableData;
use JohnIt\Bc\Core\Domain\Placeholders\PlaceholderProvider;
use function e;

class PasswordResetPlaceholderProvider extends PlaceholderProvider
{
    protected static array $placeholders = [
        'UserName' => 'bc-core-domain::user.placeholders.UserName',
        'ResetLink' => 'bc-core-domain::user.mails.invite.placeholders.ActivationUrl.description',
        'SoftwareName' => 'Software name'
    ];

    public function __construct(
        private PasswordResetMailableData $data,
    )
    {}

    public static function makeDummy(): static
    {
        return new static(PasswordResetMailableData::makeDummy());
    }

    protected function hydrate(): static
    {
        $this->values = collect([
            'UserName' => $this->data->name,
            'ResetLink' => sprintf(
                '<a href="%s">%s</a>',
                route('users.reset-password', [
                    'token' => $this->data->token,
                    'email' => $this->data->email,
                ]),
                e(trans('bc-core-domain::user.mails.invite.labels.reset')),
            ),
            'SoftwareName' => config('app.name'),
        ]);

        return $this;
    }
}

To generate placeholders a mailable must know which PlaceholderProvider to use. So the fully qualyfied class name must be configured in MailableTemplate::$placeholderProviderClass:

<?php

namespace JohnIt\Bc\Core\Domain\Mailables;

use JohnIt\Bc\Core\Domain\Mailables\Contracts\TemplateMailable;
use JohnIt\Bc\Core\Domain\DataTransferObjects\PasswordResetMailableData;
use JohnIt\Bc\Core\Domain\PlaceholderProviders\PasswordResetPlaceholderProvider;

class PasswordResetMailable extends TemplateMailable
{
    public static string $label = 'bc-core-domain::user.mails.resetPassword.name';

    public static string $description = 'bc-core-domain::user.mails.resetPassword.description';

    public static string $dataClass = PasswordResetMailableData::class;

    public static string $placeholderProviderClass = PasswordResetPlaceholderProvider::class;
}

Default template

All mailables must provide subject and body. This is done by providing a default MailTemplate. Placeholders defined by the placeholder provider can be used inside subject and body.

<?php

namespace JohnIt\Bc\Core\Domain\Mailables;

use JohnIt\Bc\Core\Domain\Mailables\Contracts\TemplateMailable;
use JohnIt\Bc\Core\Domain\Models\MailTemplate;
use JohnIt\Bc\Core\Domain\DataTransferObjects\PasswordResetMailableData;
use JohnIt\Bc\Core\Domain\PlaceholderProviders\PasswordResetPlaceholderProvider;

class PasswordResetMailable extends TemplateMailable
{
    public static string $label = 'bc-core-domain::user.mails.resetPassword.name';

    public static string $description = 'bc-core-domain::user.mails.resetPassword.description';

    public static string $dataClass = PasswordResetMailableData::class;

    public static string $placeholderProviderClass = PasswordResetPlaceholderProvider::class;

    public function defaultTemplate(): MailTemplate
    {
        return MailTemplate::make([
            'subject' => 'Aktivierung Ihres neuen Benutzerkontos',
            'template_html' => ' <p>Hallo {{UserName}},</p>'.
                '<p>Es wurde ein neues Benutzerkonto in der Software {{{ SoftwareName }}} für Sie erstellt. Bitte aktivieren Sie Ihr Konto über folgenden Link:</p>'.
                '<p style="margin-top: 40px;margin-bottom: 40px; text-align: center;" > {{{ActivationLink}}}</p>'.
                '<p>Dieser Aktivierungslink ist 2 Wochen gültig. Danach wenden Sie sich bitte an Ihren Administrator.</p>',
        ]);
    }
}

Sending mailables

Usually there are two ways to instantiate a new mailable object:

  1. using a factory method
  2. manually instantiating a mailable using the new keyword

Sending test mails

All mailables provide a factory method to create a dummy mailable for testing purposes. In example for sending a preview. Dummy mailables use example data for placeholders and attachments:

use Illuminate\Support\Facades\Mail;
use JohnIt\Bc\Core\Domain\Mailables\PasswordResetMailable;

$mailable PasswordResetMailable::makeDummy();
Mail::to('test@john-it.com')->send($mailable);

Optionally you can pass a so called Envelope to the factory method to provide recipient information:

use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Support\Facades\Mail;
use JohnIt\Bc\Core\Domain\Mailables\PasswordResetMailable;

$envelope = new Envelope(to: $user->email);
$mailable PasswordResetMailable::makeDummy($envelope);
Mail::send($mailable);

Sending mails to Notifiables

Mailables used for bulk mails usually provide a factory method for sending mailables to notifiables. Notifiables are classes implementing the NotifiableContract interface which allows to automatically determine the recipient from the notifiable. Users, Contacts but also Licenses are notifiables.

use Illuminate\Support\Facades\Mail;
use JohnIt\Bc\Core\Domain\Mailables\PasswordResetMailable;

Mail::send(PasswordResetMailable::makeFromNotifiable($user));

Optionally you can pass an envelope to the factory method to specify CC, BCC, Reply-to or additional TO addresses.

use Illuminate\Support\Facades\Mail;
use JohnIt\Bc\Core\Domain\Mailables\PasswordResetMailable;
use Illuminate\Mail\Mailables\Envelope;

$envelope = new Envelope(replyTo: 'reply@john-it.com');
Mail::send(PasswordResetMailable::makeFromNotifiable($user, $envelope));

More advanced sending method

If none of the factory methods is siutable or available for you then you can always manually instantiate a mailable. In the first parameter an envelope must be provided. In the second parameter you pass the data the mailable needs for generating placeholders and attachments:

use Illuminate\Support\Facades\Mail;
use JohnIt\Bc\Core\Domain\DataTransferObjects\PasswordResetMailableData;
use JohnIt\Bc\Core\Domain\Mailables\PasswordResetMailable;
use Illuminate\Mail\Mailables\Envelope;

// Create envelop obejct
$envelope = new Envelope(to: $user->email);

// Create data object used by placeholders and attachments.
// The PasswordResetMailableData also creates the password reset token.
$userInviteData = new PasswordResetMailableData(
    email: $user->email,
    name: $user->name,
);

// Create mailable
$mailable new PasswordResetMailable(
    $envelope,
    $userInviteData
);

// send maiable
Mail::send($mailable);

If you’d like to manually send a mail using test data you can always call the makeDummy factory method on the coresponding data classes:

use JohnIt\Bc\Core\Domain\DataTransferObjects\PasswordResetMailableData;

$userInviteData = PasswordResetMailableData::makeDummy();

Attachments

Static attachments

Dynamic attachments

Domain model

Emails and Documents

Project makes use of a package called laravel-database-mail-templates by Spatie and extends it to allow custom placeholders and attachments. Make sure to read documenation for Laravel Mail and laravel-database-mail-templates before continuing.

All Mailables in a project need to inherit from \Domain\Core\Mails\Mailable class if there is no specific need not to do so. Lets look at \Domain\Core\Mails\InviteUserMail as an example. It overrides $name and $description properties as well as following methods: - default() : This method needs to return a default instance of this class. That object is used for editing and previewing in user interface. - defaultTemplate() : Return a default MailTemplate with subject and template_html keys set. You can make use of placeholders both in subject and template_html. - defaultAttachments() : Return a Collection of \JohnIt\Bc\Core\Domain\Models\Document descendants to be dynamically attached to this Mailable. - customPlaceholders() : Add a list of \Domain\Core\Placeholder classes to parent Collection and return it. These placeholders can be used both when sending mass e-mails or inside defaultTemplate() method.

Sending Mailable is no different from sending it using default Laravel methods as described in Laravel documentation.