you can install the add-on either by visiting the workspace marketplace or from inside gmail, calendar, drive, sheets, docs or slides.

if you don't already know this - count von count is a character from sesame street and in case you missed it - i'd previously built workbook statistics - a google sheets editor add-on but as part of 2021, i'd also wanted to publish my first google workspace add-on, which is what turned out to be this yet-another-useless-app that does nothing but shows you different counts 😛

this post would mostly talk about the learnings i've had during the process of publishing a workspace add-on as it is relatively new given all the advancements that the google workspace marketplace team along with the apps script team have been working on over the past year (card services, new marketplace listing, improved documentation, new processes etc.).

lets break this down to 3 steps -

  1. coding the add-on
  2. oauth approval process
  3. add-on listing approval process

coding the add-on

with the new apps script ide in place, i found the following to be the major differentiators vs. the old one -

  • the execution log (obviously) – just like coding any other script, it was much more easier to understand what output was being delivered by the core pieces of code that was running
  • the deployment process – i got to experience working with the 'test deployment' feature that allowed me to seamlessly install it as a workspace add-on (this was detected and picked-up using the manifest specifications)
test deployment as a workspace add-on
test deployment as a workspace add-on

i also happen to be making use of apps script advanced service and so it was important that the manifest was set up accurately as well + the add-on config was no joke either and had to be meticulously tailored to accommodate all key-value pairs that were needed – enter, the manifests for google workspace add-ons documentation.

card services

it was an interesting learning curve while building card-based interfaces as i didn't want to rely on the same old html-css bundle (not that i'm complaining 😉) – there's plenty to unpack here with their widgets and action elements that can be configured in a multitude of ways.

pro tip: while you will find yourself constrained by the pre-made ui interfaces, it is worth noting that the card services are designed to behave in the most optimal way while extending add-ons across all of google's services (hopefully, to all mobile apps in the future as well 🤞🏾) and by extension, worth getting used to!

i'm making use of the DecoratedText class (an information widget) which does have terribly minimum html formatting available.
full disclosure: this was originally inspired by the KeyValue class which has unfortunately been deprecated.

this is what the bare-bones of the common homepage looks like -

function commonHome() {
  const text = CardService.newDecoratedText()
    .setText("This is the common homepageTrigger");

  const footer = CardService.newFixedFooter()
    .setPrimaryButton(CardService.newTextButton()
      .setText('SHARE')
      .setBackgroundColor("#7c4dff")
      .setOpenLink(CardService.newOpenLink()
        .setUrl('https://twitter.com/schoraria911')))
    .setSecondaryButton(CardService.newTextButton()
      .setText('READ MORE')
      .setOpenLink(CardService.newOpenLink()
        .setUrl('https://script.gs/count-von-count/')));

  const section = CardService.newCardSection()
    .addWidget(text);
    
  const card = CardService.newCardBuilder()
    .addSection(section)
    .setFixedFooter(footer)
    .build();

  return card;
}

here are 2 more things that took me longer to understand purely from a design / UX point of view -

  • handling navigation – when to update existing card vs. return a new card instead and everything that goes in-between. i particularly obsessed over whether or not to have the app show a "back button" when refreshing the app and i was mostly inclined towards not showing it because i'm not making use of multiple cards and so it was important for me to ensure that the cards that already existed, just "updated" in their existing space post refresh
  • handling notifications (toast) – thanks to the feedback from adam morris (@clssrmtechtools) & martin hawksey (@mhawksey), i got to include this in order to indicate what was happening after the app was done refreshing (and re-fetching data), based off of a click on a card

oauth approval process

my documentation discovery journey for this process, in context of publishing an add-on went on as follows -

  1. google workspace developers > add-ons > publish your add-on
  2. google workspace developers > google workspace marketplace > overview: publish an app (step 2) > configure oauth > submit for oauth verification
  3. google cloud platform console help > oauth api verification faqs* > steps to submit your app

*it is important that one reads, understand and executes this doc in its entirety (regardless of what "type" of app you're building, "purpose", or "scopes" being used).

steps-to-submit-your-app
steps-to-submit-your-app

what i wish someone had told me about or had it been documented was that i'd have to look at a page and press buttons that looked like these (i was not mentally / emotionally prepared for it 😅):

  1. once you're done "preparing" for verification, you'd actually need to press a PUBLISH APP button -

2. "confirm" that you're pushing something to "production" -


3. ensure that you've actually prepared for verification (which isn't all that bad to do, to be honest) -


