Article summary
- The Problem: Small Task, Surprisingly Easy to Miss
- Using Google Sheets as the Source of Truth
- Architecture Overview
- Creating a Slack Bot App
- Writing the Daily Apps Script
- Writing the Daily Apps Script
- Preventing Duplicate Messages (Idempotency Matters)
- Scheduling the Automation
- Lessons Learned
- Closing Thoughts
As a software developer, I spend much of my day thinking about reliability, automation, eliminating repetitive work, and building tools that help me do my best work. Most of that effort is focused on serving project goals and delivering high-quality software. But my sense of purpose at Atomic comes from more than just the code I write. I’m also fortunate to collaborate every day with people I genuinely enjoy and respect.
One thing that doesn’t always come naturally to me is the ability to make my coworkers feel appreciated and seen. However, birthdays are a great yearly opportunity to do just that. A quick “happy birthday!” takes only a moment, but it can go a long way toward making someone feel valued.
The problem is: I’m busy, and I have competing priorities. I don’t always remember what day it is, and I can barely remember my family members’ birthdays, let alone coworkers’. This means I am usually the person who realizes a week later that I missed someone’s birthday entirely.
So I decided to automate it.
At Atomic Object, we already maintain a shared Google Sheet with basic team information, including birthdays. That made it the perfect source of truth for a lightweight automation: a daily script that checks whose birthday it is and posts a message in Slack with no need for manual scheduling, no calendar reminders, and no chance of forgetting.
I’ll walk through how I built a simple birthday bot using:
- Google Sheets as the data source
- Google Apps Script as the automation layer
- Slack API for message delivery
It’s a super simple project, but it’s a great example of how a few lines of code can support team culture in a way that is personal, reliable, and low-maintenance.
The Problem: Small Task, Surprisingly Easy to Miss
Wishing someone happy birthday is simple — but in practice, it’s the kind of thing that slips through the cracks. I don’t want to do the work of manually copying everyone’s birthdays to my own calendar and keeping that up to date (which would inevitably fail) or keep scheduling posts on Slack (what happens when employees leave the company?). So I started looking for an automated, maintainable solution.
Why This Wasn’t an AI Problem
My initial thought was to reach for an AI-driven solution, something like Cowork or another agent-style assistant with the ability to monitor systems and take actions on my behalf. But it turned out this wasn’t necessary. Birthday messages are deterministic and schedule-based. The problem breaks down to this: check a data source once per day, perform a simple date comparison, and post a predictable message. Introducing an AI agent into that workflow would add unnecessary complexity, additional credentials (🙅♀️), and more operational surface area without meaningfully improving the outcome. It would also raise questions about data access and privacy that don’t exist with a small, self-contained script. For this project, a straightforward automation seemed like the more responsible engineering choice.
Using Google Sheets as the Source of Truth
The easiest automation projects are the ones where the data already exists.
In our case, Atomic already had a shared Google Sheet containing employee information, including:
- Name
- Birthday
This was ideal: no new database, no extra maintenance burden, and the sheet is auto-updated when new employees join the company. 🎉
Architecture Overview
At a high level, the system looks like this: Google Sheet → Apps Script → Slack API → Birthday Message
More concretely:
- A Google Apps Script runs once per day
- It reads the birthday sheet
- It checks which birthdays match today’s date
- It looks up each person’s Slack user ID by email
- It posts a birthday message in a Slack channel
The entire workflow runs without servers, cron jobs, or infrastructure outside Google Workspace and Slack.
Creating a Slack Bot App
To post messages into Slack, I created a simple Slack app with a bot token.
The steps were:
- Go to api.slack.com/apps
- Create a new app “from scratch”
- Add the required OAuth scopes:
chat:writeto post messagesusers:readto view people in a workspaceusers:read.emailto look up Slack users by email
- Install the app into the workspace
- Copy the bot token (
xoxb-...) for use in Apps Script - Invite the bot into the target channel using:
/invite @BirthdayBot
Using email lookup eliminates the need to maintain Slack user IDs manually because the script can resolve mentions automatically.
Writing the Daily Apps Script
Google Apps Script turned out to be a perfect fit for this kind of lightweight automation.
A Google Apps Script:
- Lives alongside the spreadsheet
- Has built-in scheduling
- Supports HTTP requests easily
- Requires almost no deployment overhead
Writing the Daily Apps Script
Google Apps Script turned out to be a perfect fit for this kind of lightweight automation:
it lives alongside the spreadsheet, has built-in scheduling, supports HTTP requests easily,
and requires almost no deployment overhead.
The core job is straightforward: read the sheet, find birthdays that match today, resolve each
person’s Slack user ID by email, and post a message to a channel.
Read rows from the sheet
First, open the spreadsheet and load the values from the sheet tab that contains the birthday data.
(In my case the headers were on row 2, but the idea is the same.)
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName("Atom Info");
const rows = sheet.getDataRange().getValues();
Match birthdays by month and day
I didn’t need the birth year, so I normalized both today’s date and each birthday into a simple
MM-DD key and compared them.
function monthDayKey(date, tz) {
return Utilities.formatDate(date, tz, "MM-dd");
}
const todayKey = monthDayKey(new Date(), tz);
const birthdayKey = monthDayKey(birthdayDate, tz);
if (birthdayKey === todayKey) {
// Birthday match!
}
Look up the Slack user by email
To mention someone properly in Slack, you need their user ID. Since the sheet already contained
emails, the script can resolve Slack IDs automatically via users.lookupByEmail.
const url = "https://slack.com/api/users.lookupByEmail?email=" + encodeURIComponent(email);
const resp = UrlFetchApp.fetch(url, {
method: "get",
headers: { Authorization: "Bearer " + token }
});
const payload = JSON.parse(resp.getContentText());
const userId = payload.user.id;
Post the birthday message
Once you have the channel and a user ID, posting is just a call to chat.postMessage.
Mentions use the format <@USERID>.
const text = `Happy birthday <@${userId}>! 🎉`;
const resp = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", {
method: "post",
contentType: "application/json; charset=utf-8",
payload: JSON.stringify({ channel, text }),
headers: { Authorization: "Bearer " + token }
});
With those pieces in place, the daily function becomes a small loop over the rows to detect birthday matches, resolve Slack users, and post messages.
Preventing Duplicate Messages (Idempotency Matters)
Even small automations benefit from engineering discipline. I wanted the script to be idempotent, so it would be safe to run multiple times without double-posting.
In order to avoid storing any additional info in the spreadsheet, I stored dedupe state in Script Properties:
const key = `bday_sent_${year}_${email}`;
if (alreadySent(key)) return;
markSent(key);
This keeps the sheet untouched while ensuring each birthday is only announced once per year.
Scheduling the Automation
Once the script worked, deployment was simple:
- Apps Script → Triggers
- Add a trigger for the daily function
- Run it every morning (e.g., 9am)
After that, the script runs quietly in the background and posts to Slack whenever there’s a birthday!
Lessons Learned
This was a small project, but it reinforced a few principles I keep coming back to:
- Use existing sources of truth instead of creating new systems
- Prefer boring infrastructure when the problem is simple
- Design for idempotency, even in tiny automations
- Automation isn’t just about productivity — it can support team culture too
Sometimes the best internal tools aren’t the ones that optimize throughput. They’re the ones that help us show up consistently for the people we work with.
Closing Thoughts
This birthday bot is a tiny piece of code, but it helps me do something nice for people I genuinely care about.
It’s a small reminder that software engineering doesn’t always have to be about building big products. Sometimes it’s about building small systems that make everyday work a little more human.
And if it helps ensure nobody’s birthday goes unnoticed? That feels like a pretty good use of automation.
Replaced by a bot 🤖😬😂
Wait, let me get this right, your solution to trying to be a better coworker and be more personable… was to be less personable?
“One thing that doesn’t always come naturally to me is the ability to make my coworkers feel appreciated and seen. However, birthdays are a great yearly opportunity to do just that. A quick “happy birthday!” takes only a moment, but it can go a long way toward making someone feel valued.”
…
“So I decided to automate it.”
I’m pretty sure that actually takes any meaning out of it, because now you never actually have to think of your coworkers on their birthday. That’s not personal at all. “I’m really bad at doing this thing and I want to do better, so my solution is to… not do that and just automate it away.”
Does Atomic Object not have someone who regularly posts about birthdays and anniversaries? Does Atomic Object not have a google calendar for birthdays (which then gives a reminder so you can send out those personalized birthday messages)?
To be clear I’m not discounting the effort you put in here and maybe this was just to learn how to do something you didn’t know how to do. That on its own would have been reason enough and just using birthdays as an example is fine
Side note, if your first instinct is to reach for an AI tool or solution… you might be using too much AI.
Sorry if this comment comes off as harsh or mean, your article just really struck me. I know Atomic Object puts out some good articles that I’ve even relied on in the past.