Send Ghost newsletter emails with Brevo - a free alternative to the native Mailgun integration
How to build a simple relay app with Node.js that uses Ghost webhook technology (with Ghost Custom Integrations Tool) to send your Ghost newsletter with Brevo - a free alternative to the native Mailgun integration
Ghost is an awesome blog platform. However, if you chose to self-host your platform (instead of using Ghost Pro for 9$/months) Ghost has only one native integration for sending newsletter Email: Mailgun. While they used to offer a free tier with 600 emails/month they now offer only paid plans starting at 35$ 😭.
In this article I share how a build a simple relay app with Node.js that uses Ghost webhook technology (with Ghost Custom Integrations Tool) to send my newsletter with Brevo (formerly Sendinblue).
Setup your Brevo account and get your API key to create campaigns and send newsletter emails
Account configuration
After signing up to Brevo (using the free plan), first configure your domain and sender profile by completing the following steps:
- Verify your domain: https://help.brevo.com/hc/en-us/articles/115000185270-Verify-your-domain-to-approve-new-senders-automatically
- Authenticate your domain to improve the deliverability of your email (DKIM): https://help.brevo.com/hc/en-us/articles/208848209
- Add a new sender: https://help.brevo.com/hc/en-us/articles/208836149
Generate a dedicated API key
Once you've configured your account, generate your API Key to interact with Brevo programatically.
Create a simple Express server app with Node.js to handle webhooks from your Ghost blog
Initial setup
Create a new Node.js skeleton and install the packages that we will need for this small server.
mkdir ghost-relay
cd ghost-relay
npm init -y
npm install express nodemon dotenv ejs sib-api-v3-sdk @tryghost/content-api
Note: ejs is a templating engine that we will use to create the email templates, sib-api-v3-sdk is the client package to interact with the Brevo API and @tryghost/content-api is a client package to interact with your Ghost content API.
Define your api endpoint for the Ghost webhook and start your app server
Next we need to create an endpoint that will receive information from the webhook (that we will setup later):
After that we can spawn the app server in our index.js file:
And adjust our package.json file to be able to launch our app with nodemon:
Setup a Ghost client
When the hook will trigger the endpoint, it is supposed to provide almost all information we need about the published post. However, it is best practice not to rely on hook information and fetching the content of the post ourselves based on the postId that will be provided by the hook.
Create an HTML generator for our email
Next step is to implement an HTML rendering engine that will generate our newsletter email. As mentioned before we are relying on ejs to do the job here which will require two things:
- Creating the views (ejs files that will be used as template from the rendering)
- Creating the email generator function that will take the post data and invoke the rendering of the ejs file and get the stringified HTML
Comment: the CSS in header.ejs is used to transform the HTML from Ghost (with the casper theme) to an email friendly one. Feel free to make the design adjustment you like!
Send the new email campaign to Brevo
Finally we just have to implement the Brevo service that will create a our mail campaign with our HTML
const SibApiV3Sdk = require("sib-api-v3-sdk");
const defaultClient = SibApiV3Sdk.ApiClient.instance;
const apiKey = defaultClient.authentications["api-key"];
apiKey.apiKey = process.env.BREVO_API_KEY;
const { SENDER_EMAIL, SENDER_NAME, REPLY_TO, EMAIL_LIST_ID, CAMPAIGN_TAG } =
process.env;
const apiInstance = new SibApiV3Sdk.EmailCampaignsApi();
const createEmailCampaign = async ({ htmlContent, slug, title }) => {
try {
const response = await apiInstance.createEmailCampaign({
sender: { name: SENDER_NAME, email: SENDER_EMAIL },
tag: CAMPAIGN_TAG,
name: slug,
subject: title,
replyTo: REPLY_TO,
recipients: {
listIds: [parseInt(EMAIL_LIST_ID, 10)],
},
inlineImageActivation: false,
htmlContent,
});
return response;
} catch (error) {
console.log(error);
}
};
module.exports = { createEmailCampaign };
Set your environment variables and launch the app
Across the code, we rely on several environment variables that you will need to adjust with your own variables.
Then you can launch your server by running the ```npm run dev``` command.
Enable local tunnelling with ngrok
If you are sunning your app locally, you will need to expose your localhost to the internet. One simple way of doing it is to use a proxy service like ngrok which will create an endpoint available to the internet and securely tunnel incoming requests to your localhost where your app is running.
npm install -g ngrok
ngrok http 3000 // will redirect incoming requests to localhost:3000
Create a custom integration in Ghost
Once your app is running and your endpoint is exposed, you can now create a custom integration to your blog.
When creating your webhook, be sure that you are using the ngrok endpoint (and refresh it if needed as it has a limited "time to live").
How to easily test your setup
One simple way to test the complete flow is to create a "test webhook" that will be triggered every time you update a post. When it's done, just update one of your posts and see if a campaign is created with the correct data and layout. If yes, then delete the test hook and you are good to go!
Closing remark
In this tutorial, I showed how I created an alternative to the mailgun integration for your self hosted Ghost newsletter. The presented setup does not trigger the sending of the newsletter campaign in Brevo (in order to be able to check the results beforehand).
If you want me to create a follow-up article on how to host the ghost-relay app and to fully automate the process (or add other integration). Just let me know!
Link to the GitHub repository (feel free to fork it or suggest improvements):
That's it! Thanks for reading ✌️
Subscribe to The Bootstrapping Dad to receive my bi-weekly newsletter about technology, coding, and bootstrapping directly in your inbox 🥐☕️