Brevo ExecModule
Overview
BrevoModule connects ValkyrAI workflows to Brevo API v3 through the native map I/O ExecModule ABI. It discovers account, contact, list, sender, and campaign state; performs bounded contact mutations; sends transactional messages; creates draft email campaigns; and guards customer-visible campaign delivery.
The connector follows the useful contact, email, and sender patterns in n8n's Brevo node while keeping credentials in IntegrationAccount, routing only to the fixed Brevo API origin, bounding inputs and responses, retrying only reads, and requiring explicit confirmation before contact deletion or campaign sending.
Usage
- Create a dedicated Brevo API key or private OAuth app with only the required contacts, campaigns, sender, and transactional-email permissions.
- Store the API key or current OAuth access token in the encrypted
IntegrationAccount.apiKeySecureField. - Set the IntegrationAccount status to
READYand bind it throughExecModuleConfig.authConfig.integrationAccount. - Choose
authMode: api_keyfor standard Brevo keys oroauth_bearerfor a current access token. - Use discovery operations to confirm visible lists, verified senders, contacts, and campaign IDs before mutations.
- Reconcile Brevo after any ambiguous write timeout; the module never automatically repeats mutations.
Workflow payloads cannot supply credentials, authorization headers, or an arbitrary provider URL.
Inputs
| Name | Type | Requirement | Default | Constraints |
|---|---|---|---|---|
operation | string | Required | None | One of the fourteen operations below. |
contactIdentifier | string | Contact get/update/delete | None | Email, numeric ID, or external identifier; at most 320 characters and no path delimiters. |
email | string | Create/upsert | None | Valid email, at most 320 characters. |
attributes | object or JSON | Optional contact mutation | None | At most 100 safe, non-credential-like keys. |
listIds | integer array or JSON | Optional create/upsert/update | None | 1–50 positive list IDs. |
unlinkListIds | integer array or JSON | Optional update | None | 1–50 positive list IDs. |
emailBlacklisted | boolean | Optional update | None | Updates marketing-email blacklist state. |
smsBlacklisted | boolean | Optional update | None | Updates SMS blacklist state. |
sender | object or JSON | Transactional email | None | Verified sender name and email. |
recipients | array or JSON | Transactional email | None | 1–99 objects containing email and optional name. |
subject | string | Inline transactional email | None | Required without templateId; at most 998 characters. |
textContent | string | Inline transactional email | None | At least one of text or HTML; at most 200,000 characters. |
htmlContent | string | Inline transactional email | None | At least one of HTML or text; at most 200,000 characters. |
templateId | integer | Template transactional email | None | Positive Brevo transactional-template ID. |
params | object or JSON | Optional template data | None | At most 100 safe keys. |
tags | string array or JSON | Optional transactional tags | None | 1–50 values, each at most 128 characters. |
campaignId | integer string | Campaign get/send | None | Positive integer. |
campaign | object or JSON | Create campaign | None | At most 50 allowlisted fields and 256 KiB. |
query | object or JSON | List operations | None | At most 12 operation-specific scalar filters. |
offset | integer | Optional lists | 0 | 0–1,000,000. |
limit | integer | Optional lists | 100 | 1–500 emitted items. |
confirmDelete | boolean | delete_contact | false | Must be exactly true. |
confirmSend | boolean | send_campaign | false | Must be exactly true. |
create_campaign accepts only Brevo email-campaign fields including name, subject, sender, type, recipients, one supported content source, scheduling, A/B-test, warmup, header/footer, reply, tag, and UTM fields. It requires name, sender, recipients, and one of htmlContent, htmlUrl, or templateId.
Outputs
| Name | Type | When present | Description |
|---|---|---|---|
status | string | Always | success or error. |
operation | string | Always | Normalized operation name. |
resource | string | After validation | Brevo resource family. |
attempts | integer | Always | Provider attempts consumed. |
httpStatus | integer | Provider responded | Last HTTP status. |
data | object | Single-resource or action success | Provider response normalized into the native output shape. |
items / count | array / integer | List success | Bounded resources and emitted count. |
hasMore | boolean | List success | Whether another bounded offset may exist. |
nextOffset | integer | Another page may exist | Next provider offset. |
requestId | string | Provider supplies it | Trace reference for Brevo support. |
error | object | Failure | Safe code, message, and retryable flag. |
API keys, OAuth bearer values, and credential-like payload fields are rejected or redacted from failures, outputs, logs, and workflow events.
IntegrationAccount Requirements
| Setting | Requirement |
|---|---|
| Provider | Brevo API v3 |
accountName | Human-readable automation identity |
apiKey | Encrypted SecureField containing an API key or current OAuth access token |
status | Exactly READY |
| API-key permissions | Contact/list reads and required contact writes; sender reads; campaign reads/writes; transactional email send |
| OAuth scopes | Minimum explicit contacts:read, contacts:write, campaign read/write, and transactional.email:write equivalents needed by selected operations |
Brevo API keys use the api-key header. OAuth mode sends Authorization: Bearer. OAuth token refresh remains an IntegrationAccount lifecycle responsibility; the module never accepts refresh tokens in workflow payloads.
Configuration
{
"version": "1.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:brevo-customer-engagement"
},
"retryPolicy": {
"maxAttempts": 3,
"backoffStrategy": "EXPONENTIAL",
"initialDelayMs": 250,
"maxDelayMs": 5000
},
"executionConfig": {"timeoutMs": 30000},
"payloadConfig": {
"parameters": "{\"authMode\":\"api_key\",\"operation\":\"list_contacts\",\"limit\":100}"
}
}
The account reference is symbolic. Persisted workflows bind the generated relationship, never a plaintext credential.
Operations
| Operation | Brevo behavior | Side effect |
|---|---|---|
get_account | Reads the authenticated Brevo account and plan state. | Read-only; safe retries. |
list_contacts | Lists a bounded contact page with allowlisted sorting/date filters. | Read-only; safe retries. |
get_contact | Reads one contact by encoded identifier. | Read-only; safe retries. |
create_contact | Creates one contact with typed email, attributes, and list IDs. | Contact mutation; one attempt. |
upsert_contact | Uses Brevo's updateEnabled contact contract. | Contact mutation; one attempt. |
update_contact | Updates supplied contact attributes, list membership, or blacklist state. | Contact mutation; one attempt. |
delete_contact | Deletes one contact after explicit confirmation. | Destructive; one attempt. |
list_contact_lists | Lists visible Brevo contact lists. | Read-only; safe retries. |
list_senders | Lists configured and verified senders, locally capped by limit. | Read-only; safe retries. |
send_transactional_email | Sends one bounded inline or template-based transactional message. | Customer-visible delivery; one attempt. |
list_campaigns | Lists a bounded email-campaign page with allowlisted filters. | Read-only; safe retries. |
get_campaign | Reads one email campaign and verifies returned identity when supplied. | Read-only; safe retries. |
create_campaign | Creates one bounded email campaign, normally in draft state. | Campaign mutation; one attempt. |
send_campaign | Sends an existing campaign immediately after explicit confirmation. | Broad customer-visible delivery; one attempt. |
Errors
| Code | Typical cause | Retryable | Resolution |
|---|---|---|---|
VALIDATION_ERROR | Missing or malformed email/ID, unsupported field/filter, oversized payload, empty update, or missing confirmation. | No | Correct the named input; no unsafe request was sent. |
UNSUPPORTED_OPERATION | Unknown operation. | No | Select a documented operation. |
INTEGRATION_ACCOUNT_REQUIRED | No bound account. | No | Bind a Brevo IntegrationAccount. |
INTEGRATION_ACCOUNT_NOT_READY | Account status is not READY. | No | Repair or reconnect the account. |
BREVO_HTTP_400 | Invalid provider field, value, recipient, content, or campaign state. | No | Correct the bounded provider error. |
BREVO_HTTP_401 | Credential is missing, revoked, expired, or invalid. | No | Rotate the key or refresh/reconnect OAuth. |
BREVO_HTTP_403 | Account, IP allowlist, plan, or scope denies the action. | No | Grant the minimum permission or authorize the runtime IP. |
BREVO_HTTP_404 | Identifier is wrong, deleted, or invisible. | No | Re-run discovery under the same account. |
BREVO_HTTP_429 | Endpoint or account rate limit is exhausted. | Yes for reads | Honor Retry-After and reduce concurrency. |
BREVO_HTTP_5xx | Transient provider failure. | Yes for reads | Retry reads; reconcile writes before manual replay. |
NETWORK_ERROR | Timeout, DNS, TLS, or connectivity failure. | Yes for reads | Verify connectivity; inspect Brevo before repeating writes. |
RESPONSE_TOO_LARGE | Response exceeded 5 MiB. | No | Lower limit or narrow filters. |
Example
Send an onboarding email after an approved workflow confirms consent and sender identity:
{
"operation": "send_transactional_email",
"sender": {"name": "Valkyr Labs", "email": "ops@example.com"},
"recipients": [{"name": "Ada", "email": "ada@example.com"}],
"subject": "Your governed workspace is ready",
"textContent": "Your ValkyrAI workspace is ready for the approved onboarding workflow.",
"params": {"WORKSPACE": "Ada"},
"tags": ["onboarding", "workspace-ready"]
}
Expected result:
{
"status": "success",
"operation": "send_transactional_email",
"resource": "transactional-email",
"attempts": 1,
"httpStatus": 201,
"data": {"messageId": "<provider-message-id>"}
}
Notes
- Pagination: contacts, contact lists, and campaigns use Brevo
limit/offsetpagination. The module emits at most 500 items and returnsnextOffsetonly when provider evidence indicates another page. Sender results are locally capped because the sender endpoint is not treated as an offset collection. - Rate limits: read operations may retry HTTP 408, 429, 500, 502, 503, and 504 and network failures, honoring numeric
Retry-After. Mutations never retry automatically. - API limits: request bodies are capped at 256 KiB, provider responses at 5 MiB, recipients at 99, contact-list IDs at 50, and transaction tags at 50. Provider plan and daily-send quotas still apply.
- Idempotency:
upsert_contactuses Brevo'supdateEnabledbehavior, but every write is sent once because a timeout can follow a committed mutation. Reconcile contacts, campaign state, or message events before replaying. - Destructive behavior:
delete_contactrequiresconfirmDelete=true.send_campaignrequiresconfirmSend=truebecause it can notify an entire audience. Bulk contact deletion, campaign deletion, SMS/WhatsApp sending, and webhook deletion are not exposed. - External effects: contact changes can enter Brevo automations; transactional messages and campaigns can produce delivery, open, click, bounce, unsubscribe, complaint, and webhook events after the request returns.
- External verification: deterministic local tests cover both auth modes, fixed routing, encoded contact IDs, request bodies, pagination, retries, mutation non-retry, confirmation guards, redaction, validation, and metadata discovery. Live plan limits, IP allowlists, sender verification, deliverability, OAuth refresh, automations, and tenant-specific contact attributes require separately authorized Brevo credentials and are not exercised in repository tests.
- Deferred operations: contact imports, attributes, folders, list membership bulk jobs, transactional templates, scheduled campaign updates, webhook management, SMTP statistics, SMS, WhatsApp, conversations, eCommerce, custom objects, and automation triggers are deferred.
- Functional references: n8n Brevo source, Brevo API overview, Brevo contact API, and Brevo transactional email API.