Skip to content

Email campaigns since 2.6 ​

Newsletter-style emails built from blocks in the panel, rendered as email-safe HTML, checked for the usual pitfalls, and either copied into any sending tool or pushed to Brevo as a campaign draft. Evolve does not send campaign mail itself. Since 2.17 the module also syncs form leads to Brevo as contacts.

The module is off by default since 2.22. The mail templates for form mails, the email blocks and their renderer are part of the core and work without it. A site that uses campaigns or the contact sync sets EVOLVE_MODULE_EMAIL=true, see To 2.22.

  • Module key email
  • Default off (on until 2.21)
  • Switch EVOLVE_MODULE_EMAIL, see Modules
  • Role access evolve.roles.modules.email
  • Panel /evolve/email/campaign
  • Since 2.6

Configuration ​

VariableDefaultEffect
EMAIL_DRIVERunsetProvider. Unset means copy-paste only: the push button does not exist and there is no contact sync. brevo enables the push and the contact sync with Brevo. log writes contact operations to the log instead, for local work (no push).
EMAIL_BREVO_API_KEYfalls back to BREVO_API_KEYBrevo API key (v3).
EMAIL_BREVO_BASE_URLhttps://api.brevo.com/v3Brevo API base.
EMAIL_SENDER_NAME, EMAIL_SENDER_EMAILunsetDefault sender when a campaign sets none. The address must be a verified sender in Brevo.
EMAIL_MERGE_DIALECTbrevoWhich merge tags the unsubscribe, mirror and profile links use: brevo, mailcoach or mailchimp. Read by the core (evolve.mail.dialect) since 2.22.

The contact sync variables (EMAIL_BREVO_WEBHOOK_*, EMAIL_BREVO_DOI_TEMPLATE_ID, EMAIL_CONTACTS_*, EMAIL_ALERT_ADDRESS) are listed on Brevo. None of these are edited in the panel. The look of the rendered mail (EMAIL_BRAND_COLOR, EMAIL_CONTAINER_WIDTH, EMAIL_ASSET_BASE and the theme values) is set in evolve.mail of the core since 2.22, see Mail templates; a published config/email.php that still carries theme, dialect or asset-base keeps winning.

In the panel ​

Email > Campaigns at /evolve/email/campaign. A campaign has a name, a subject, a preheader (the preview text in the inbox), an optional sender name and address, a status (draft or final), and its blocks. Cmd-K: "Campaigns", "New campaign" (also found with "newsletter").

Content is built from the email blocks of the core, the same ones the mail templates use (the lead summary block is left out, a campaign has no submission). They never appear in the page block picker. 2.22 adds four blocks for object mails: Kopfzeile (with the "view in browser" link of the merge tag dialect), Abschnittstitel, Objektdaten and Ansprechperson, plus a centred layout with pill buttons through evolve.mail.theme.alignment and button-style.

Three actions sit on a campaign:

  • HTML (/evolve/email/campaign/{id}/html) renders the final document for copy and paste into any sending tool. The same screen lists the checks below.
  • Preview (.../preview) shows the rendered mail; ?draft=1 includes unpublished blocks.
  • Push sends the campaign to the configured driver, see below.

The campaigns list of the Email module

Checks before sending ​

Every render is checked. Errors block the push, warnings do not:

CheckLevelWhat it means
Placeholder linkserrorButtons or links still point at # or a placeholder.
Layout tableerrorThe document structure is broken, usually a block without its wrapper.
Gmail clippingerror above, warning near the limitGmail cuts mails above about 100 KB of HTML. Shorten the campaign or split it.
UnsubscribeerrorNo unsubscribe line. Every campaign needs the Abmelde-Zeile block.
Relative assetserrorAn image points at a relative path; set EMAIL_ASSET_BASE or APP_URL.
Image alt textwarningImages without alt text.
Image widthwarningImages without an explicit width, which Outlook renders at natural size.

Pushing to Brevo ​

With EMAIL_DRIVER=brevo and a key, the push creates an email campaign in Brevo with the campaign's name, subject (the name when empty), sender, preview text and the rendered HTML. Evolve stores Brevo's campaign ID; a second push updates the same Brevo campaign instead of creating another. Recipients, scheduling and the actual send happen in Brevo. A push with open errors is refused with a message; a Brevo API error is shown verbatim and reported to the error tracker.

Contacts 2.17 ​

With a driver set, the module also syncs form leads to the provider as contacts. Which forms sync, to which lists and under which consent is set per form in the Marketing card; the provider setup (key, double opt in template, webhook, environment variables, health check) is on Brevo.

