How to Sync Your Sanity CMS Newsletter with Brevo Email Marketing
Extend your existing Sanity newsletter to automatically sync contacts to Brevo without breaking UX

📋 Complete Sanity Development Guides
Get practical Sanity guides with working examples, schema templates, and time-saving prompts. Everything you need to build faster with Sanity CMS.
Related Posts:
I recently built a newsletter subscription system using Sanity CMS that was working perfectly for collecting subscriber data. But I realized I was missing a crucial piece - actually sending emails to those subscribers. While Sanity is excellent for content management, it's not an email marketing platform.
After evaluating several options, I chose Brevo (formerly Sendinblue) for its generous free tier and robust API. Rather than rebuilding my entire newsletter system, I wanted to extend it to sync subscribers to both platforms automatically. This guide shows you exactly how to integrate Brevo with your existing Sanity newsletter system, ensuring every signup gets saved to both your CMS and email marketing platform simultaneously.
If you haven't built the initial newsletter form yet, check out my previous guide on building a newsletter form in Next.js 15 with Sanity CMS first, then return here to add the Brevo integration.
The Integration Strategy
The key insight here is maintaining the existing user experience while adding Brevo sync as an enhancement. When someone subscribes through your newsletter form, the flow becomes:
- Validate and process the subscription (existing Sanity flow)
- If Sanity creation succeeds, attempt to sync the contact to Brevo
- If Brevo sync fails, log the error but still show success to the user
This approach ensures your newsletter continues working even if Brevo experiences issues, while providing the email marketing capabilities you need when everything is running smoothly.
Installing the Brevo SDK
Start by adding the official Brevo SDK to your project. This package provides TypeScript support and handles all the API communication details.
npm install @getbrevo/brevo --save
The SDK includes everything needed to manage contacts, lists, and email campaigns through Brevo's REST API. We'll focus specifically on the contacts functionality for this integration.
Setting Up Your Environment
You'll need a Brevo API key with contacts management permissions. In your Brevo dashboard, navigate to Settings → API Keys and generate a new key. Add it to your environment file:
# File: .env
BREVO_API_KEY=xkeysib-your-actual-api-key-here
Brevo API keys always start with xkeysib-
followed by your unique key. Keep this secure and never commit it to your repository. If you're deploying to Vercel or similar platforms, add this environment variable in your deployment settings.
Creating the Brevo Service Module
Rather than placing Brevo logic directly in your server action, create a dedicated service module. This separation makes the code more maintainable and allows you to reuse the Brevo functionality elsewhere in your application.
// File: src/lib/brevo.ts
import { ContactsApi, ContactsApiApiKeys } from '@getbrevo/brevo'
if (!process.env.BREVO_API_KEY) {
throw new Error('BREVO_API_KEY environment variable is required')
}
const contactsApi = new ContactsApi()
contactsApi.setApiKey(ContactsApiApiKeys.apiKey, process.env.BREVO_API_KEY)
interface BrevoContact {
email: string
name?: string
source?: string
}
export async function addContactToBrevoList(contact: BrevoContact, listId: number = 7) {
try {
// Split name into first and last name for Brevo attributes
let firstName = ''
let lastName = ''
if (contact.name) {
const nameParts = contact.name.trim().split(' ')
firstName = nameParts[0] || ''
lastName = nameParts.slice(1).join(' ') || ''
}
// Prepare contact data with Brevo's expected format
const brevoContact = {
email: contact.email,
attributes: {
FIRSTNAME: firstName,
LASTNAME: lastName,
...(contact.source && { SOURCE: contact.source })
},
listIds: [listId],
updateEnabled: true // Handle existing contacts gracefully
}
console.log('Adding contact to Brevo:', { email: contact.email, listId })
const result = await contactsApi.createContact(brevoContact)
console.log('Successfully added to Brevo:', result.body)
return { success: true, data: result.body }
} catch (error: any) {
console.error('Failed to add contact to Brevo:', {
email: contact.email,
error: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
response: error.response?.body || error.response?.data
})
// Check if error is because contact already exists
if (error.response?.body?.code === 'duplicate_parameter') {
console.log('Contact already exists in Brevo, this is okay')
return { success: true, data: { message: 'Contact already exists' } }
}
// Check for API key issues
if (error.response?.status === 401) {
console.error('Brevo API authentication failed - check API key validity')
}
return {
success: false,
error: error.message || 'Failed to add contact to Brevo'
}
}
}
This service module handles several important aspects of the Brevo integration. The environment variable check ensures the API key is present at startup rather than failing silently later. The name splitting logic accommodates Brevo's separate FIRSTNAME and LASTNAME attributes, which work better for email personalization than a single name field.
The error handling is particularly important here. Brevo returns specific error codes for different scenarios, like when a contact already exists. We treat duplicate contacts as a success since the end result (contact in Brevo) is what we want. The comprehensive logging helps debug issues during development and monitoring in production.
Updating Your Newsletter Server Action
Now modify your existing server action to include the Brevo sync. The key principle is to maintain the existing Sanity workflow while adding Brevo as an enhancement.
// File: src/actions/subscribeToNewsletter.ts
'use server'
import { createEmailSubscription } from '@/lib/sanity-server'
import { addContactToBrevoList } from '@/lib/brevo'
interface NewsletterState {
error?: string
success?: boolean
}
async function subscribeToNewsletter(
previousState: NewsletterState | null,
formData: FormData
): Promise<NewsletterState> {
try {
const email = formData.get('email')
const name = formData.get('name')
const source = formData.get('source')
if (!email) {
return { error: 'Email is required' }
}
if (typeof email !== 'string') {
return { error: 'Invalid input format' }
}
const nameStr = typeof name === 'string' ? name : undefined
const sourceStr = source?.toString() || 'homepage'
console.log('Attempting to create subscriber:', { email, name: nameStr, source: sourceStr })
// Step 1: Create new subscriber in Sanity
const sanityResult = await createEmailSubscription({
email,
name: nameStr,
source: sourceStr,
})
if (!sanityResult.success) {
return { error: sanityResult.error || 'Failed to subscribe to newsletter' }
}
console.log('Successfully created subscriber in Sanity:', sanityResult.data)
// Step 2: Add contact to Brevo list
const brevoResult = await addContactToBrevoList({
email,
name: nameStr,
source: sourceStr
}, 7) // List ID 7
if (!brevoResult.success) {
// Log error but don't fail the entire process
console.error('Brevo sync failed, but Sanity creation succeeded:', brevoResult.error)
// Could optionally store this failure in Sanity for retry later
} else {
console.log('Successfully synced to Brevo:', brevoResult.data)
}
return { success: true }
} catch (error: any) {
console.error('Failed to subscribe to newsletter:', {
name: error.name,
message: error.message,
})
return { error: 'Failed to subscribe to newsletter. Please try again later.' }
}
}
export default subscribeToNewsletter
The updated server action follows a clear two-step process. First, it attempts to create the subscription in Sanity using your existing logic. Only if that succeeds does it attempt the Brevo sync. This ensures that if Brevo is experiencing issues, your primary newsletter collection continues working.
The error handling strategy is crucial here. If Sanity creation fails, we return an error to the user since that's the primary system. If Sanity succeeds but Brevo fails, we log the error for debugging but still return success to the user. This maintains a smooth user experience while ensuring you can investigate and resolve sync issues later.
Handling Brevo Authentication Issues
During development, you might encounter a 401 authentication error even with a valid API key. This typically indicates IP restrictions on your Brevo account. The error message will specify the IP address that needs to be whitelisted.
To resolve this, visit your Brevo security settings at https://app.brevo.com/security/authorised_ips
and add your server's IP address to the whitelist. For development environments, you may need to add multiple IPs or temporarily disable IP restrictions.
If you're using a hosting service like Vercel, the IP addresses can change, so you might need to disable IP restrictions entirely or use a more flexible approach for production deployments.
Testing the Integration
With the code in place, test your newsletter signup form. You should see console logs showing the progression through both Sanity and Brevo creation. A successful integration will show:
- Subscriber creation attempt logged
- Successful Sanity creation with document details
- Brevo sync initiation
- Successful Brevo contact creation
Check both your Sanity studio and Brevo contacts list to confirm the subscriber appears in both systems with the correct attributes populated.
Production Considerations
This integration is designed with production reliability in mind. The graceful error handling ensures your newsletter continues working even during Brevo API outages. However, consider a few additional aspects for production deployment.
Monitor your logs for Brevo sync failures to identify patterns or persistent issues. You might want to implement a retry mechanism or queue system for failed syncs, though for most applications, the simple logging approach works well.
Ensure your Brevo list ID (7 in the example) matches your actual list structure. You can find list IDs in your Brevo dashboard under Contacts → Lists. Different lists might be appropriate for different subscription sources or user segments.
Expanding the Integration
This foundation opens up numerous possibilities for enhancing your newsletter system. You could extend the Brevo service module to handle unsubscriptions, update subscriber preferences, or trigger automated email sequences based on subscription source.
The separation between Sanity and Brevo also allows you to maintain your content workflow in Sanity while leveraging Brevo's email marketing features like segmentation, A/B testing, and detailed analytics.
You now have a robust dual-platform newsletter system that maintains all your subscriber data in Sanity while automatically syncing to Brevo for email marketing. Every new subscriber gets the best of both worlds - your content management capabilities and professional email marketing features.
Let me know in the comments if you have questions, and subscribe for more practical development guides.
Thanks, Matija