GMail - GTD Tickler

By ruiz Last update Feb 22, 2010 — Installed 13,348 times.

Archived Comments (locked)

Subscribe to Archived Comments 54 posts, 25 voices

Jesse Andrews Admin

The following is an archive of comments made before threaded discussions was implemented (November 16th, 2008)

Derek C User

It would probably help if I had a better understanding of how Gmail, Gcal, and Google Apps worked. It would also be easier if I knew how to code javascript, but still I've made the most progress towards making the tickler Gapps-friendly.

I think the problem is how the script recognizes times and dates through the Gapps interface in comparison to the Gmail interface. I'm still getting the "tickle me error - invalid date or time" message.

Also, when I click on "Edit Event Details" in the Gmail tickler, it opens up the popup window and fills the title and the description, but the dates range from ??/??/???? to ??/??/????, regardless of what date I entered in the original tickler bubble.

Any ideas?

Derek C User

Okay, I've made some progress in 5 minutes. At the links for, replace the "g" with "apps" and it will give you another error.

Now it's telling me "tickle me error; invalid date or time."

At least it's communicating now. It just can't understand anything. I really want this to work with Google Apps.

Derek C User

For some reason, I need to have the tickler installed for Gmail Addons to work right. I'm trying to adapt it to Google Apps, but I'm having some difficulties.

Edit Event Details works so far, but Remind Me doesn't work so you always have to do it the long way. Anybody know what is, and if it differs between regular Google accounts and Google Apps accounts? I think that's the only issue right now.

Here's the semi-fix for Google Apps users:

// ==UserScript==
// @name GMail - GTD Tickler
// @author Martin Ruiz
// @namespace Martin Ruiz
// @description Very Simple Ticker file functionality to GMail/GTDGMail using GCal
// @include*
// @include*
// ==/UserScript==
Author: Martin Ruiz


- I adapted code from a lot of other scripts on userscripts... lost track:)

+ Adds 'Tickle me' link for each message in message view.
+ Custom Popup for Quick Add and link to 'edit event details>>'
+ ';' shortcut key to popup 'Tickle Me' box.
+ Quickly add 'When' you want to be reminded, just like GCal QuickAdd - otherwise -
+ Opens and Fills out GCal Template in separate window with link to original message.
+ Link in GCal reminder will open GMail message in a seperate window.

+ Choose the color 'red' or some other bold color so that it clearly stands out.
+ Make your goals calendar public and have your friends track your goals too.

+ Also works with Drafts - In round about way. Draft and email, save, leave draft,
come back to draft, then 'Tickle Me'
- Catch: only works with keyboard shortcut because 'Tickle Me' link does not appear
with draft.

+ Add 'Tickle Me' link when new msgs in thread appear.
+ When not in GMail - The highlighted text and current link are included in GCal details

+ Works with Firefox 2.0 for PC with Greasemonkey

