My office is within walking distance of Wrigley Field, the Cubs' stadium. Most street parking is permitted after 5 pm during night games. This leaves me in a predicament: I have to move my car before 5 pm on night games so I don’t get a ticket. Last year, someone in the office racked up $300 in parking tickets.
The first thing I thought of was adding all the night games to my calendar, but there are tons of them, and I was lazy, so the manual wasn’t gonna work. I could have scripted it to capture all the night games and populate my calendar, but I ran into many Google Calendar authentication issues.
So, I decided to write a cron job to check an API to see whether there was a night game; if there were, I would send an automated email in the morning to remind myself not to park there.
Here is whatwe'lll be using to build this automated email system.
Firebase Functions to run a daily cron job to check for games.
MySportsFeeds is a free service to get all of our Cubs data from.
Moment.js and moment-timezone, because timezones suck.
Axios, to make our HTTP request to MySportsFeeds.
Nodemailer to send our emails.
What You'll Need
Before diving in, make sure you have the following set up:
Node.js is installed on your machine
A Firebase project — you can create one for free at console.firebase.google.com
A MySportsFeeds account (free tier covers this use case) — register at mysportsfeeds.com
A Gmail account to send from (more on the authentication quirks below)
The Firebase CLI is installed:
npm install -g firebase-tools
This entire setup runs within Firebase's free tier — no billing required for a daily scheduled function at this frequency.
Setting Up Firebase Functions
Create a new directory and initialize our Firebase project
mkdir cubs-automation
cd cubs-automation
firebase initGo through the project setup and choose functions for the time being
Next, we’ll need to install a bunch of packages to use all this goodness.
cd functions
npm install axios moment moment-timezone nodemailer --save
At the top of your index.js file, you’ll need to load your dependencies
The following block loads all of our dependencies at the top of the index.js file:
const functions = require("firebase-functions");
const nodemailer = require("nodemailer");
const axios = require("axios");
const moment = require("moment");
const momentTZ = require("moment-timezone");Scheduling the Function with PubSub
Firebase pubsub scheduled functions work by triggering a Cloud Pub/Sub topic on a cron-style schedule you define. The function wakes up at the specified time, runs your code, and goes back to sleep — no server to manage.
Because I needed the function to run daily, I created a pubsub function that runs every day at 6:45 am.
exports.sendEmail = functions.pubsub
.schedule("every monday, tuesday, wednesday, thursday, friday 06:45")
.timeZone("America/Chicago")
.onRun(async context => {
})We can use the pubsub cron jobs to do a check every weekday. You can read more about it here and here.
I’m in Chicago, so I chose that time zone.
The following block grabs the current date and sets up the Nodemailer transporter with our Gmail credentials:
const now = moment().format("YYYYMMDD");
const mailList = [
"[email protected]"
];
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: "********",
pass: "********"
}
});We get the current date and other options for nodemailer to send us an email.
Calling the MySportsFeeds API
The following try/catch block calls the MySportsFeeds API for today's Cubs schedule, extracts the game time and venue, and checks whether a home night game is happening:
try{
const GameData = await axios.get(
`https://api.mysportsfeeds.com/v2.1/pull/mlb/2019-regular/games.json?date=${now}&team=chicago-cubs`,
{
auth: {
username: "*********",
password: "*********"
}
}
);
const { startTime } = GameData.data.games[0].schedule;
const { id } = GameData.data.games[0].schedule.venue;
const gameTime = momentTZ(startTime)
.tz("America/Chicago")
.format("HHmm");
console.log(gameTime);
const timeCheck = 1500;
console.log(startTime);
if (id === 133) {
if (gameTime > timeCheck) {
const mailOptions = {
from: "[email protected]",
to: mailList,
subject: "CUBS EVENING GAME - PARK CAREFULLY!",
html:'<p>dont park here</p>'
}
const mailSent = await transporter.sendMail(mailOptions);
console.log(mailSent);
}
}
} catch(error) {
console.log(error)
}
return true;
})Sending the Email with Nodemailer
To send an email from a Firebase Function, you pass a mailOptions object — containing from, to, subject, and html — to Nodemailer's transporter.sendMail() method. The transporter handles the SMTP connection to Gmail on your behalf.
We’ll use a try/catch because no one likes promise hell. You’ll have to get your own API key from MySportsFeeds. Notice that I’m using v2 of the API; v1 has different data types that won’t work with this code. We only really need the startTime and the game and venue IDs. The startTime v2 comes in as UTC, which we have to format for our time zone. We define a constant timeCheck for the earliest possible start time of an evening game. We create intTime so we can easily compare timeCheck to the gameTime. We’ll use parseInt to do so. We need to check whether the game is at home. In this case, Wrigley Field has an id of 133. We then await the transporter function to send out the email.
Gotchas and Lessons Learned
Note: In a production application, the worst thing you could do is store all your authentication data in plain text, as I have here. The thing you should do is save those as config variables. You can read more about that here.
Another note: You will run into issues with Gmail. You’ll have to allow your email to use insecure applications, more on that here and here.
Also: Use MySportsFeeds v2 of the API, not v1. The data structures are different enough that this code will break on v1. Verify the API is still available and active before building on it — the free tier has been reliable, but it's a third-party dependency.
This has saved me from so many tickets. I wake up and instantly know where not to park.
I used the same Firebase pubsub + Nodemailer foundation later when I built my Mailchimp daily email reporter — same scheduled-function pattern, different API and output. And I used it again for the Omeda Slack reporter, swapping the email output for Slack Block Kit messages.
Conclusion
I added everyone in the office to the list, and the parking tickets went to 0. So, don’t remember stuff; automate it to have more time to drink mimosas while you nurse a hangover.