Create your own custom Viz of the Day Emails using the Tableau Public API
I log in to Tableau Public (nearly) daily to get inspired, look for resources, and see what my fellow data-vizzers have been up to. One of my favorite features is Viz of the Day, where a different viz is selected as a highlight on the homepage. This spotlight recognizes great storytelling, analysis, and design, and showcases what’s possible in Tableau Public for business style dashboards, artistic infographics, and leveraging new Tableau features.
In this post I’ll walk through how to use the Tableau Public API and Google Sheets to create your own Viz of the Day emails.
The Tableau Public API
Information about the Tableau API can be found here:
A big thanks to Will Sutton for this documentation!
We’ll be using this example API call for this project:
Get last 12 VOTDs: https://public.tableau.com/public/apis/bff/discover/v1/vizzes/viz-of-the-day?page=0&limit=12
I kept it at “limit=12”, but you could change this to 1 if you’d like.
Step 1: Create Your Google Sheet
- Go to Google Sheets and create a new blank spreadsheet.
- Give it a name like “Super Cool Viz of the Day Tracker”
Step 2: Open the Apps Script Editor
- In your new sheet, click Extensions > Apps Script.
- This opens the Google Apps Script editor in a new tab.
Step 3: Copy/Paste the Script
You should see a page that looks like this:
You may want to change the title from “untitled project” to something more memorable
Delete any existing starter code, and copy/paste the full script below:
function fetchVizOfTheDay() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
addHeadersIfNeeded(sheet);
const apiUrl = 'https://public.tableau.com/public/apis/bff/discover/v1/vizzes/viz-of-the-day?page=0&limit=12';
const response = UrlFetchApp.fetch(apiUrl);
const data = JSON.parse(response.getContentText());
const items = Array.isArray(data.vizzes)
? data.vizzes
: Array.isArray(data)
? data
: null;
if (!items) {
console.error("Unable to locate 'vizzes' array in API response.");
return;
}
const existingTitles = getExistingTitles(sheet);
items.reverse().forEach(item => {
const title = item.title || "No title";
const author = item.authorDisplayName || "Unknown";
const authorProfileName = item.authorProfileName || "unknown-profile";
const profileUrl = `http://public.tableau.com/app/profile/${authorProfileName}`;
const imageUrl = item.curatedImageUrl || item.thumbnailUrl || "";
const description = item.description ? stripHtml(item.description) : "";
if (!existingTitles.has(title)) {
const hyperlinkFormula = `=HYPERLINK("${profileUrl}", "View Profile")`;
const imageFormula = imageUrl ? `=IMAGE("${imageUrl}", 1)` : "";
// Add to Google Sheet including description
sheet.appendRow([title, author, hyperlinkFormula, description, imageFormula]);
// Send email notification with author profile link & description
emailNewViz(title, author, profileUrl, description, imageUrl);
}
});
formatSheet(sheet);
}
// Utility function to strip HTML tags from description
function stripHtml(html) {
return html.replace(/<[^>]*>?/gm, '').trim();
}
function emailNewViz(title, author, profileUrl, description, imageUrl) {
const emailAddress = Session.getActiveUser().getEmail(); // Or hardcode
const subject = `New Viz of the Day: ${title}`;
const htmlBody = `
<h2>${title}</h2>
<p><strong>Author:</strong> ${author}</p>
<p><a href="${profileUrl}" target="_blank">View Profile</a></p>
<p>${description}</p>
${imageUrl ? `<img src="${imageUrl}" alt="Viz preview" style="max-width:100%;height:auto;">` : ''}
`;
MailApp.sendEmail({
to: emailAddress,
subject: subject,
htmlBody: htmlBody
});
}
function getExistingTitles(sheet) {
const lastRow = sheet.getLastRow();
if (lastRow < 2) return new Set();
return new Set(sheet.getRange(2, 1, lastRow - 1, 1).getValues().flat());
}
function addHeadersIfNeeded(sheet) {
const headers = ["Title", "Author", "Profile URL", "Description", "Image Preview"];
if (sheet.getLastRow() === 0) {
sheet.appendRow(headers);
}
}
function formatSheet(sheet) {
const lastRow = sheet.getLastRow();
if (lastRow < 2) return;
sheet.setColumnWidths(1, 5, 180);
sheet.setRowHeights(2, lastRow - 1, 120);
sheet.getRange(2, 1, lastRow - 1, 5).setWrap(true);
}Note: this code was generated by lots of back and forth with ChatGPT.
Step 4: Save & Test the Script
- Click the disk icon or press Ctrl+S to save.
2. Run the function fetchVizOfTheDay by selecting it from the dropdown and clicking the ▶️ Run button.
The first time you run it, you’ll need to authorize permissions for the script to access your spreadsheet and send emails. Follow the prompts to sign in and review permissions.
If you see this screen after logging in, click “advanced” in the lower left:
Then click “Go to VOTD Email Script (unsafe)” or whatever you named the project.
It will ask you to sign in:
And then allow it to access the following, then click “save”.
If all goes well, your sheet will fill with the latest Tableau Vizzes of the Day, including clickable “View Profile” links and image previews.
Note — you may get a warning saying that some formulas are trying to send and receive data from external parties. Click “Allow access”
Once you click “allow”, your spreadsheet will populate with the 12 most recent Viz of the Days, with the title, author, profile URL, description, and image preview.
If you check your inbox, you should also just have received an email blast:
The emails will look similar to the following:
Each time the script runs, it will check to see if there is a new viz of the day — if there is not, nothing will happen. If there is a new one, a row will be added to your spreadsheet, and you’ll receive an email.
Step 5: Automate It with a Daily Trigger
To run this script automatically:
- In the Apps Script editor, click the clock icon (Triggers) on the left.
2. Click + Add Trigger in the bottom right.
3. Configure:
- Choose function:
fetchVizOfTheDay - Deployment: Head
- Event source: Time-driven
- Type of time-based trigger: Day timer
- Time of day: Select your preferred hour
And hit save! Now your sheet will update daily, and you’ll get notified by email about new Tableau Vizzes. If you’d like to have it check more frequently, you can choose to have it update at the hourly level.
Optional: Customize Your Email
You can hardcode your email instead of using your Google account’s email by replacing this line:
const emailAddress = Session.getActiveUser().getEmail();with
const emailAddress = "youremail@example.com";And that’s it — you’ve now got your own custom Viz of the Day emails going directly to your inbox.
I hope this helps you keep track of vizzes you love, introduce you to new authors, and get inspired by the work available on Tableau Public.