Version History:
2.5 - 05.05.2008 - merge bug fixes by cbg3
2.4 - 03.26.2007 - Fix date issues
2.3 - 11.12.2007 - Fixed for New GMail
2.2 - 10.29.2007 - Bug fix - default reminders not being set
2.1 - 10.23.2007 - Bug fix - disable shortcut in text areas
2.0 - 09.01.2007 - Rewrite
+ Shortcut
+ Quick Add
(function() {

var gmail;
var DEBUG = false;
var p = null;
var Window;
var Document;

window.addEventListener('load', function() {
if (unsafeWindow.gmonkey) {
unsafeWindow.gmonkey.load('1.0', main);
}, true);

function main(g) {

gmail = g;

Document = gmail.getNavPaneElement().ownerDocument;
Window = Document.defaultView;
p = new Prompt();

function runGTDTickler() {
if (gmail.getActiveViewType() != 'cv') return;
//gmail.getActiveViewElement().addEventListener("DOMNodeInserted", function() { insertDelegateLinks() }, false);

function setupShortcut() // keyboard shortcut: ";"
Window.addEventListener('keyup', function(e) {
if (e.altKey || e.ctrlKey || e.metaKey) {
return false;

if (e.keyCode!=59) return;
if (p.isEditing()) return;

var element;
else if(e.srcElement) element=e.srcElement;
if(element.nodeType==3) element=element.parentNode;

if(element.tagName == 'INPUT' ||
element.tagName == 'TEXTAREA') return;

if (e.keyCode==59) delegate();
}, true);

function addReminder(title,when,content) {
when = parseWhen(when);
method: 'GET',
url: '' + when,
onComplete: function(detail) {
var txt = detail.responseText;
txt = txt.replace(/while\(1\)\;/ig,'');
var dates = eval('('+txt+')');
var enddate;
var startdate;
var date;
var from = dates[0][4];
var to = dates[0][5];
if (noDate(from) && noDate(to) ) {
alert('Tickle Me Error. Invalid date or time');
// some heuristics...
if ( noDate(to) ) {
date = from;
log("no to date");
} else if ( noDate(from) ) {
date = to;
log("no from date");
} else if ( containsToday(when) ) {
date = from;
log("contains today");
} else if ( containsTomorrow(when) ) {
date = from;
log("contains tomorrow");
} else if ( containsDay(when) && !containsMonth(when) ) {//mon vs month
date = from;
log("contains day but not month");
} else if ( containsNext(when) ) {
date = from;
log("contains next");
} else if ( !isToday(from)) {
date = from;
log(from+' != '+today());
log("from != today");
} else {
date = to;
log("default date = to");
if (containsTime(when)) {
startdate = enddate = formatDate(date)+formatTime(date);
} else {
startdate = formatDate(date);
log(startdate+' - '+enddate);
addEvent(title, content, startdate, enddate);
onError: function(detail) {
alert('Tickle Me Error. Invalid date or time.');

function addEvent(title, content, startdate, enddate) {
var data=
" <category>"+
" <title>"+title+"</title>"+
" <gd:transparency>"+
" "+
" <gd:eventstatus>"+
" "+
" <gd:when><gd:reminder>"+
method: 'POST',
url: "",
data: data

function gcalHttpRequestAuthorized(params) {
method: 'POST',
data: '',
url: "",
onComplete: function (detail) {
var token;
try {
token= RegExp.$1;
if (!params.headers) params['headers'] = {};
params['headers']['Authorization'] = "GoogleLogin auth="+token;
} catch (e) {}
if (!token) { alert('You may not be logged into GCal. Try again later.'); return;}
onError: function (detail) {
alert('Tickle Me Error. You may not be logged into GCal.');

function gcalHttpRequestUnauthorized(params) {
var r = {headers:{}};

r.method = params.method;

r.headers = (params.headers?params.headers:{});

r.headers["Content-Type"] = "application/atom+xml";
if ( {
r.headers['Content-length'] =; =;
} else {
r.headers['Content-length'] = 0;

r.url = params.url;

r.onload = function(detail) {
if (detail.status == 200 || detail.status == 201) {
if(params.onComplete) params.onComplete(detail);
} else {
if (params.onError) params.onError(detail);
error("HTTP request failed2", detail);

r.onerror = function(detail) {
if (params.onError) params.onError(detail);
error("HTTP request failed", detail);



function requestToString(r) {
var s='';
for (var i in r) {
s = s + i + ": "+
(i=='headers'?requestToString(r[i]):r[i]) + "\n";
return s;

function parseWhen(when) {
var w = String(when);

// support 'tomorrow' shortcut 'tom'
w = w.replace(/^tom$/ig,"tomorrow");

if (containsNext(w)) {
if (!containsDay(w)) {
// support 'next' instead of one
w = w.replace(/next/ig,'one');
return w;

function get2Digits(x) {
return (x<10>
function today() {
var date = "yyyymmdd";
var d = new Date();

date = date.replace(/yyyy/i, d.getFullYear());
date = date.replace(/mm/i, get2Digits(d.getMonth()+1));
date = date.replace(/dd/i, get2Digits(d.getDate()));
log('today is '+date);
return date;
function isToday(d) { return (d==today()); }

function noDate(date) {return (^\?/ig) > -1);}
function containsMonth(when) {return ( > -1);}
function containsTomorrow(when) {return ( > -1);}
function containsToday(when) {return (|(now)/ig) > -1);}
function containsNext(when) {return ( > -1);}
function containsTime(when) {return (\d{1,2}((pm|am)|(:\d{2}(am|pm)))/ig) > -1);}
function containsDay(when) {return (|(tue(sday)?)|(wed(nesday)?)|(thu(rsday)?)|(fri(day)?)|(sat(urday)?)|(sun(day)?)/ig) > -1);}
function formatTime(date) {
var s = 'T00:00:00.000';//Z

if (date.length>8) {
s = 'T'+

return s;

function formatDate(date) {
var s = date.substr(0,4)+'-'+

return s;

function containsTime(when) {
var t = /\d{1,2}((pm|am)|(:\d{2}(am|pm)))/ig;
return (>-1);

function log(x) {
if (!DEBUG) return;
if (unsafeWindow.console) {

function error(x,d) { log(x+"\n"+d.responseText); };

function askWhen(callback) {;

var when = prompt("When? eg. tomorrow, next week, friday at 4pm");
if (callback && when != null) callback(when);

function editReminderDetails(url) {
var nwin =,"Tickle Me",'height=470,width=600,scrollbars=yes,menubar=no,toolbar=no,status=no');
if (nwin.focus) {nwin.focus();}

function processReminder(when, title, gmail_url, event_details_url) {
if (when) {
addReminder(title, when, gmail_url);

if (when == null) return; // cancelled --- 'blank' is Okay, open event details


function delegate()
var url = String(;
var real_id = url.match(/#(.*)?\/(.*)/)[2];
var title = Window.frames.content.document.title; var t=title;
title = escape(t);
//var gmail_url = ''+real_id;
var gmail_url = "" + real_id;
var event_details_url = "" + title + "&details=" + escape(gmail_url);

gmail_url = gmail_url.replace(/\&/ig,'&'); // replace ampersands

askWhen(function(when) { processReminder(when, t, gmail_url, event_details_url); });

function insertDelegateLinks() {
var msgs = getMessages();

var msg_count = msgs.length;
for(var id=0; id < msg_count; id++) {
var msg = msgs[id];
if(!Document.getElementById("remindme_" + id)) {

var footerLink = document.createElement('div');
footerLink.className = "X5Xvu";
footerLink.setAttribute("style","float: left;"); = "remindme_" + id;

footerLink.innerHTML = 'Tickle Me';


// customized popup window
function Prompt()
var box = Document.getElementById('tickler_prompt');
if (box) {; return this; }

var self = this;

this.callback = null;

box = Document.createElement('div');
box.setAttribute('id','tickler_prompt'); = '101';

var overlay = Document.createElement('div');
overlay.setAttribute('style','display: none;position: fixed;width: 100%;height: 100%;top: 0px;left: 0px;background: #FFFFFF;filter: alpha(opacity=50);-moz-opacity: 0.5;opacity: 0.5;');

var container = Document.createElement('div');
container.setAttribute('style','display: none;position: fixed;width: 100%;height: 100%;top: 0px;left: 0px;background: transparent;');
var inner = Document.createElement('div');
inner.setAttribute('style','display: block;width: 325px;height: 131px;margin: 120px auto 0 auto;background: #FFFFFF;text-align: center;border: 2px solid rgb(171, 171, 171); -moz-border-radius: 25px;');


'<input />'+
e.g., tomorrow, next week, friday at 5pm
<input />'+
' '+ //need space
'<label>edit event details »</label>'+
'; = box;
this.overlay = overlay;
this.container = container;
this.inner = inner;

this.isEditing = function() { return ('');}

this.hide = function() { = = 'none';} = function(callback) {
this.callback = callback;
var when = Document.getElementById('tickler_when'); = = '';
when.value = '';



// close button
Document.getElementById('tickler_close_btn').addEventListener("mousedown", function() { self.hide();}, false);
// ok button - book reminder in gcal
function() {
var when = Document.getElementById('tickler_when').value;
if(when) self.callback(when);
}, false);
// goto to gcal details
function() {
}, false);
// escape key
//Document.getElementById('tickler_when').addEventListener("blur", function(e) { self.hide(); }, false);

// enter and submit
function(e) {
if (e.keyCode == 27) self.hide();
}, false);

function(e) {
if (e.keyCode == 13) {
}, false);


function getMessages()
var msgs = [];
try {
var iterator = gmail.getActiveViewElement().ownerDocument.evaluate(
".//td[contains(concat(' ', @class, ' '), ' bEgJye ')]",
} catch(e) { return null; }
for (var msg = iterator.iterateNext(); msg; msg = iterator.iterateNext()) {

return msgs;



Comment if you're able to get it to work perfectly, and I'll do the same if I beat you to it.</gd:reminder>

veganjay User

Please disregard my previous comment - I did not realize GCal has default reminders!
Google Calendar -> Settings -> Calendars -> Notifications -> Event Reminders -> default
This is working great now! Thanks!

veganjay User

Nice add-on! I've been looking for something like this. Would it be possible to add a check-box for "Reminder" on the initial pop-up instead of going to "Edit event details"?

Tim Nikolaev User

And also would be great if we could use this with Google Apps.
Thanks for a great tool.

emptyowe User

It would be great if the initial popup allowed one to select which calendar to post into. Or if one could specify the default calendar to post tickles to.

Britonius User

Thanks Ruiz. I look forward to your findings on testing FF3b5. I would also love to see this work with the GreaseKit Simbl plugin for Safari on Mac. Know how to do that or know a Mac script dev who could help make it compatible for the Safari browser as well?

ruiz Script's Author

@cbg3 thanks for the bug fixes. I've merged them into the script. We all appreciate you looking at it.

cbg3 User

Firebug says that containsMonth is undefined. This seems to prevent the script from adding anything to Google Calendar. But once the containsMonth function is added, the script works. (BTW, the definition of Tuesday in the containsDay function needs to be tweaked from (tue(day)?) to (tue(sday)?)

ruiz Script's Author

I've never tested with FF3b5. I'll try it.

Britonius User

I added this to Firefox 3b5 and I am not seeing anything. I also use Folders4Gmail, GmailAgenda & Goolge Account Multi-Login. I have also tried this in Safari via the GreaseKit plugin and still nothing.

Big Jon Roberts User

I want to second Nathan Sudds - request - it would be great if this worked with Google Apps email accounts as well.

ruiz Script's Author

@MySaturdaySelf just fixed some of the date issues.

MySaturdaySelf User

The script does not recognize the date now. I don't know why. Before it works ok, now it only accepts tomorrow as date. Any other dates listed in the Gcal quick add don't work.

ruiz Script's Author

@Kate2174 Will try:)

Kate2174 User

This is EXACTLY the tool I've been searching for, but I'm having the same problem from my private domain Google Apps Gmail as "Master Scott" (see 03/16/08 posting below). Hopefully, this can be resolved soon. Thanks!

Master Scott User

I read the previous post about this, but am unclear as to the solution, or if there is one.

I'm using this in my custom domain Google Apps Gmail account. When I click the 'Tickle Me' link, it works like a charm, but, it defaults back to my calendar, not the Gmail/cal account that I am sending from. Is this fixable on my end? If not, I'd like to request it. Thanks

FYI: My account does have Gmail 2 new features enabled. I have GTDInbox installed.

ruiz Script's Author

@Tim Nikolaev almost got it working with google apps. It needs a few tweaks. Hopefully, I'll have a working version shortly.

Tim Nikolaev User

Google Apps is easily upgradable to Gmail 2.0.
It is under Domain Setting - General Tab:
Checkmark this: "Turn on new application features to my domain before they are rolled out to all Google Apps customers"

This is an awesome script and it would be great if it worked with Goggle Apps + Gmail 2.0

ruiz Script's Author

@Nathan Sudds - Unfortunately, it appears that Google Apps uses the old version of GMail. The script is incompatible with the old version. I'll have to investigate some more.

Nathan Sudds User

I love this idea, I'm using Google Apps, just wondering how hard it would be to adapt to Google Apps or if it's working already.

I've tried it but doesn't seem to add to my calendar.

Great script though -- oh and for those with Google Apps that mentioned the "new code not updated yet" if you are the administrator or can get them to change the setting in Manage This Domain -- In Domain Settings... Turn on new application features to my domain before they are rolled out to all Google Apps customers."

Here's the link to the instructionss:

(sorry I hope this wouldn't be considered clogging up the thread.)

rakess User

thanks man it is greatly appreciated!!

ruiz Script's Author

@rakess No, but I can look into it.