Secure email monitor

I segment my online accounts into two groups: valuable accounts and everything else. “Valuable” can vary a little by personal priorities, but for most of us, our most valuable accounts will be those with direct access to cash: banking and investments.

By transitivity, any account that can allow access to those accounts is also in the valuable and high-risk group. These include financial aggregators and any email address used for account recovery.

I would like to keep the only computer with access to the valuable accounts locked away in a dungeon guarded by trolls, but highly restricted access also makes it difficult to monitor activity. I want to be able to see notifications on all my devices while not actually allowing them access.

Enter Google Apps script.

Set up the script

You’ll need an everyday – normal – email address, one you access from anywhere. Then, you’ll need another that you only access from secured devices, the restricted – high risk – account.

Make a restricted Gmail account for yourself.

  • Don’t use an existing email address in for recovery
  • Do use a password manager and make sure you have a backup
  • Do set up two-factor auth

Go to

Untitled Google Apps script


function digestAndArchive() {
  var monitor = ""

  // Docs say that if you have many threads, for some unspecified value of "many", you
  // should use the paginated version of getInboxThreads, as the simple version will fail.
  // It turns out that means "fail silently", returning at most some arbitrary number of
  // threads, and there is no obvious way to know there are more. I suspect the "correct"
  // way is to keep calling the paginated version with an increasing start index until
  // it returns nothing, but that seems ridiculous. For practical purposes, this function
  // returns more threads than you are likely to receive in a day.
  // So, upon first installing this script on a long-ignored inbox, it might need to run
  // several times before it clears out the inbox, but that shouldn't hurt anyone.
  var threads = GmailApp.getInboxThreads()
  var bySender = {}
  for (var i = 0; i < threads.length; i++) {
    // I'm assuming this is a receive-only email address, so all messages in a thread
    // presumably have the same sender (or similar). Organizing by sender isn't
    // strictly necessary, but I think the final digest is more understandable.
    // The docs don't say whether the first message is the most recent or not, but that
    // generally should not matter.
    var message = threads[i].getMessages()[0]
    var sender = message.getFrom()
    bySender[sender] = bySender[sender] || []
  var body = ''
  var indent = '\n  - '
  for (var sender in bySender) {
    body += sender + indent + bySender[sender].join(indent) + '\n'
  // Experimentally, it seems that GmailApp.sendEmail encodes the body as text/plain
  // so it should be safe to drop any old string in it. Would be nice to find
  // documentation to that effect. It munges astral plane characters, but for my
  // purposes here, I don't care.
  GmailApp.sendEmail(monitor, "Daily digest for " + new Date(), body)
  for (var i = 0; i < threads.length; i++) {
    // GmailApp.moveThreadsToArchive() can move multiple threads at once, but throws an
    // error and moves nothing for more than 100 threads. That's a pretty low limit when
    // you first run this on an inbox you haven't been regularly cleaning, so move one
    // by one.
    // Error detection. When a trigger-based app script fails, Google sends you an email
    // from, so it's a good idea to set up a
    // forwarding rule that emails from should go
    // to the email address you monitor.
    // Sometimes, however, Google breaks app scripts silently. In 2020, they restricted
    // permissions and this script needed to be re-approved. Instead of failing,
    // ``GmailApp.getInboxThreads()`` returned an empty result set. The daily digests
    // continued to arrive. Each digest was empty, but it's easy not to notice that
    // digests have been empty for an unusually long time.
    // So, this adds a canary email. If you receive a digest and it doesn't list this
    // status email, something has failed.
    Session.getActiveUser().getEmail(), "Daily digest OK", "")

Remember to change the email address in the script above to your normal, every day email. Save, as, for example “daily-digest”

To run by hand, go to the Run menu or just click the play button in the toolbar. At your normal address, you should see an email like this:

Subject: Daily digest for Fri Dec 02 2016 08:11:39 GMT-0500 (EST)
From: [your restricted address]

Andy from Google <>
– Josiah, welcome to your new Google Account

Run on a timer

To schedule, click Resources (menu) -> Current Project Triggers -> Click the link to add a trigger.

Set up to run daily on a timer. The time option is in your local time, as detected by Google.
Google apps time-driven trigger example

When you save the trigger, it will prompt you to authorize the trigger to run. Click “Review permissions”, which opens a new popup window, then allow.

Run the script by hand again after setting up the trigger. It should prompt for another required permission.

Daily summaries of your restricted account should now start to appear in your normal email.

If you’re paranoid – and if you’ve gotten this far, you probably are – delete the digest emails from time to time. Deleting the digests removes a possible attack vector on your high-value account because Google’s account recovery process asks the date you created an account as a form of identity verification.

Use the string literals

I think that some of my college classes took points off your grade for using literal strings instead of #define. Likewise, linters and coding standards typically want to prevent programmers from using literals.

Many indoctrinated programmers, therefore, insist that the correct way of writing a select statement is something like this:

"select " + COLUMN_NAME + ", " + COLUMN_EMAIL
+ " from " + TABLE_PEOPLE
+ " where " + COLUMN_ID + " = ? "

This jihad gains its supposed righteousness from the idea that literals make your code less maintainable. But, aside from thorough tests, what does make code maintainable? In order of importance:

  1. Readability
  2. Fewer dependencies
  3. All else equal, shorter is better

So, assume for a moment that the most maintainable code is the most readable code, and compare:

"select name, email from people where id = ?"

I suggest that the second statement is far more readable, and therefore more maintainable. It is also shorter, even without including the constant declarations, and it removes a dependency on constants defined somewhere else.

Ha! You’re ignoring the dependency on the database structure. If I want to change a column or table name, the first code is dryer.

Perhaps, but structural database changes are complex and should not be undertaken with the flippant attitude that you can do them simply by changing the value of a constant somewhere. Consider, just for starters, that if you change column name “email” to, say, “primary_email”, you will probably want to change the name of the constant to match and you will still have to search for any places the string literal might have been used, just in case.

Ok, but at least I will spend less time tracking down bugs related to spelling errors.

Sure, but seriously, how much time do you spend on spelling errors? They are typically among the easiest bugs to fix. The cumulative effort of fixing typos is less than the effort involved in managing your constants.

Life without literals is burdensome. Where should the constant declaration go? Does a definition for the same value already exist in scope? If it does, and has the same name as what you want to use, does it refer to the same thing? You add a “department” table that also has a “name” column; should you (a) use the existing constant, (b) make a new constant with a non-conflicting name, (c) rename the existing constant or, (d) both b and c?

But we have a coding standard that says what to do, so I can just follow that.

That’s just one more thing you need to remember. Do you remember precisely what your coding standard says to do? Does it even cover every case?

Does your coding standard say that uppercase identifiers must never be built from external data? Even if it does, unless you can see the constant definition in the same screenful of text as where it’s used, your code is not obviously safe from injection.

Instead of taking on this travail, just funnel your energy into picking good names for your tables, urls, or whatever in the first place. You’ll need fewer structural changes later.

Good points, but constants can add meaning, instead of being “magic numbers” sprinkled about.

True, adding meaning (see “readability”) is the one good reason for a constant instead of a literal, but usually this applies only to numbers, not strings.

What about localization?

Well… there are some good arguments for externalizing strings that appear to users, but that’s another article.

A practical web of trust?

In order of difficulty, there are three basic parts to using gpg:

  1. Generate your keys
  2. Keep your keys secure
  3. Decide who to trust

Most people will never even get to step one, much less the far more difficult steps that follow. I’ve written about this problem before [article no longer online]:

The web of trust is based on the idea that you can reduce a complex human dynamic, namely trust, into a mathematical system.

Even if you more or less understand the levels of trust gpg offers, it’s hard to be certain how much trust to assign any given key, since you can never know how the owner might use a key.

But what if the goal were simplified? Instead of something complex like “trust”, we might say you have only one decision to make about a key: is this the person I think it is?

Such a system might, I think, gain large-scale acceptance if correctly implemented. Your email address already is your identity, so use email for key signing. A large email provider, say Google, could generate a key for every address. So when Alice signs up for a Gmail account, Gmail transparently signs her outgoing mail with the key held in escrow.

This signing should be implemented such that mail agents can validate it if they know how, but those that don’t transparently ignore it. Perhaps it can be done with some form of multipart/alternative.

When using a client that is aware of the signature, the mail client prompts the user, unobtrusively. So, when Bob gets a message from Alice he sees something like this off to the side:

Are you sure this message is from Alice?

Bob can either click “yes” or ignore the prompt. If Bob clicks yes, he signs Alice’s key under the covers with his own key, also held in escrow.

Implemented in a large enough community, this system could rapidly build a network of signed keys. It’s not clear how we might use this, but if a large scale network of reliably signed keys existed, who knows?