Guest User

Polymarket Commission Email Filter v2 - Gmail quota handling

a guest
Oct 24th, 2026
10
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2. Polymarket Commission Email Filter v2 - Google Apps Script
  3.  
  4. SETUP
  5. 1. Create a project at https://script.google.com/ and paste this entire file into Code.gs.
  6. 2. Set LIMIT_DOLLARS to your preferred threshold (default: $10).
  7. 3. In the editor, go to Services > + > Gmail API > Add.
  8. 4. Run archiveSmallCommissions once and authorize Gmail access.
  9. 5. Add one hourly time-driven trigger for archiveSmallCommissions.
  10.    If you already have this trigger, keep it instead of adding a duplicate.
  11. 6. For a standard Google Cloud project, also enable the Gmail API in its console.
  12.  
  13. BEHAVIOR
  14. - Archives matching inbox messages with commissions strictly below the threshold.
  15. - Reads the sender and subject; leaves message bodies unread by this script.
  16. - Inspects at most 100 messages per run, with at least one second between API calls.
  17. - Uses a hidden Gmail label to remember inspected messages, including ones kept
  18.   in the inbox. Changing the threshold creates a new marker for future checks.
  19. - Retries temporary API failures with increasing delays, then defers remaining
  20.   work to the next scheduled run. Other errors remain visible as failures.
  21. - Stops early to leave time before the Apps Script execution limit.
  22. - To inspect a marked message again at the same threshold, remove its
  23.   PolymarketChecked_v2_<threshold in cents> label in Gmail.
  24. - This code does not send email or transmit mailbox data to third-party services.
  25. */
  26.  
  27. const LIMIT_DOLLARS = 10;
  28. const DUB_SENDER = '[email protected]';
  29. const MAX_MESSAGES_PER_RUN = 100;
  30. const MIN_API_INTERVAL_MS = 1000;
  31. const MAX_RUN_TIME_MS = 240000;
  32. const MAX_API_RETRIES = 4;
  33.  
  34. function archiveSmallCommissions() {
  35.   const limitCents = Math.round(LIMIT_DOLLARS * 100);
  36.   if (typeof LIMIT_DOLLARS !== 'number' || !Number.isSafeInteger(limitCents) || limitCents < 0) {
  37.     throw new Error('LIMIT_DOLLARS must be a non-negative dollar amount.');
  38.   }
  39.  
  40.   const lock = LockService.getScriptLock();
  41.   if (!lock.tryLock(1000)) return;
  42.  
  43.   const deadline = Date.now() + MAX_RUN_TIME_MS;
  44.   const pause = new Error('Resume remaining work on the next scheduled run.');
  45.   let nextCallAt = 0;
  46.   let checked = 0;
  47.   let archived = 0;
  48.  
  49.   // Pace every API attempt, including retries and label operations.
  50.   function callGmail(request) {
  51.     for (let attempt = 0; attempt <= MAX_API_RETRIES; attempt++) {
  52.       const wait = Math.max(0, nextCallAt - Date.now());
  53.       if (Date.now() + wait + 10000 >= deadline) throw pause;
  54.       if (wait > 0) Utilities.sleep(wait);
  55.       nextCallAt = Date.now() + MIN_API_INTERVAL_MS;
  56.  
  57.       try {
  58.         return request();
  59.       } catch (error) {
  60.         const text = String(error && error.message || error);
  61.         const temporary = /quota exceeded|rate.?limit.?exceeded|too many requests|\b429\b|backend.?error|service unavailable|internal error|\b50[0234]\b/i.test(text);
  62.         if (!temporary) throw error;
  63.         if (attempt === MAX_API_RETRIES) throw pause;
  64.  
  65.         const delay = Math.min(32000, 2000 * Math.pow(2, attempt)) +
  66.           Math.floor(Math.random() * 1000);
  67.         if (Date.now() + delay + 10000 >= deadline) throw pause;
  68.         Utilities.sleep(delay);
  69.       }
  70.     }
  71.   }
  72.  
  73.   function wasRemoved(error) {
  74.     return /\b404\b|not found|requested entity was not found/i.test(
  75.       String(error && error.message || error)
  76.     );
  77.   }
  78.  
  79.   try {
  80.     const labelName = 'PolymarketChecked_v2_' + limitCents;
  81.     const labels = callGmail(() => Gmail.Users.Labels.list('me')).labels || [];
  82.     let marker = labels.find(label => label.name === labelName);
  83.  
  84.     if (!marker) {
  85.       marker = callGmail(() => Gmail.Users.Labels.create({
  86.         name: labelName,
  87.         labelListVisibility: 'labelHide',
  88.         messageListVisibility: 'hide'
  89.       }, 'me'));
  90.     }
  91.  
  92.     // Read one bounded page before modifying any messages. The marker excludes
  93.     // completed work on the next run, so there is no shifting-page pagination.
  94.     const page = callGmail(() => Gmail.Users.Messages.list('me', {
  95.       q: `in:inbox from:${DUB_SENDER} subject:commission subject:Polymarket -label:${labelName}`,
  96.       maxResults: MAX_MESSAGES_PER_RUN
  97.     }));
  98.  
  99.     for (const item of page.messages || []) {
  100.       if (Date.now() + 15000 >= deadline) throw pause;
  101.  
  102.       let message;
  103.       try {
  104.         message = callGmail(() => Gmail.Users.Messages.get('me', item.id, {
  105.           format: 'metadata',
  106.           metadataHeaders: ['Subject', 'From']
  107.         }));
  108.       } catch (error) {
  109.         // A message may have been deleted since the list request.
  110.         if (wasRemoved(error)) continue;
  111.         throw error;
  112.       }
  113.  
  114.       if (!(message.labelIds || []).includes('INBOX')) continue;
  115.  
  116.       const headers = (message.payload || {}).headers || [];
  117.       const getHeader = name =>
  118.         (headers.find(h => h.name.toLowerCase() === name) || {}).value || '';
  119.  
  120.       const from = getHeader('from').trim();
  121.       const address = (from.match(/<([^<>]+)>/) || [null, from])[1];
  122.       let shouldArchive = false;
  123.  
  124.       // Require the exact sender and a recognized commission subject.
  125.       // Unrecognized emails stay in the inbox and receive only the marker.
  126.       if (address.trim().toLowerCase() === DUB_SENDER) {
  127.         const match = getHeader('subject').trim().match(
  128.           /^You['’]ve made a \$([0-9]+(?:,[0-9]{3})*)(?:\.([0-9]{2}))? commission from Polymarket$/i
  129.         );
  130.  
  131.         if (match) {
  132.           const cents = Number(match[1].replace(/,/g, '')) * 100 +
  133.             Number(match[2] || '0');
  134.           shouldArchive = Number.isSafeInteger(cents) && cents < limitCents;
  135.         }
  136.       }
  137.  
  138.       const changes = { addLabelIds: [marker.id] };
  139.       if (shouldArchive) changes.removeLabelIds = ['INBOX'];
  140.  
  141.       try {
  142.         // Archive and record completion in the same idempotent operation.
  143.         callGmail(() => Gmail.Users.Messages.modify(changes, 'me', item.id));
  144.       } catch (error) {
  145.         if (wasRemoved(error)) continue;
  146.         throw error;
  147.       }
  148.  
  149.       checked++;
  150.       if (shouldArchive) archived++;
  151.     }
  152.   } catch (error) {
  153.     if (error !== pause) throw error;
  154.     console.warn('Run paused. Remaining messages will be retried on the next scheduled run.');
  155.   } finally {
  156.     console.log(`Messages checked: ${checked}. Messages archived: ${archived}.`);
  157.     lock.releaseLock();
  158.   }
  159. }
  160.  
Advertisement
Add Comment
Please, Sign In to add comment