Brevo

Add the people who submit your forms to a Brevo contact list, filling their Brevo contact attributes from the answers you mapped.

Brevo is an add-on for Forms. Once your Brevo account is connected, every form you edit gains a Brevo panel in its sidebar where you describe one or more actions. When the form is submitted each action creates a contact in the Brevo list you chose, filling that contact's details from the answers you mapped onto them.

Nothing else about the form changes: validation, messages, redirects, notification emails and the Submissions list all behave as they did before — as long as Brevo accepts the contact. What happens when it does not is the one thing on this page worth reading before you switch it on.

Where to find it

Two places, because there are two kinds of setting. The account connection is site-wide, at XPAC → Integrations on the Brevo tab, whose screen is headed Brevo Settings. What each form sends is per-form, in the Brevo panel of the document sidebar while you edit that form.

Forms has to be active

The plugin does nothing on its own. Without XPAC Forms it registers no settings tab, no panel and no routes, and shows an admin notice asking for Forms instead.

Connecting your account

Create an API key in Brevo, under SMTP & API → API Keys in your Brevo account (app.brevo.com/settings/keys/api). The plugin reads your contact lists, reads the contact attributes on your account, and creates contacts, so the key needs access to contacts.

Paste it into the Api Key field at XPAC → Integrations → Brevo and save. That single key serves every form on the site.

Missing credentials do not hide your actions

The Brevo panel and its saved actions remain visible when the key is missing or revoked. Account-backed pickers show the connection error, and a submission still creates its Forms entry before the queued Brevo delivery is marked failed. Save a working key, then retry that delivery from XPAC → Forms → Deliveries.

Treat the key as a secret

The settings screen uses a password control, but the value is still a credential stored in WordPress. Limit settings access, use a revocable key, and rotate it if it ever leaks.

Adding someone to a list

Edit the form, open the Brevo panel and press the round + button (Add action). The Create contact modal opens.

Choose the list under Select list.

Map the fields. Each row pairs a Local field from your form with a Remote field — one of your Brevo contact attributes — and you add as many rows as you need. One of them has to be EMAIL; read the warning below before you save.

Save the action, then update the form. Updating the form is what stores it.

An action can create or unsubscribe a contact, send a transactional template, or record an event for a Brevo automation. A form can hold several actions, and Conditional Logic can decide which ones apply to a particular submission.

A contact action needs an email address

Brevo identifies a contact by their email address, and this plugin refuses to send an action that has not got one. If no mapping row points at the EMAIL attribute — or if one does, but the visitor left that field blank — the queued delivery fails permanently with that explanation. The visitor's entry and notification flow are not rolled back.

So map your email field to EMAIL first, and make that field required. An optional email field means submissions that omit it cannot complete that action.

Mapping fields

  • Local field lists every field in the form that holds a value, under its label, or under its field name where the label is blank.
  • Remote field lists your Brevo contact attributes, with EMAIL always at the top. Only the ordinary and transactional ones are offered; the values Brevo works out for itself, and its other kinds of attribute, are left out.
  • That attribute list is the same whichever list you pick, because in Brevo the attributes belong to the account rather than to a list.
  • A row with either half left empty is ignored, so a half-finished mapping costs nothing.
  • Two rows using the same Local field do not both send — only the last one counts. One answer cannot be sent to two attributes.

Match the form field to the Brevo attribute type

A Brevo multiple-choice attribute receives an array of the selected labels. Other attributes receive one normalized value in their own boolean, number, date or text shape, and an email target is validated as an address. A File Upload answer is not uploaded into Brevo, so do not use one as a contact attribute.

The list and template pickers read Brevo's results in pages until the account has no more, rather than stopping at Brevo's first page.

Both dropdowns are read once per editing session

They are filled the first time the panel renders and then held for the rest of the session. A list or an attribute you create in Brevo while the editor is open will not appear until you reload the page.

Choose the contact policy deliberately

A create-contact action can add the address immediately or enable Ask for confirmation first (double opt-in). Double opt-in needs an active Brevo DOI template and a confirmation redirect; Brevo adds the contact to the chosen lists only after the recipient clicks. Without it, the contact joins immediately.

Re-subscribe contacts who opted out is a separate, explicit switch. Use it only when this form records a new grant of consent. The integration does not infer legal consent from an ordinary submission, so keep the relevant checkbox or wording in the stored Forms entry.

Deliveries, retries and duplicate limits

Forms writes the entry first and schedules Brevo afterwards. The visitor never waits for Brevo, and a remote failure cannot erase the submission. The Deliveries screen shows Brevo's message and keeps each completed action marked done, so a retry of one failed action does not resend its successful siblings.

