Some jobs should run once: send the reminder Thursday at 9, close the form at midnight, re-check the invoice in two hours. createOneShotTriggerAt(handlerName, when) creates a time-based trigger with .at(date) and returns its unique id. deleteTriggerById(id) then removes that one trigger after it fires. Other triggers for the same handler are left alone.
When to use
- One-off reminders and follow-ups scheduled from a sheet row or menu
- “Try again in 30 minutes” retries after a flaky API call
- Opening / closing something at a specific time (a form, a sheet protection)
How to use this snippet
/**
* Create a one-time trigger for handlerName at a specific Date.
* @param {string} handlerName top-level function name
* @param {Date|string|number} when must be in the future
* @return {string} trigger unique id (save it if you need to cancel later)
*/
function createOneShotTriggerAt(handlerName, when) {
var at = when instanceof Date ? when : new Date(when);
if (isNaN(at.getTime())) {
throw new Error('createOneShotTriggerAt: invalid date');
}
if (at.getTime() <= Date.now()) {
throw new Error('createOneShotTriggerAt: date is in the past');
}
// Typos in handler names otherwise fail silently at fire time
if (typeof globalThis[handlerName] !== 'function') {
throw new Error('createOneShotTriggerAt: no function named ' + handlerName);
}
var trigger = ScriptApp.newTrigger(handlerName).timeBased().at(at).create();
return trigger.getUniqueId();
}
/**
* Delete exactly one trigger by unique id. Returns true if it was found.
* @param {string} triggerId
* @return {boolean}
*/
function deleteTriggerById(triggerId) {
var all = ScriptApp.getProjectTriggers();
for (var i = 0; i < all.length; i++) {
if (all[i].getUniqueId() === triggerId) {
ScriptApp.deleteTrigger(all[i]);
return true;
}
}
return false;
}
Example
// Schedule: one hour from now
function scheduleFollowUp() {
var id = createOneShotTriggerAt('sendFollowUp', new Date(Date.now() + 60 * 60 * 1000));
console.log('Scheduled trigger ' + id);
}
// Time-driven event objects include triggerUid, the id of the trigger that fired
function sendFollowUp(e) {
try {
doFollowUp_(); // your work
} finally {
// Clean up: fired one-shot triggers otherwise linger in the Triggers list
if (e && e.triggerUid) deleteTriggerById(e.triggerUid);
}
}
Tips:
- A fired
.at()trigger won’t run again, but it can stay in the Triggers list and count toward the per-user trigger limit (20 per script). Delete it by id when it fires. - Why not deleteTriggersByHandler? It removes every trigger for that function, including other reminders you scheduled. Deleting by id only removes the one that fired.
- For recurring schedules, use createDailyTrigger instead.
- Timing is approximate: Apps Script may fire a few minutes after
when. Don’t use this for anything that needs to happen on the exact second. - Need data for the run (row number, email)? Save it in Script Properties under the trigger id, then read and delete it in the handler.
Tip: NitroGAS Co-Pilot can turn “remind the requester 48 hours after the row goes Pending” into a createOneShotTriggerAt call plus a handler that cleans up after itself.
Happy Coding!
