Skip to content

Brevo ​

Brevo (formerly Sendinblue) appears in three places: as the push target of the email campaigns module, as the contact directory that form leads are synced to (since 2.17), and possibly as the SMTP relay for the site's transactional mail. Campaign push and contact sync share the API key and EMAIL_DRIVER=brevo; the SMTP relay is configured separately. Both need the email module, which is off by default since 2.22 (EVOLVE_MODULE_EMAIL=true).

Campaign push ​

What you need from the Brevo account:

  1. An API key (v3) from SMTP & API > API keys. Keys are shown once.
  2. A verified sender (Senders & IP). The address in EMAIL_SENDER_EMAIL or on the campaign must be one of them, otherwise Brevo rejects the campaign.
  3. The contact lists the campaign will go to. The push does not touch lists; recipients are picked in Brevo after the push.
VariableNotes
EMAIL_DRIVER=brevoturns the push on
EMAIL_BREVO_API_KEYor BREVO_API_KEY, which some projects already have for SMTP
EMAIL_SENDER_NAME, EMAIL_SENDER_EMAILdefault sender
EMAIL_MERGE_DIALECT=brevothe default; keeps , and as Brevo expects

The push creates or updates a campaign draft in Brevo. Scheduling and sending stay in Brevo.

Contact sync 2.17 ​

With EMAIL_DRIVER=brevo, every form whose Marketing card has the sync switched on turns its leads into Brevo contacts: on the chosen lists, with the mapped attributes, and with marketing consent from a checkbox or a double opt in. Unsubscribes, bounces and spam complaints come back through a webhook. How editors set up a form is described in Forms and leads, what happens to contacts afterwards in Email campaigns.

Setting up an account takes these steps, in this order.

1. API key ​

The same v3 key as for the campaign push (EMAIL_BREVO_API_KEY, falling back to BREVO_API_KEY). If the account limits API access to authorised IP addresses (Security > Authorised IPs), add the outgoing IP of the web server. Otherwise Brevo answers 401 and the health check reports the key as rejected, although the key itself is fine.

2. Phone verification ​

New Brevo accounts must verify a phone number before Brevo sends any transactional mail, and the double opt in confirmation is one. Until then the API answers 403 SMTP account not activated. Evolve records each refused confirmation as a sync failure with that message, and the health check reports that Brevo has not activated the transactional mail relay (only while a form uses double opt in). Verify the number in the Brevo account, then run php artisan email:retry-sync to send the confirmations that were held back.

3. Sender domain ​

Authenticate the sender domain in Brevo (Senders, domains: SPF, DKIM, DMARC). The health check looks at the domain of MAIL_FROM_ADDRESS and reports it when Brevo does not know the domain or has not authenticated it. Use an address on an authenticated domain as the sender of the double opt in template as well.

4. Double opt in template ​

Only needed when a form uses double opt in. In Brevo, create a template of type Double opt-in. Its button or link must point at , Brevo replaces it with the confirmation link. Put the template's ID into EMAIL_BREVO_DOI_TEMPLATE_ID. The target after the click is set by Evolve with every request (a signed link back to the site), the template needs no redirect of its own.

Without the ID, double opt in leads are stored but fail to sync ("No double opt in template configured"), and the health check names the missing variable.

5. Attributes and lists ​

bash
php artisan email:setup --dry-run   # show what would be created
php artisan email:setup

email:setup creates the contact attributes Evolve sends, if the account lacks them, and can be run again at any time:

AttributeTypeContent
SOURCE_SITEtextEMAIL_CONTACTS_SITE, else the slug of APP_NAME. Tells the contacts of several sites in one account apart.
SOURCE_FORMtextname of the form the lead came from
SOURCE_DATEdateday of the sync
LANGtextlanguage the form was submitted in
CONSENT_MARKETINGbooleantrue with consent, false after an unsubscribe
CONSENT_DATEdateday the consent was given or confirmed
CONSENT_TEXT_VERSIONtexthash of the consent checkbox text the visitor saw

Attributes registered by modules (for example the computed attributes of a form type) are created too. Given and family name are Brevo's own attributes and are not created: they go to FIRSTNAME and LASTNAME, or to the names in EMAIL_CONTACTS_FIRSTNAME_ATTRIBUTE and EMAIL_CONTACTS_LASTNAME_ATTRIBUTE for accounts that use others (such as VORNAME and NACHNAME). Brevo silently drops attributes it does not know, so check these names once per account.

Lists are picked per form from the lists the account already has. To have email:setup create lists, name them in contacts.lists in config/email.php (publish tag email.config); they are created in the folder "Evolve", or in the first existing folder.

At the end the command prints the manual steps with the webhook URL of this install.

6. Webhook ​

Generate a random secret (openssl rand -hex 32) and set it as EMAIL_BREVO_WEBHOOK_SECRET. Then add a marketing webhook in Brevo:

  • URL: https://<domain>/api/email/webhook?token=<secret>
  • Events: unsubscribed, hard bounce, soft bounce, spam, contact deleted