The site keeps its own record of every synced address in email_contacts: consent, lists, the attributes last sent, and a status. Leads point to their contact (contact_id), so several leads of one address share one contact. The lead drawer shows the sync state of each lead, and since 2.18 the panel lists the contacts (see below).

StatusMeaning
pendingDouble opt in mail sent, the click is missing.
activeSynced; with consent once the box was ticked or the double opt in confirmed.
unsubscribedUnsubscribed at the provider. CONSENT_MARKETING is set to false there.
invalidHard bounce, too many soft bounces (5 by default) or a spam complaint. The address is removed from every list it was synced to.

A failed sync that is retried later never undoes an unsubscribe or a bounce that arrived in the meantime.

Overview and contacts in the panel 2.18 ​

With a driver set, Email in the sidebar has two more entries. Both only read: nothing there writes to the provider, and contacts cannot be edited or deleted in the panel, since leads and the webhook keep them up to date.

Übersicht (Overview) at /evolve/email/dashboard shows the nine newest contacts of the main list as the provider has them, with name, address and when they were added. Addresses the provider has blocklisted carry "Gesperrt" (Blocked), and contacts the site also knows link to their drawer. The main list is EMAIL_CONTACTS_MAIN_LIST (a list ID from the provider); without it the card shows the newest contacts of the whole account, including imports and sign ups from outside the site. Next to it are the local contacts per status and the open sync failures. When the provider is unreachable, the card shows its error and the local numbers stay. With the log driver only the local numbers appear.

Kontakte (Contacts) at /evolve/email/contact lists email_contacts with search (name, address) and a status filter. The drawer shows status with the unsubscribe or bounce time, consent with time and hash, the lists by name, the attributes last sent and the linked leads, each opening its lead drawer. Cmd-K: "Overview", "Contacts" (also found with "brevo").

A driver can supply the overview list by implementing Racerfish\Email\Contracts\ListsRecentContacts next to ContactDirectory; without it the overview shows only the local numbers.

Webhook events ​

The provider reports back through the webhook at api/email/webhook:

EventEffect
unsubscribedstatus unsubscribed, consent removed
hard bouncestatus invalid, removed from the lists
soft bouncecounted; at contacts.soft-bounce-threshold (5) handled like a hard bounce
spamlike a hard bounce, plus an alert mail
contact deletedthe contact is deleted on the site too; its leads stay

Other events are acknowledged and ignored.

Failures and retries ​

A provider that refuses a request or cannot be reached never affects the visitor. The lead is stored, the error is kept on the lead (shown as "Sync fehlgeschlagen" in the drawer) and in email_sync_failures. With a queue other than sync, the sync runs in the queue worker instead of the request.

php artisan email:retry-sync replays the open failures. The scheduler runs it daily; run it by hand after fixing the cause, for example a wrong key or the missing phone verification at Brevo. A failed lead is replayed from the lead itself, so a corrected field mapping applies on the retry. When one failure has failed contacts.alert-after-attempts times (10), one alert mail goes to EMAIL_ALERT_ADDRESS, falling back to HEALTH_WEBMASTER_ADDRESS. Spam complaints mail the same address. Without either address the alert only goes to the log. The health check counts the open failures.

Every request to the provider and every webhook call is logged in email_request_logs without the API key or webhook token, and pruned daily after contacts.request-log-retention-days (30).

For module developers ​

Attributes a module sends (for example the computed attributes of its form types) can be registered so that email:setup creates them and the health check expects them:

php
use Racerfish\Email\Contacts\AttributeSchemas;
use Racerfish\Email\Enums\AttributeType;

app(AttributeSchemas::class)->register(['BUDGET_MIN' => AttributeType::Number]);

Types are Text, Number, Date and Boolean.

Publishing ​

email.config publishes config/email.php. email.views publishes render/campaign.blade.php, the campaign wrapper around the core's mail document, to theme/views/vendor/email/render. The document layout and the block views belong to the core since 2.22 and publish with evolve.mail-views into theme/views/vendor/evolve/email; block copies that an earlier email.views left under theme/views/vendor/email/render/blocks are still read while the module is on. The email blueprints are package blueprints and are not published.

Verifying ​

Build a campaign with a logo header, a headline, a paragraph, a button and the unsubscribe line, open HTML and confirm the checks are green. Send the HTML to yourself through Brevo's test send, or push and use Brevo's preview. On staging, nothing is sent by Evolve, so the mail redirect from Mail does not apply here.