Configuration, authentication and validation failures reported as 400/401/403/404/410/422 are left failed for an administrator to fix. Rate limits, 5xx replies and transport failures are retried after about 1, 5, 30 and 120 minutes, for at most five attempts. Restoring a key does not silently discard the old failure; retry it from the Deliveries screen.

Contact create/update and unsubscribe actions converge on contact state. Transactional template email uses a stable Brevo idempotency key, but Brevo retains that key for only 30 minutes. Double-opt-in requests and tracked events have no documented durable idempotency guarantee. A timeout can therefore leave those operations in an unknown, at-least-once state; use the entry/action identity in downstream automation where possible.

Editing and removing an action

Each row in the panel shows the action's name — Create contact — with a pencil (Edit action) that reopens the modal and a red X (Remove action) that asks Are you sure you want to remove the item? before acting.

Use the switch on a row to pause or resume that action without losing its settings. The X removes the selected row after confirmation; other actions keep their order and settings.

For developers

The API key is stored in the shared xpac option under the brevo_api_key key (packages/Brevo/Addon.php:158), written by a text field on the Integrations settings module (:45-79). The module is registered without show_in_rest (packages/Shared/Settings/Settings.php:481), so the option is not exposed on /wp/v2/settings; it is readable and writable on the settings module's own routes, wp-react-settings/xpac/read and …/update, both gated on manage_options (packages/Shared/Settings/Rest.php:27-37).

Per-form actions live in the form's form_settings post meta on the xpac-form post type, under a brevo key, registered on the Forms meta schema (Addon.php:205-246) and therefore readable and writable over the REST API at /wp-json/wp/v2/xpac-form/<id>. The default is an empty items array (:257-264):

{
	"brevo": {
		"items": [
			{
				"status": true,
				"action": "subscribe",
				"list": "12",
				"map": [{ "local": "email", "remote": "EMAIL" }]
			}
		]
	}
}

status and action are both declared in the schema. action may be subscribe, unsubscribe, sendEmail or trackEvent. Items carry additionalProperties: true, which is what lets Conditional Logic store its own condition object on an item with no schema entry of its own. Write status when you write this meta directly: Forms' shared filter reads $item['status'] with no fallback (Forms/core/base/Utils.php:58) and Conditional Logic's listener declares a non-nullable bool (ConditionalLogic/Forms/Addon.php:285-289).

One route backs the editor UI, requiring manage_options (Addon.php:272-285):

GET /wp-json/xpac/v1/form/brevo/lists

It answers a data array of { id, name, fields }, where fields is the same account-wide attribute list on every entry (:367-381), filtered to the normal and transactional categories with EMAIL prepended (:296-313, :338-357). A Brevo failure is not turned into an HTTP error: the route catches the exception and answers 200 with a message key instead (:322-328), and the editor renders that message where the dropdown would be (assets/packages/brevo/Components/FormSettings.js:22).

Brevo requests use a filterable ten-second timeout. subscribe() is registered through xpac_forms_submit_callbacks; Forms dispatches it only after the entry exists, and Deliveries\Runner executes it off the visitor request. Exceptions retain their HTTP status so the shared retry classifier can distinguish permanent remote refusals from transient transport, rate-limit and server failures. Accepted sub-actions call recordDelivered(), which is what preserves continuity across retries.

The package registers no blocks. It attaches to Forms hooks — xpac_forms_init to bootstrap (Addon.php:142, fired at Forms/Bootstrap.php:93), xpac_forms_post_meta_schema and xpac_forms_post_meta_default_values for the shape of its settings (fired at Forms/core/base/PostType.php:126 and :118), xpac_forms_post_script_dependencies to load its editor panel (PostType.php:329), and xpac_forms_submit_callbacks for the send — plus rest_api_init for its route and plugins_loaded/admin_notices in Bootstrap.php.

On the editor side the panel is contributed from JavaScript through the xpac-forms-form-panels filter (assets/packages/brevo/index.js:16, applied at assets/packages/forms/hooks/useFormPanels.js:62). It declares no priority, which is why it sorts below every core Forms panel and why its position relative to other add-ons' panels is not stable (useSortedAndFilteredPanels.js:18-21). Its action modal fires xpac-forms-after-settings-modal-content (assets/packages/brevo/Components/Modal.js:101), passing the action's current options with an onChange callback and the module id xpac-brevo; Conditional Logic is the only listener (assets/packages/conditional-logic/forms/admin.js:76).

The package's only licensing touchpoint is Updater::register() in Bootstrap.php:54. Nothing in Addon.php checks a licence.

On this page