Sync subscribers between Revue & Ghost using Apps Script
Run a 2-way automated sync between your Revue account and Ghost blog while creating a backup of all your subscribers/members in a Google Sheet.
Backstory
A few months ago I migrated my blog from being self-hosted to a paid Ghost (Pro) account and while I was at it, also found this neat little opportunity to setup Revue & connect it to my Twitter account β
I liked the idea because I could showcase my portfolio site (choraria.io) in the actual URL of my profile while not loosing the ability to showcase my work on this blog as well π
Problem statement
While the original/earlier versions of Ghost didn't have an option to manage memberships or send out newsletters, the latest versions do and must admit, they do a heck of an amazing job with it (no, I'm not getting paid to say that π) β
But, having a second source to capture new subscribers (i.e. Revue, via Twitter), meant that I'd had to come-up with a way to keep both of these sources in sync with each other β
I didn't just want to push subscribers from Revue to Ghost but also from Ghost to Revue so that I could to brag about my numbers without actually brining it up π and not have to do any of this manually.
I started with taking the easy route but soon ran into issues like these β
I bet there are ways to solve this or simply wait them out but the increased frequency of these errors just gave me more anxiety than I could handle π and so, figured I'd fallback on the ol' reliable Apps Script instead.
Architecture
I've broken down the project files into 3 sections β
- Config β you can store your API keys here, along with start/continue or schedule the entire workflow, as needed.
- Revue β connect with Revue's API, start a CSV export (as opposed to querying the API for a list of existing subscribers), import the data from the URL and add it to the spreadsheet. You'll also see a way to
syncWithGhost
here so you can add new members that have subscribed form Revue to your Ghost blog. - Ghost β connect with Ghost's API, import members from Ghost to the spreadsheet and also
syncWithRevue
This workflow uses a Google Sheet as the database to sync subscribers between your Revue and Ghost account and the workflow is triggered by first running the sequence with Revue via the importRevueList
function β it would require us to request a list export, followed by waiting for a minute or more (depending on the size of your list) and then fetching the subscribe_url
from that export.
It uses a nifty little piece of logic where a time-based trigger is automatically created if the subscribe_url
isn't readily available and then checks the API every minute to see if it is. Once it's able to grab the URL, it also deletes just this specific trigger too and moves on to the next operation.
Next comes the task of importing the data from the URL. The data comes in CSV format so the operation here is pretty straightforward too β
Notice that it uses the createTextFinder
function in order to avoid adding duplicates to the sheet and while there's more than one way to handle this, I've intentionally chosen this approach as opposed to β say β adding EVERYTHING again on the list and then removing just the duplicates. All of this is yet to be tested for scale β as in, I'm not sure how would either of these technique hold when there are a hundred-thousand+ subscribers in these accounts π¬ (stay tuned for that!).
Once we've imported subscribers from Revue, we can then move-on to importing members from Ghost and that uses the simple API-pagination approach (nothing fancy there) β
Once we have all the data from both platforms, we can then start the sync process. Here's an example for the syncWithRevue
function and there's a corresponding syncWithGhost
function that does pretty much the same task (but the other way around) β
Codebase
You can access the entire project code from my GitHub repository here.
Config.gs
There are a few mandatory parameters that you'll need to fill in order to start using the project β
You can start by running the startSync
function to test the workflow manually β
const startSync = () => importRevueList();
const continueSync = () => {
let jwt = importGhostMembers();
if (jwt) {
if (syncWithGhost(jwt)) {
if (syncWithRevue()) {
console.log("Sync was successful!");
} else {
console.log("syncWithRevue() falied at startSync().");
}
} else {
console.log("syncWithGhost(jwt) falied at startSync().");
}
} else {
console.log("importGhostMembers() falied at startSync().");
}
}
const scheduleSync = () => ScriptApp.newTrigger("importRevueList")
.timeBased()
.atHour(0)
.nearMinute(1)
.everyDays(1)
.inTimezone(Session.getScriptTimeZone())
.create();
Once your initial spreadsheet is set-up, you can then run the scheduleSync
function which will automatically run the workflow every night right after midnight (based on your timezone).
Gotchas
- While the
scheduleSync
function does what's needed β
const scheduleSync = () => ScriptApp.newTrigger("importRevueList")
.timeBased()
.atHour(0)
.nearMinute(1)
.everyDays(1)
.inTimezone(Session.getScriptTimeZone())
.create();
You may still want to go to the Triggers section of your project and edit this trigger's Failure notification settings from Notify me daily (which is what's set by default) to Notify me immediately (that's the one I prefer).
2. One of the odd downsides of requesting a list export from Revue is that you'll also get an email notification indicating that your export is ready to be downloaded and given the way this workflow has been setup, there's a good chance you'll get it twice β my apologies for it in advance ππ½
Credits
- This would've NOT been possible without the guide from Amit Agarwal (@labnol) on generating JWTs using Google Apps Script.
- The workflow also uses Kanshi TANAIKE's code snippet on converting hex 'secret' to byte array then base64Encode.
- Also thanks to RiΓ«l Notermans (@rieln) for helping me solve an issue with INVALID_JWT error in Google Apps Script while working with the Ghost API.
- Dozens of different Stack Overflow posts that helped me in working with 2d arrays, JSON data etc.