Quick Take: Discourse can send reliable transactional email through Brevo using Brevo’s SMTP relay, but entering the SMTP credentials is only half the job. For consistent inbox placement, you also need a properly authenticated sending domain, a correct SPF policy, DKIM signing, DMARC alignment, valid reverse DNS on Brevo’s side, and a bounce-handling process.
The practical setup is:
- Brevo SMTP relay:
smtp-relay.brevo.com- Port:
587- Encryption: STARTTLS
- Username: your Brevo SMTP login
- Password: your Brevo SMTP key
- From address: an authenticated address on your own domain
- DNS: SPF, DKIM, and DMARC configured before production sending
Discourse is more than a traditional forum. It can operate as a mailing list, community platform, long-form chat room, notification engine, and private support portal. Every one of those use cases depends on email.
If Discourse cannot reliably deliver account confirmations, password resets, new-topic notifications, digest emails, and moderation alerts, users experience the problem as a broken application—even when the Discourse container itself is perfectly healthy.
Brevo, formerly known as Sendinblue, provides an SMTP relay for transactional email. This guide explains how to configure it inside the Discourse Docker container using app.yml, authenticate your sending domain, test the connection, and design a sensible bounce-handling workflow.
Benchmark Telemetry & Empirical Metrics
What You Need Before Starting
Prepare the following before editing Discourse:
- A running Discourse installation with SSH access to the server
- Permission to edit
/var/discourse/containers/app.yml - A Brevo account with transactional email enabled
- A domain that you control, such as
example.com - Access to your DNS provider—Cloudflare, Route 53, GoDaddy, Namecheap, or your registrar
- A sender address such as
[email protected] - A Brevo SMTP key, not merely a Brevo API key
The most common operational mistake is using a personal Gmail, Outlook, or Yahoo address as the sender. That may work temporarily, but it creates alignment, reputation, and policy problems. Use a sender on your own authenticated domain.
Brevo Transactional Email
SMTP relay, domain authentication, templates, logs, and bounce management for Discourse and other applications.
Step 1: Authenticate Your Domain in Brevo
Log in to Brevo and open the sender/domain authentication area. The exact menu label can change as Brevo updates its dashboard, but it is generally available under:
Settings → Senders & IP → Domains
Add your sending domain, for example:
example.com
Do not add only community.example.com unless you specifically intend to authenticate that subdomain separately. For most Discourse installations, authenticating the parent domain is simpler.
Brevo will display DNS records that you must publish. These commonly include:
- A Brevo verification record
- A DKIM record, usually provided as a TXT or CNAME record
- An optional or recommended DMARC record
- An SPF instruction or
includevalue
Brevo may present DKIM as a CNAME rather than a raw public-key TXT record. Copy the record exactly as shown in the current Brevo dashboard. Do not manually retype a long DKIM value.
Example DNS Records
The exact hostnames and values vary by account, region, and Brevo configuration. A typical structure may look like this:
| Record | Host/Name | Value |
|---|---|---|
| TXT | @ |
Brevo verification value |
| CNAME/TXT | Brevo-provided selector | Brevo DKIM target or public key |
| TXT | _dmarc |
v=DMARC1; p=none; rua=mailto:[email protected] |
| TXT | @ |
SPF policy including Brevo |
For Cloudflare, DKIM and domain-verification CNAME records should normally be set to DNS only, not proxied. Email-related DNS records cannot be reverse-proxied through Cloudflare.
Do not create multiple SPF records
v=spf1 records causes SPF to return PermError, which defeats the purpose of the configuration.SPF Configuration
If Brevo instructs you to add an SPF include, merge it with your current policy. For a domain sending only through Brevo, the policy may look conceptually like:
v=spf1 include:spf.brevo.com ~all
However, always use the exact SPF include recommended by Brevo in your account. Brevo has changed infrastructure and guidance over time, so copying an old blog post’s value is not a good production practice.
If Google Workspace also sends mail from the same domain, the record might conceptually become:
v=spf1 include:_spf.google.com include:spf.brevo.com ~all
That is an example, not a universal final record. Count SPF DNS lookups as well. SPF has a limit of 10 DNS-mechanism lookups, and large collections of services can exceed it.
Step 2: Configure DKIM and DMARC Correctly
DKIM
DKIM adds a cryptographic signature to outgoing messages. The receiving mail provider retrieves the public key from DNS and verifies that the message was authorized by the domain.
A valid DKIM result typically appears as:
dkim=pass header.d=example.com
The important part is header.d=example.com, or an aligned subdomain of it. A DKIM signature from a completely unrelated domain may technically pass but still fail DMARC alignment.
After publishing the DKIM record, return to Brevo and click the verification or authenticate button. DNS changes may appear within minutes, but allow up to 24–48 hours in stubborn cases.
DMARC
DMARC tells receiving providers what to do when SPF and DKIM fail alignment. Begin with monitoring mode:
v=DMARC1; p=none; rua=mailto:[email protected]; adkim=s; aspf=s
For a smaller deployment, a less strict policy is also reasonable:
v=DMARC1; p=none; rua=mailto:[email protected]
Once you have reviewed reports and confirmed that all legitimate senders pass, move gradually to:
v=DMARC1; p=quarantine; rua=mailto:[email protected]
Eventually, you may use:
v=DMARC1; p=reject; rua=mailto:[email protected]
Do not jump directly to p=reject if your domain is also used by WordPress, Google Workspace, Microsoft 365, contact forms, accounting tools, or marketing platforms. You can accidentally reject legitimate business email.
Step 3: Generate the Brevo SMTP Credentials
In Brevo, navigate to the SMTP or transactional settings area and locate your SMTP credentials.
You need:
- SMTP server:
smtp-relay.brevo.com - SMTP port:
587 - SMTP login: the username shown by Brevo
- SMTP key/password: a Brevo-generated SMTP key
The SMTP key is not necessarily the same as your Brevo account password. It is also not automatically the same as a Brevo API key.
Treat this key like a production secret:
- Do not paste it into a public Git repository
- Do not include it in screenshots
- Do not send it through a chat channel
- Rotate it if you suspect exposure
- Keep a record of which applications use each key
Step 4: Edit Discourse app.yml
SSH into the Discourse host:
ssh root@your-discourse-server
Create a backup before editing:
cp /var/discourse/containers/app.yml \
/var/discourse/containers/app.yml.backup-$(date +%F)
Open the configuration:
nano /var/discourse/containers/app.yml
Inside the env: section, configure the SMTP parameters. A typical configuration is:
env:
DISCOURSE_SMTP_ADDRESS: smtp-relay.brevo.com
DISCOURSE_SMTP_PORT: 587
DISCOURSE_SMTP_USER_NAME: your-brevo-smtp-login
DISCOURSE_SMTP_PASSWORD: your-brevo-smtp-key
DISCOURSE_SMTP_ENABLE_START_TLS: true
DISCOURSE_SMTP_AUTHENTICATION: login
DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: peer
DISCOURSE_SMTP_DOMAIN: example.com
DISCOURSE_NOTIFICATION_EMAIL: [email protected]
The surrounding app.yml contains many other settings. Do not replace the entire file with this snippet. Add or update only the relevant variables under the existing env: block.
The DISCOURSE_SMTP_DOMAIN value should normally match your authenticated sending domain. The notification email should use the same domain, or a domain that is properly configured for sending.
Why Port 587?
Port 587 is the standard message-submission port and is intended for authenticated client-to-server email delivery. Discourse connects to Brevo and upgrades the connection using STARTTLS.
This is different from implicit TLS on port 465:
- 587: connect, authenticate, then negotiate STARTTLS
- 465: TLS encryption begins immediately
For this Discourse/Brevo configuration, use port 587 with STARTTLS unless Brevo’s current documentation explicitly instructs you otherwise.
Keep SMTP credentials out of WordPress
Step 5: Rebuild the Discourse Container
Save app.yml, then rebuild the application:
cd /var/discourse
./launcher rebuild app
The rebuild can take several minutes. Discourse will recreate the container using the updated environment variables.
If the rebuild fails, inspect the output carefully. YAML indentation is a frequent cause. This is valid:
env:
DISCOURSE_SMTP_ADDRESS: smtp-relay.brevo.com
This is not valid if it is accidentally placed outside the env: hierarchy:
DISCOURSE_SMTP_ADDRESS: smtp-relay.brevo.com
After the container returns, open the Discourse administration panel and use the email test function. Depending on your Discourse version, this is available under the email settings or admin diagnostics area.
You can also inspect the container logs:
cd /var/discourse
./launcher logs app
Search for SMTP, timeout, authentication, or delivery errors:
./launcher logs app | grep -iE 'smtp|mail|delivery|authentication|timeout'
Step 6: Verify Delivery, Headers, and Authentication
Do not stop after seeing “email sent” in Discourse. Test with at least:
- Gmail
- Outlook or Microsoft 365
- A mailbox hosted on your own domain
- A deliverability testing service
Inspect the full message headers. You want to see results similar to:
spf=pass
dkim=pass
dmarc=pass
Also check:
- The visible
From:address - The
Return-Path - The DKIM signing domain
- Whether the message landed in Inbox, Promotions, Spam, or Junk
- Whether links and images are being rewritten by Brevo
- Whether the Message-ID and timestamps look normal
A successful SMTP handoff only proves that Brevo accepted the message. It does not guarantee inbox placement.
If Discourse says sent but users receive nothing
Bounce Handling and Suppression
Brevo tracks hard bounces, soft bounces, blocks, spam complaints, and unsubscribes. These events affect future delivery.
A practical bounce workflow is:
- Configure a Brevo webhook for transactional events.
- Subscribe to hard bounce, soft bounce, blocked, complaint, and unsubscribe events.
- Send those events to your own HTTPS endpoint or integration layer.
- Log the recipient, event type, message identifier, and timestamp.
- Suppress permanently invalid addresses.
- Investigate repeated soft bounces rather than deleting users immediately.
- Keep Discourse’s user email status consistent with your suppression policy.
Brevo’s webhook payload and event names can change, so use the current event documentation when implementing the receiver.
Important Discourse Limitation
A Brevo webhook does not automatically become a native Discourse bounce processor. Discourse is not a generic webhook ingestion endpoint. If you point Brevo at an arbitrary Discourse URL, you may simply create failed requests or expose an endpoint that was never designed to process those events.
Use a small authenticated middleware service, serverless function, or application integration to receive Brevo events. That service can then:
- Store delivery events
- Alert administrators
- Mark an address as undeliverable through an approved Discourse integration
- Prevent repeated sends from another system
For basic community deployments, Brevo’s own suppression and blocklists may be sufficient. For a large forum, connect the events to your monitoring or CRM workflow.
Deliverability Checklist
Before declaring the setup production-ready, verify:
- Brevo sender domain is authenticated
- SPF is merged into one valid record
- DKIM returns
pass - DMARC exists and is initially monitored
- Discourse uses port 587
- STARTTLS is enabled
- SMTP key is correct and active
-
DISCOURSE_NOTIFICATION_EMAILuses an authenticated domain - The container was rebuilt after editing
app.yml - Gmail and Outlook tests were completed
- Brevo transactional logs show accepted and delivered messages
- Bounce and complaint monitoring is configured
- SPF lookup count is below the protocol limit
- No public DNS record contains a private SMTP credential
Common Errors
535 Authentication failed
Usually one of these:
- Wrong SMTP key
- Brevo SMTP service is not activated
- API key used instead of SMTP key
- Extra whitespace copied into the password
- Wrong SMTP username
Generate a new SMTP key and update app.yml if necessary.
TLS or certificate errors
Confirm:
- Hostname is exactly
smtp-relay.brevo.com - Port is
587 - STARTTLS is enabled
- The server clock is accurate
- The host is not blocking outbound TCP 587
Messages land in spam
Check authentication first, then reputation. A technically valid SPF/DKIM setup cannot compensate for:
- Purchased email lists
- High complaint rates
- Repeated sends to invalid addresses
- Misleading subjects
- Sudden volume spikes
- A sender domain with no history
Start with normal Discourse activity and increase volume gradually. Do not send a backlog of thousands of digests immediately after connecting a new domain.
DNS verification does not complete
Use:
dig TXT example.com
dig TXT _dmarc.example.com
dig CNAME selector.example.com
Check the record from an external DNS resolver, not only your local network. Remove quotation-mark mistakes, duplicate records, and accidental Cloudflare proxying.
Final Recommendation
Brevo is a sensible SMTP relay for Discourse when you want a managed transactional layer without operating Postfix, handling IP reputation manually, or debugging recipient-provider throttling yourself.
The robust configuration is not just:
smtp-relay.brevo.com:587
It is the complete chain:
Authenticated domain
→ SPF and DKIM
→ DMARC alignment
→ STARTTLS SMTP submission
→ Discourse container rebuild
→ Header verification
→ Bounce and complaint suppression
Build that chain once, monitor it monthly, and email reliability becomes a predictable infrastructure concern rather than a recurring support emergency.
[ash_booking_card type="discourse-
