12/2023 Aman Azad
Shufl is a small tool I built that solves a very specific problem I ran into while attempting to drum up marketing for ArabicBridge.
For ArabicBridge, I had tens of thousands of text content in the form of quotes from the Quran, and Islamic Hadith (sayings of the Prophet Muhammad ﷺ).
10,000+ Quranic Verses and Hadith
Following some lessons learned from Legaci, I felt it best I spend some cycles trying to build a marketing engine.
I had seen some apps and Instagram accounts have a lot of success posting static image content usually containing some background image and a quote. I figured there had to be a way to leverage the corpus of text content I had setup in my database in a way to programmatically generate similar pieces of content.
Many an Instagram account dedicated to this content
Having some skills with writing software I felt I could easily automate a lot of the menial work of creating these images.
Before diving into the details, feel free to check out the project and the code. You'll find a more condensed version of this post in the README.
Not all marketing efforts need to be high-effort TikTok reels, or YouTube videos. Consumers still show they love, love, love meaningful content whatever medium it takes. We can see this in the number of apps serving meaningful quotes by way of daily affirmation apps, vocabulary apps, or religious apps.
And not just in terms of engagement are consumers showing they value this form of content, we see these apps have lots of success by way of revenue as well. For example the Motivation App by Monkey Taps has an incredible 1.3million followers, ~300k monthly downloads, and ~$400k MRR.
Motivation App IG Page
Motivation App Estimated Revenue
These static quote images are a significant marketing effort for Monkey Taps. Generating these images at scale is tedious, and very time consuming to generate without automated tooling.
The core problems with existing offerings:
We have a large database of engaging content, but it’s very time consuming to pull in an image, grab some content from the database, and generate our final marketing material that’ll go on IG, Pinterest, Facebook, etc.
It’d be a huge time saver to auto generate this content from an API endpoint. Existing offerings like Canva do support bulk creation, but in a limited way given their UI.
Shufl lets you upload your own images, set a basic font styling, and either use a pre-existing API configuration to generate content at scale, or allows you to enter a custom API endpoint.
For example, let's generate a static image content strategy for ArabicBridge.
ArabicBridge is a very standard NextJS app. We have the core NextJS app hosting the API and some static pages I set up for an SEO experiment.
I updated the ArabicBridge webapp to add the endpoint.
/api/getIslamicQuote
This outputs the following:
{
"title": "He is the Omnipotent over His bondmen; and He is the Wise, the Aware.",
"subtitle": "Al-An'aam:18"
}
This is a randomly selected Islamic quote that we'll use to build our static images.
I use midjourney to generate a handful of topically relevant images to serve as background images as well:
ArabicBridge Midjourney Images
Some of the prompts I used to generate the above images are as follows:
"Close up 3/4 three quarters shot of multiple muslim mosque minaret photorealistic brightly lit traditional royal"
"Silhouette of muslim woman standing making dua in a large room, darkly lit in space, shot from far behind"
"Moon reflecting on a calm ocean coast at night"
"Close up 3/4 three quarters shot of moon nostalgic photo film 35mm"
Inputting the images and API endpoint into SHUFL, and generating the images gives us this output:
Shufl Generated Images
Done! Each of these images can be downloaded and ready for upload into Pinterest, Instagram, etc.
All of these images are generated and downloadable client side, so no fear of any rate limits beyond what API your using.
Overall, this project is your typical NextJS app using their new app router.
The crux of the functionality can be find in the CustomImagePreview.tsx file. The two main aspects things to call out are how I use html2canvas to generate the images, and the toDataUrl() function to download the generated images.
Starting first with our html2canvas call, essentially we pass in a React ref to a styled div component that contains the randomly selected image, and quote content. Here's the relevant block of code that takes care of that:
<div
ref={ref}
id={randomId}
className={cn(
"w-full flex flex-col justify-center items-start relative overflow-hidden bg-black",
"max-w-[280px] lg:max-w-[400px]",
aspectRatio === "socialStory"
? "aspect-socialStory"
: "aspect-socialPost",
)}
>
<img
alt=""
src={finalImage}
className={cn("h-full w-auto z-10", "relative max-w-none")}
style={{
transform: "translate(-50%, -50%)",
top: "50%",
left: "50%",
}}
/>
<div
className={cn(
"absolute top-0 left-0 h-full w-full",
"w-full rounded-md flex flex-col justify-center items-center z-20",
"text-2xl text-white font-bold text-center",
"drop-shadow-lg p-4",
fontChoice === "inter" && "font-inter",
fontChoice === "crimson" && "font-crimson",
fontChoice === "caveat" && "font-caveat",
aspectRatio === "socialStory"
? "aspect-socialStory"
: "aspect-socialPost",
)}
style={{
WebkitTextStroke: "0.5px black",
}}
>
<div>{title}</div>
<div
className="text-lg text-white pt-4 drop-shadow-xl"
style={{
WebkitTextStroke: "0.5px black",
}}
>
{subtitle}
</div>
</div>
</div>
Generally, what this is doing is setting up the elements, and using some Tailwind classes to style the elements based on the user's desired aspect ratio. An important call out is any images used have to be explicit image components. html2canvas has a hard time rendering image backgrounds, hence why it was necessary to absolutely position the image versus using CSS background styles.
The "randomId" used here allows the DOM to differentiate between the multiple different images generated, and to specify with our refs which image the user wants to download.
html2canvas returns an HTML canvas element, which exposes a "toDataUrl()" function. This allows us to give the user the ability to download a PNG of the image.
const onDownload = () => {
if (ref?.current) {
html2canvas(document.querySelector(`#${randomId}`) ?? ref.current, {
useCORS: true,
allowTaint: false,
}).then((canvas) => {
const imageUrl = canvas.toDataURL(); // Convert canvas to data URL
// Create a download link
const link = document.createElement("a");
link.href = imageUrl;
link.download = `${randomId}.png`;
link.click();
});
}
};
This tool is live, and free to host so I'll keep it up as long as I feel it's useful to someone!
For ArabicBridge's marketing efforts, I ended up writing a script that leverages FFMPEG to auto-generate TikTok videos from the same dataset following a similar content format to Shufl. That post is coming soon, the intention is to create a CLI tool that may fit folks who fall into a similar category of developers turned marketer.