Brevo does not sign its webhook calls, so the secret in the URL is the protection. Without a secret the endpoint answers 503, with a wrong token 403. To accept calls only from Brevo's servers in addition, put the IP ranges Brevo publishes for its webhooks into EMAIL_BREVO_WEBHOOK_ALLOWED_IPS, comma separated in CIDR notation; calls from other addresses get 403. Behind a proxy or load balancer this only works when Laravel sees the real client IP (trusted proxies).

Without the webhook, unsubscribes and bounces in Brevo never reach the site, and its contacts keep showing those addresses as active. What each event does is listed in Email campaigns.

Variables ​

VariableDefaultEffect
EMAIL_DRIVERunsetbrevo turns on campaign push and contact sync. log writes contact operations to the log instead, for local work. Unset: no contact sync, no Marketing card settings.
EMAIL_BREVO_API_KEYfalls back to BREVO_API_KEYAPI key (v3).
EMAIL_BREVO_BASE_URLhttps://api.brevo.com/v3API base.
EMAIL_BREVO_WEBHOOK_SECRETunsetToken in the webhook URL. Unset, the webhook answers 503.
EMAIL_BREVO_WEBHOOK_ALLOWED_IPSemptyComma separated CIDR ranges the webhook accepts. Empty accepts any address with a valid token.
EMAIL_BREVO_DOI_TEMPLATE_ID0 (none)ID of the double opt in template.
EMAIL_CONTACTS_SITEslug of APP_NAMEValue of SOURCE_SITE.
EMAIL_CONTACTS_MAIN_LISTunsetBrevo list ID whose newest contacts the email overview shows. Unset: the newest contacts of the account. 2.18
EMAIL_CONTACTS_FIRSTNAME_ATTRIBUTE, EMAIL_CONTACTS_LASTNAME_ATTRIBUTEFIRSTNAME, LASTNAMEBrevo attributes for given and family name.
EMAIL_ALERT_ADDRESSfalls back to HEALTH_WEBMASTER_ADDRESSReceives sync alerts and spam complaints. Sent through the site's own mailer, never through Brevo.

In config/email.php only: contacts.lists (lists email:setup creates, default none), contacts.soft-bounce-threshold (5), contacts.alert-after-attempts (10), contacts.request-log-retention-days (30, 0 turns the request log off), brevo.requests-per-second (10) and brevo.retry-backoff-ms ([1000, 2000], the short retry for 429, 5xx and network errors before a request counts as failed).

Health check ​

The Email directory check runs hourly and shows in Settings > Health. Without a driver it reports off. Otherwise it is green with "Contact directory reachable and set up", or a warning that names every problem it found:

MessageCause
No Brevo API key configuredEMAIL_BREVO_API_KEY and BREVO_API_KEY are empty.
Brevo rejects the API key401 or 403 from Brevo: wrong key, or an IP restriction (step 1).
Brevo is not reachableNetwork or server error, with Brevo's message.
A form uses double opt in, but EMAIL_BREVO_DOI_TEMPLATE_ID is not setStep 4.
A form uses double opt in, but Brevo has not activated the transactional mail relayStep 2, the phone verification.
No webhook secretStep 6.
The sender domain is not set up in Brevo / is not authenticatedStep 3, for the domain of MAIL_FROM_ADDRESS.
The account lacks attributesRun php artisan email:setup (step 5).
Contact sync failures are openLeads that did not reach Brevo, see retries.

The messages follow the panel language.

Staging ​

Brevo sends the double opt in mail itself, so the mail redirect of staging does not apply to it, and every synced lead becomes a real contact in the account. Point staging at a separate Brevo test account, or leave EMAIL_DRIVER unset or on log there. The alert mails go through the site's mailer and are redirected as usual.

Verifying ​

  1. php artisan email:setup --dry-run lists the attributes, then the health check is green.

  2. Switch on the sync for a form, pick a list, keep consent on Checkbox. Submit it with the consent box ticked. In Brevo the contact is on the list with SOURCE_SITE, SOURCE_FORM, LANG and the CONSENT_* attributes. The lead drawer shows "Synchronisiert" with the time.

  3. Switch to double opt in and submit with a new address. The confirmation mail arrives from Brevo, the lead shows "Bestätigung ausstehend". After the click the lead shows "Marketing erlaubt" with the consent time and the contact is on the list.

  4. Unsubscribe the test contact through a campaign mail or delete it in Brevo. With the webhook set up, the contact's status in the email_contacts table changes to unsubscribed (a deleted contact is removed there). To test the endpoint without Brevo, simulate the call:

    bash
    curl -s -X POST "https://<domain>/api/email/webhook?token=<secret>" \
      -H 'Content-Type: application/json' \
      -d '{"event":"unsubscribed","email":"you+test@example.com"}'

    The answer is {"status":"ok"}.

Transactional mail ​

A site may also send its own mail (form notifications, event confirmations, health check mails) through Brevo's SMTP relay. That is ordinary Laravel mail configuration, MAIL_MAILER=smtp with Brevo's host and the SMTP key from the same account, see Mail. Neither the campaign push nor the contact sync depends on it.

Verifying the campaign push ​

After a push, the campaign appears in Brevo under Campaigns > Email as a draft, with the preview text and sender filled in. A 401 in the panel message means the key is wrong or lacks the campaign permission; a 400 naming the sender means the address is not verified.