4. and finally, rest while the verification is in progress – until, of course, there are some iterations you may have to do that the api oauth dev verification team asks you to -


and that's when the fun really begins 😬 in my case, i had to address the following objections -

  1. make the privacy policy page for this add-on a lot better -

while my add-on does make use of 2 sensitive scopes, it doesn't make use of any restricted scopes but i went on a limb here (by reading between the lines) and did everything that i had to assuming that i was indeed making use of restricted scopes instead. here are some of the changes that i made after this email -

  • added a limited use disclosure on the privacy policy page, as described here
  • on the add-on homepage, added a blockquote with the link to the privacy policy page (prior to that, the privacy policy link was the very end of the page – which in retrospect, was my bad)
  • made my words more explicit about data not being stored anywhere except being displayed on the add-on interface + it not being accessible by anyone except the user
  • while individual explanation to scope already existed in the images displayed on homepage, i also added text based explanation against every scope on the privacy policy page too (which i think make a lot more sense as well)

2. create a new demo video


to which i created a new video and ensured that my response captured everything (point-by-point) as was requested in the email -


you may struggle trying to get the client id being shown in the url of the oauth consent screen but just expand the entire window to its' full size limit and navigate to the very end of the url by double-clicking it (the cursor wont show-up as the url, in that pop-up, would not be an editable one)

overall, this process mostly felt like an art which may (at times) require you do a little more than what's documented. i can only imagine the trouble folks who may use restricted scopes go through but try and keep you cool and continue engaging the review team as simply as you can 😇

genuinely happy moment amidst all this -


add-on listing approval process

unfortunately, there still isn't an email notification that's triggered at the moment you submit your add-on for review and so, it starts with waiting (mostly) -


in about 3.5 days post submission, i received an email + an invite to comment on a google doc towards the review (which isn't exactly how i'd expected it would be but that was cool).

i don't suppose there was much to do here except to share an explanation towards the behavior of the add-on while it was being executed on slides and docs but in the meantime, i suspect my add-on was marked as rejected 😭 (i can see from the review teams' pov, it was with good reasons).

amidst all the panic, i forgot to take a snap of what a rejected workspace marketplace listing status looks like but here's some context on what happened -


codebase

you can access the entire script on my github repository here or make a copy of the original script here.

here's a preview for one of the stats that i'm gathering (gmail, my favourite) -

function gmailData() {
  const mailCount = {};

  const labels = Gmail.Users.Labels.list(`me`).labels
    .filter(label => label.type == 'system')
    .forEach(function (label) {
      const labelName = label.name;
      const labelMeta = Gmail.Users.Labels.get(`me`, labelName);

      mailCount[labelName] = {
        "messagesTotal": labelMeta.messagesTotal,
        "messagesUnread": labelMeta.messagesUnread,
        "threadsTotal": labelMeta.threadsTotal,
        "threadsUnread": labelMeta.threadsUnread,
      };

    });

  console.log(mailCount);
  return mailCount;
}
gmail.gs

open source

please feel free to share your feedback, feature requests or contributions for improvements either via email (code@script.gs), twitter, github or by any other form of communication.

known limitations

  • takes too long – while this should not be the case with gmail, almost all the other services' (viz. calendar, drive, sheets, docs & slides) data need to be retrieved by paginating across the entirety of an account's lifecycle. what this means is if you've had a gmail / workspace (gsuite) account for about a decade, then the apis for all these services would try and fetch each and every resource (in bulk) but for that very time period, which could...take a while 😅
  • spinner keeps spinning (and nothing happens) – even during my workspace listing process, the odd experience that i was getting to face was that the add-on under slides and docs services did not actually load during first-run or post-install and to solve that, i had to simply refresh those docs at once and when i ran the add-on again, it worked just as well as it did for the other services. i've absolutely no idea why as there are no errors thrown and there's absolutely no difference in which the add-on has been coded for these services (in comparison to that of drive or sheets)