     catch e
       e
 
+  @uninstall: (automatic) ->
+    base = ScriptApp.getService().getUrl()
+    if not automatic and base
+      # Only if no automatic uninstall (e.g. the user has to confirm) and the script is published as a WebApp
+      target = base.substring 0, base.lastIndexOf '/'
+
+      HtmlService.createHtmlOutput  """Click here to <a href="#{target}/manage/uninstall">uninstall</a>."""
+    else
+      # Fallback, if this is not a published WebApp or automatic uninstall is wanted
+      # remove all triggers, so there are no errors when we invalidate the authentification
+      for trigger in ScriptApp.getScriptTriggers()
+        ScriptApp.deleteTrigger trigger
+
+      # invalidate authentication
+      ScriptApp.invalidateAuth()
+      ContentService.createTextOutput 'Application successfully uninstalled.'
+
   result: (err, result) ->
     ret = []
     if @prefix

class MailButler

  constructor: (@prefix) ->

  @LABEL_BASE: "MailButler"
  @LABEL_OUTBOX: MailButler.LABEL_BASE + "/" + "Outbox"
  @KEY_PREFIX_MESSAGE: "msg_"
  @FREQUENCY_MINUTES: 1

  prefix: null

  scheduleJson: (params) ->
    try

      ScriptApp.newTrigger("process").timeBased().everyMinutes(MailButler.FREQUENCY_MINUTES).create()
    return

  @getEmail: ->
    Session.getEffectiveUser().getEmail()

  @getScheduledMails: ->
    q =
      user:     @getEmail()
      type:     @TYPE
    result = @DB.query q
    result.next() while result.hasNext()

  @processButlerMails: (d) ->
    Logger.log "Checking for scheduled mails on %s", d

    q =
      user:     @getEmail()
      version:  @VERSION
      type:     @TYPE
      when:     @DB.lessThanOrEqualTo d.getTime()

    Logger.log "Using the query #{q}"

    result = @DB.query(q).sortBy 'when', @DB.ASCENDING

    if (s = result.getSize()) > 0    
      # yep there are some
      Logger.log "Found %s candidates", s

      while result.hasNext()
        @processButlerMail result.next()

    else
      # No scheduled messages available
