Sitemap

Create your own custom Viz of the Day Emails using the Tableau Public API

6 min readJul 21, 2025

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

  1. Go to Google Sheets and create a new blank spreadsheet.
  2. Give it a name like “Super Cool Viz of the Day Tracker”

Step 2: Open the Apps Script Editor

  1. In your new sheet, click Extensions > Apps Script.
  2. This opens the Google Apps Script editor in a new tab.
Press enter or click to view image in full size

Step 3: Copy/Paste the Script

You should see a page that looks like this:

Press enter or click to view image in full size

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

  1. 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”

Press enter or click to view image in full size

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.

Press enter or click to view image in full size

If you check your inbox, you should also just have received an email blast:

Press enter or click to view image in full size

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:

  1. In the Apps Script editor, click the clock icon (Triggers) on the left.

2. Click + Add Trigger in the bottom right.

Press enter or click to view image in full size

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
Press enter or click to view image in full size

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.

Press enter or click to view image in full size

--

--

Brittany Rosenau
Brittany Rosenau

Written by Brittany Rosenau

Design Nerd | Analytics Professional | 10x Tableau #VizOfTheDay | Iron Viz Finalist | Tableau Visionary + Public Ambassador

Responses (1)