Upgrading
On this page:
Upgrading to v6
This version adds numerous new features and a completely new look to Mailcoach.
Some notable features:
- Completely new design with improved UX
- Add & manage multiple mailers with different providers with automatic setup
- Show a website archive of your email list’s campaigns
- A full-featured template system
- A new & improved Markdown editor
- Send outgoing webhooks
- Improved list insights & charts
- A new “Manage preferences” screen where subscribers can manage the (public) tags attached to them
- A command palette
- Automations can be configured to run more than once for a subscriber
- The ability to override, replace and extend every page of Mailcoach
Updating your composer.json
- If your
composer.json
containsspatie/mailcoach-ui
, you can remove this as it’s now included within the corelaravel-mailcoach
package. - Update the requirement of
spatie/laravel-mailcoach
to^6.0
- Mailcoach now requires PHP 8.1, so make sure to update that requirement as well
"require": { - "php": "^8.0", + "php": "^8.1", "fruitcake/laravel-cors": "^2.0.5", "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^9.0", "laravel/horizon": "^5.9", "laravel/tinker": "^2.7", - "spatie/laravel-mailcoach": "^5.0", + "spatie/laravel-mailcoach": "^6.0", - "spatie/mailcoach-ui": "^5.0" },
Composer scripts
- The composer hook command has been renamed, make sure to edit this in your
scripts
section if it is present, usually only in a standalone installation: - The necessary migrations will now be published by the publish command
"scripts": { "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi" ], "post-root-package-install": [ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], "post-create-project-cmd": [ "@php artisan key:generate --ansi", - "@php artisan vendor:publish --tag mailcoach-migrations", - "@php artisan vendor:publish --tag mailcoach-ui-migrations", "@php artisan mailcoach:prepare-git-ignore", - "@php artisan mailcoach:execute-composer-hook" + "@php artisan mailcoach:publish" ], "post-update-cmd": [ - "@php artisan mailcoach:execute-composer-hook", + "@php artisan mailcoach:publish", "@php artisan vendor:publish --tag=laravel-assets --ansi --force" ] }
Remove the mailcoach-ui routes
Mailcoach UI required separate routes to be registered, in the standalone version these are registered inside your RouteServiceProvider
, you can remove these and replace them by the default Mailcoach route macro:
public function boot() { ... - Route::mailcoachUi('/'); + Route::mailcoach('/'); ... }
After this, you can run a composer update
Update your Schedule
There have been a few changes to the scheduled commands, make sure your Mailcoach commands in your app/Console/Kernel.php
file look like this:
protected function schedule(Schedule $schedule) { $schedule->command('mailcoach:send-automation-mails')->everyMinute(); $schedule->command('mailcoach:send-scheduled-campaigns')->everyMinute(); $schedule->command('mailcoach:send-campaign-mails')->everyMinute(); $schedule->command('mailcoach:run-automation-triggers')->everyMinute(); $schedule->command('mailcoach:run-automation-actions')->everyMinute(); $schedule->command('mailcoach:calculate-statistics')->everyMinute(); $schedule->command('mailcoach:calculate-automation-mail-statistics')->everyMinute(); $schedule->command('mailcoach:send-campaign-summary-mail')->hourly(); $schedule->command('mailcoach:cleanup-processed-feedback')->hourly(); $schedule->command('mailcoach:send-email-list-summary-mail')->mondays()->at('9:00'); $schedule->command('mailcoach:delete-old-unconfirmed-subscribers')->daily(); }
Methods like runInBackground()
and withoutOverlapping()
are no longer necessary as we dispatch unique jobs inside the command instead.
Update the login route
Mailcoach has now prefixed its authentication routes, if you don’t have any login route of your own, add the following to your app/Exceptions/Handler.php
:
protected function unauthenticated($request, AuthenticationException $exception) { return $this->shouldReturnJson($request, $exception) ? response()->json(['message' => $exception->getMessage()], 401) : redirect()->guest($exception->redirectTo() ?? route('mailcoach.login')); }
Configuration updates
- Delete the
config/mailcoach-ui.php
file if present in your installation, be sure to port over any changes to theconfig/mailcoach.php
config file.
A full diff of the config file can be found here
We recommend running php artisan vendor:publish --tag=mailcoach-config --force
which will override your old configuration file with the new one. Using a git diff you can then re-apply any changes you previously made to your configuration file.
Notable changes to the config file are documented below:
Throttling
Throttling config is no longer set inside the Mailcoach configuration file, these settings can be controlled for each mailer
Welcome mails
Any configuration relating to welcome mails have been removed, you can now create a welcome automation instead.
Livewire
All views now use Livewire components which you can override in the config to add your own functionality if necessary.
Horizon
A schedule queue was added to Mailcoach, make sure your horizon config contains it:
'mailcoach-general' => [ 'connection' => 'mailcoach-redis', - 'queue' => ['general', 'mailcoach', 'mailcoach-feedback', 'send-mail', 'send-automation-mail'], + 'queue' => ['general', 'mailcoach-schedule', 'mailcoach', 'mailcoach-feedback', 'send-mail', 'send-automation-mail'], 'balance' => 'auto', 'processes' => 10, 'tries' => 2, 'timeout' => 60 * 60, ],
If you’re using simple balancing, make sure the schedule queue is defined early as that determines priority.
User model
If you use your own User model, make sure to replace the one in config/mailcoach.php
with your own:
'models' => [ ... - 'user' => \Spatie\Mailcoach\Domain\Settings\Models\User::class, + 'user' => \App\Models\User::class, ... ]
The standalone starter project defined the Auth user model as \Spatie\MailcoachUi\Models\User
, replace this with the new User model, or your own model in config/auth.php
.
'users' => [ 'driver' => 'eloquent', - 'model' => Spatie\MailcoachUi\Models\User::class, + 'model' => \Spatie\Mailcoach\Domain\Settings\Models\User::class, ],
Views
Delete the resources/views/vendor/mailcoach
folder if you did not have any changes made to the views.
Otherwise, we recommend deleting the folder anyway and re-publishing to port over any of your changes.
Run php artisan view:clear
to delete any compiled views.
Migration changes
There have been a few additions and changes to the Mailcoach tables, you can use the migration below to update your database:
Some notable changes:
- All models now have a UUID
- Custom confirmation mails now use transactional mails instead of raw html
Make sure you have doctrine/dbal
installed in your app for rename migrations to work.
composer require doctrine/dbal
Below you can find the full upgrade to v6 migration, depending on how much data you have this could take a long time. It might be a good idea to split this up into separate migrations.
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\DB; return new class extends Migration { public function up() { Schema::table('mailcoach_email_lists', function (Blueprint $table) { $table->unique('uuid'); $table->foreignId('confirmation_mail_id')->after('requires_confirmation')->nullable(); $table->after('allowed_form_extra_attributes', function (Blueprint $table) { $table->string('honeypot_field')->nullable(); $table->boolean('has_website')->default(false); $table->boolean('show_subscription_form_on_website')->default(true); $table->string('website_slug')->nullable(); $table->string('website_title')->nullable(); $table->text('website_intro')->nullable(); $table->string('website_primary_color')->nullable(); $table->string('website_theme')->default('default'); $table->text('website_subscription_description')->nullable(); }); $table->dropColumn([ 'confirmation_mail_subject', 'confirmation_mail_content', 'send_welcome_mail', 'welcome_mail_subject', 'welcome_mail_content', 'welcome_mailable_class', 'welcome_mail_delay_in_minutes', ]); }); Schema::table('mailcoach_subscribers', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_segments', function (Blueprint $table) { $table->uuid('uuid')->after('id'); }); DB::table('mailcoach_segments')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_segments', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_campaigns', function (Blueprint $table) { $table->unique('uuid'); $table->index('sent_at'); $table->index(['scheduled_at', 'status']); $table->boolean('show_publicly')->after('subject')->default(true); $table->unsignedBigInteger('template_id')->after('email_list_id')->nullable(); $table->after('segment_description', function (Blueprint $table) { $table->boolean('add_subscriber_tags')->default(false); $table->boolean('add_subscriber_link_tags')->default(false); }); $table->dropColumn([ 'track_opens', 'track_clicks', ]); }); Schema::table('mailcoach_campaign_links', function (Blueprint $table) { $table->uuid('uuid')->after('id'); }); DB::table('mailcoach_campaign_links')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_campaign_links', function (Blueprint $table) { $table->unique('uuid'); }); Schema::rename('mailcoach_transactional_mails', 'mailcoach_transactional_mail_log_items'); Schema::table('mailcoach_transactional_mail_log_items', function (Blueprint $table) { $table->uuid('uuid')->after('id'); $table->json('attachments')->after('bcc')->nullable(); $table->dropColumn([ 'track_opens', 'track_clicks', ]); }); DB::table('mailcoach_transactional_mail_log_items')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_transactional_mail_log_items', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_automation_mails', function (Blueprint $table) { $table->unique('uuid'); $table->unsignedBigInteger('template_id')->after('subject')->nullable(); $table->after('utm_tags', function (Blueprint $table) { $table->boolean('add_subscriber_tags')->default(false); $table->boolean('add_subscriber_link_tags')->default(false); }); $table->dropColumn([ 'track_opens', 'track_clicks', ]); }); Schema::table('mailcoach_sends', function (Blueprint $table) { $table->renameColumn('transactional_mail_id', 'transactional_mail_log_item_id'); $table->timestamp('invalidated_at')->after('sent_at')->nullable(); $table->index('transport_message_id'); $table->index(['sending_job_dispatched_at', 'sent_at'], 'sent_index'); }); Schema::table('mailcoach_campaign_clicks', function (Blueprint $table) { $table->uuid('uuid')->after('id'); }); DB::table('mailcoach_campaign_clicks')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_campaign_clicks', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_campaign_opens', function (Blueprint $table) { $table->uuid('uuid')->after('id'); }); DB::table('mailcoach_campaign_opens')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_campaign_opens', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_campaign_unsubscribes', function (Blueprint $table) { $table->uuid('uuid')->after('id'); }); DB::table('mailcoach_campaign_unsubscribes')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_campaign_unsubscribes', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_send_feedback_items', function (Blueprint $table) { $table->uuid('uuid')->after('id'); }); DB::table('mailcoach_send_feedback_items')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_send_feedback_items', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_templates', function (Blueprint $table) { $table->uuid('uuid')->after('id'); $table->boolean('contains_placeholders')->after('name')->default(false); }); DB::table('mailcoach_templates')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_templates', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_subscriber_imports', function (Blueprint $table) { $table->unique('uuid'); $table->text('errors')->after('imported_subscribers_count')->nullable(); $table->dropColumn('error_count'); }); Schema::table('mailcoach_tags', function (Blueprint $table) { $table->uuid('uuid')->after('id'); $table->boolean('visible_in_preferences')->after('type')->default(false); }); DB::table('mailcoach_tags')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_tags', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_email_list_subscriber_tags', function (Blueprint $table) { $table->index(['subscriber_id', 'tag_id'], 'subscriber_id_tag_id_index'); }); Schema::table('mailcoach_automations', function (Blueprint $table) { $table->unique('uuid'); $table->after('interval', function (Blueprint $table) { $table->boolean('repeat_enabled')->default(false); $table->boolean('repeat_only_after_halt')->default(true); }); }); Schema::table('mailcoach_automation_actions', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_automation_triggers', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_automation_action_subscriber', function (Blueprint $table) { $table->uuid('uuid')->after('id'); $table->index('action_id'); $table->index('subscriber_id'); }); DB::table('mailcoach_automation_action_subscriber')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_automation_action_subscriber', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_automation_mail_opens', function (Blueprint $table) { $table->uuid('uuid'); }); DB::table('mailcoach_automation_mail_opens')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_automation_mail_opens', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_automation_mail_links', function (Blueprint $table) { $table->uuid('uuid'); }); DB::table('mailcoach_automation_mail_links')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_automation_mail_links', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_automation_mail_clicks', function (Blueprint $table) { $table->uuid('uuid'); }); DB::table('mailcoach_automation_mail_clicks')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_automation_mail_clicks', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_automation_mail_unsubscribes', function (Blueprint $table) { $table->uuid('uuid'); }); DB::table('mailcoach_automation_mail_unsubscribes')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_automation_mail_unsubscribes', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_transactional_mail_opens', function (Blueprint $table) { $table->uuid('uuid')->index(); }); DB::table('mailcoach_transactional_mail_opens')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_transactional_mail_opens', function (Blueprint $table) { $table->unique('uuid'); }); Schema::table('mailcoach_transactional_mail_clicks', function (Blueprint $table) { $table->uuid('uuid'); }); DB::table('mailcoach_transactional_mail_clicks')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_transactional_mail_clicks', function (Blueprint $table) { $table->unique('uuid'); }); Schema::rename('mailcoach_transactional_mail_templates', 'mailcoach_transactional_mails'); Schema::table('mailcoach_transactional_mails', function (Blueprint $table) { $table->uuid('uuid')->after('id'); $table->unsignedBigInteger('template_id')->after('bcc')->nullable(); $table->dropColumn([ 'track_opens', 'track_clicks', ]); }); DB::table('mailcoach_transactional_mails')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_transactional_mails', function (Blueprint $table) { $table->unique('uuid'); }); if (! Schema::hasColumn('users', 'welcome_valid_until')) { Schema::table('users', function (Blueprint $table) { $table->timestamp('welcome_valid_until')->nullable(); }); } if (! Schema::hasTable('mailcoach_uploads')) { Schema::create('mailcoach_uploads', function (Blueprint $table) { $table->bigIncrements('id'); $table->uuid('uuid')->unique(); $table->timestamps(); }); } else { Schema::table('mailcoach_uploads', function (Blueprint $table) { $table->uuid('uuid')->after('id'); }); DB::table('mailcoach_uploads')->update([ 'uuid' => DB::raw('uuid()'), ]); Schema::table('mailcoach_uploads', function (Blueprint $table) { $table->unique('uuid'); }); } if (! Schema::hasTable('mailcoach_settings')) { Schema::create('mailcoach_settings', function (Blueprint $table) { $table->string('key')->index(); $table->longText('value')->nullable(); }); } Schema::create('mailcoach_mailers', function (Blueprint $table) { $table->id(); $table->uuid()->unique(); $table->string('name'); $table->string('config_key_name')->index(); $table->string('transport'); $table->longText('configuration')->nullable(); $table->boolean('default')->default(false); $table->boolean('ready_for_use')->default(false); $table->timestamps(); }); Schema::create('mailcoach_webhook_configurations', function (Blueprint $table) { $table->id(); $table->uuid()->unique(); $table->string('name'); $table->text('url'); $table->string('secret'); $table->boolean('use_for_all_lists')->default(true); $table->timestamps(); }); Schema::create('mailcoach_webhook_configuration_email_lists', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('webhook_configuration_id'); $table->unsignedBigInteger('email_list_id'); $table->timestamps(); $table ->foreign('webhook_configuration_id', 'wc_idx') ->references('id')->on('mailcoach_webhook_configurations') ->cascadeOnDelete(); $table ->foreign('email_list_id', 'mel_idx') ->references('id')->on('mailcoach_email_lists') ->cascadeOnDelete(); }); if (! Schema::hasColumn('personal_access_tokens', 'expires_at')) { Schema::table('personal_access_tokens', function (Blueprint $table) { $table->timestamp('expires_at')->after('last_used_at')->nullable(); }); } } };
If you’re already using Laravel MediaLibrary make sure that the media
table in your project has the same fields as the media
migration included with Mailcoach.
Open & Click tracking
The Campaign, AutomationMail and TransactionalMail no longer have the ability to enable/disable open & click tracking.
This is because this is a setting at the provider level that Mailcoach has no direct control over. You could disable tracking in Mailcoach but still have tracking links & pixels present because the settings was enabled at your provider.
When setting up mailers through the new mailer UI, Mailcoach will make the necessary API calls to enable/disable tracking for that provider.
Transactional models renamed
The Spatie\Mailcoach\Domain\TransactionalMail\Models\TransactionalMail
model has been renamed to Spatie\Mailcoach\Domain\TransactionalMail\Models\TransactionalMailLogItem
The Spatie\Mailcoach\Domain\TransactionalMail\Models\TransactionalMailTemplate
model has been renamed to Spatie\Mailcoach\Domain\TransactionalMail\Models\TransactionalMail
Make sure to rename these in the correct order if you reference these inside your application.
Translations
We’ve reworked how Mailcoach translations work, if you had published them make sure to republish the translations.
If you referenced Mailcoach translations somewhere in your app, make sure to replace it by the new helper, the mailcoach -
prefix is no longer necessary:
- {{ __('mailcoach - Some translation string') }} + {{ __mc('Some translation string') }} - {{ trans_choice('mailcoach - One|More', 2) }} + {{ __mc_choice('One|More', 2) }}
API
All API resources & endpoints now use uuid
s instead of id
s for references to models. Update your integrations with the API if necessary.
Unlayer users
This version of Mailcoach uses an newer version of Unlayer. Your old templates might not be compatible. To make Unlayer templates compatible, you might try the code mentioned in this issue. Also take look at this issue
If you still reference Route::mailcoachUnlayer()
somewhere in project, you should remove it.