You may have noticed I’m running a series of giveaways on this blog. Literally every developer I’ve reached out to has been willing to put up 1-10 licenses or 1-year subscriptions for their app, and I’m extremely pleased with the list I’ve put together. In an effort to keep track of all of them, I’ve built some (if I do say so myself) impressive automation around this project, and I thought I’d share. It’s some mad science, but it’s working great and I kinda want to brag about it.
For every giveaway, there are 5 major pieces:
- The announcement post containing the form to enter for the drawing, on Monday
- An announcement email to subscribers linking to the announcement post, also on Monday
- A followup email on Thursday reminding subscribers there’s still time to enter
- A drawing of the winners, and notification emails sent to them, on Friday
- An announcement post listing the winner’s names and upcoming giveaways, also on Friday
So here’s how it works. I have a master YAML file where each giveaway has the following info:
- app_name: Timing
contact: [Hookmark link to email conversation with developer]
dev_email: Daniel Gräfe <REDACTED>
keywords: [productivity, time tracking, timing]
dev_link: https://timingapp.com/
offer: a 1-year subscription ($108 value)
number_of_prizes: 1
followup: I guarantee it will be useful for anyone who needs to know where they spend there time, for any reason.
fulfillment: emails
giveaway_slug: [secret giveaway slug]
blurb: >
I use Timing daily and it's helped me automatically track time that I would never have had the discipline to
manage on my own. Billable hours, time I spend on personal projects, time I spend gaming, pretty much
anything I do on my Mac, my iPhone, or my iPad gets tracked and I can easily categorize based on rules
(that are as easy as dragging and dropping to generate). I even integrated it with
[Doing](https://brettterpstra.com/projects/doing/) for adding depth to my "What Was I Doing" tracking.
dev_blurb: >
Just keep focusing on your work while Timing records your time automatically, then review your time when you
want to. Record time faster than ever with just a few clicks. See when you worked on what and how
productive you were.
screenshot: /uploads/2023/09/timingscreenshot.jpg
setapp: true
It takes me about 10 minutes to build an entry. I have to write out my personal blurb, get a developer blurb from the website, and generate images including a screenshot (usually pulled from the website and automatically sized and converted by a Hazel script when I save it to my desktop) and a blog header and winner post header1. (Social sharing images and WEBP versions are automatically generated from the header images using RetroBatch.) So that’s the bulk of the creation time.
Slotting a new giveaway in is simply a matter of adding a new entry like above to the YAML array, and I can slot them in at any point and all of the dates will adjust accordingly.
When my giveaways.rb
script runs, it goes through this YAML file and generates all of the pieces mentioned above. It has a start date hardcoded for the start of this series, and then for each giveaway entry it adds 7 days, and sets a drawing date for the following Friday. It uses ERB templates to generate the giveaway announcement (including blurbs and screenshot), the two emails, and the winner announcement (including the followup text). The winner announcement, which gets saved to my drafts folder, includes a note letting me know exactly what command to run to execute the drawing, including how many winners to draw, where to pull the fulfillment codes from, etc. The winner announcement is also “hooked” to the conversation with the developer using Hookmark, so I can easily jump back to it. It also creates a list of upcoming giveaways, saved twice, once to a Markdown file with the secret slugs used to populate and query the Firebase database for the drawing, and once to a list that becomes publicly viewable.
The fulfillment key can be one of emails
, coupons
, or codes
, and depends on the developer’s preference for fulfilling the giveaway. If there are coupons or codes, those get a YAML array listing them and they’re written out to an individual YAML file that the Giveaway Robot will read. The Giveaway Robot (the script that does the random picking of entrants) reads the appropriate type of fulfillment file and sends a notification email to each of the winners containing a link, a redeem code, or notification that their email has been shared with the developer (who gets cc’d based on the dev_email
setting) who will be in touch with a license.
The emails that are generated are in Markdown format, saved locally. On the week that a giveaway is going to run, I run a script called create_giveaway_emails.rb APP_NAME
, where app name is just something like App Tamer
or Timing
. That will find and render the generated Markdown files, apply my BrettTerpstra.com styling, and use the Sendy API to create and schedule both emails for the week.
All of these moving pieces of this are a lot to track, so the script includes notes at every pertinent point letting me know which script needs to be run, and also generates a calendar entry with an alert that links (Hookmark) to the necessary notes, which literally tell me exactly which command to run and what to do with the output.
There’s a Jekyll plugin that generates and runs the giveaway form. I just include:
{% giveaway SLUG ISO_END_DATE %}
which is generated automatically by the giveaways.rb
script in the announcement post, and all entries will go to a Firebase database named for the SLUG. At the end of the giveaway, I just give the same slug to the “robot” and it uses random number generation to pick winners. The robot (run by the command in the winner announcement post note) generates emails to the winners, saves a list of names and emails, and outputs a list of winner names to the Terminal, ready for pasting into the winner announcement post. Actually it also uses pbcopy
to put the list right into my clipboard and opens the winner post for pasting…
So, aside from the time it took to build all of this automation, my effort per giveaway is:
- email discussion with the developer, nailing down number of copies and fulfillment options, 10m elapsed time per giveaway
- edit the master YAML file with details, 10m
- run the giveaways.rb script, 5s
- on Sunday
- edit and publish the generated giveaway post for the week, 2m
- run the
create_giveaway_emails.rb
script to generate the emails, 5s - double check the emails and scheduling because I don’t fully trust the automation, 2m
- on Wednesday
- share the giveaway reminder to social media, 5m
- on Friday
- run the giveaway robot using the command already saved in the winners post, 1m
- edit the giveaway post with the list of winners returned by the robot, 2m
So, now that the automation is built, it takes me approximately 23 minutes to add and run a giveaway. This would take me at least 3 hours per week if I were doing this manually every time, with the multiple posts, the multiple emails, and the drawing/notification, plus all of the tracking down of previous conversations and handling of codes for every giveaway. So this automation is easily saving me almost 3 hours per giveaway, and with nearly 40 giveaways currently lined up (and growing), that’s almost 120 hours of time saved already. Easily worth the ~8 hours I put into building it.
I doubt all of the scripts behind this will be of use to anyone else as is, but if you want to create any kind of similar automation, I’m available to help. If you just want to see my code to alter on your own, I’ll share it for free, and if you want to hire me to build something out for you, I charge a reasonable hourly fee. If I ever lose my cushy corporate job, this is exactly the kind of thing I would love to freelance on, so keep me in mind for that fateful day.
-
All of these header images are the same background with an app icon rotated and positioned in the hand of the “giveaway robot,” but I haven’t yet been able to automate the generation of these images. I just need to extract a PNG from the app (easy), then size, rotate, and position it over a static background. RetroBatch can’t do it, so if anyone has any grand ideas, I’d love to hear them. I bet ImageMagick could do this, but I get into rabbit holes when I start looking into it… ↩