Last active
December 17, 2025 15:49
-
-
Save bdunnette/2dfee84776ec73633ae27c2b1eb5b213 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * GOOGLE APPS SCRIPT: Auto-Invite to Groups.io on Sheet Edit | |
| * * SETUP INSTRUCTIONS: | |
| * 1. Open your Google Sheet. | |
| * 2. Go to Extensions > Apps Script. | |
| * 3. Paste this code into the editor. | |
| * 4. Update the CONFIGURATION section below with your real API Key and Group Name. | |
| * 5. Save the project. | |
| * 6. Set up the Installable Trigger: | |
| * - Click on the "Triggers" (alarm clock icon) in the left sidebar. | |
| * - Click "+ Add Trigger". | |
| * - Function to run: "processEdit" | |
| * - Deployment: "Head" | |
| * - Event source: "From spreadsheet" | |
| * - Event type: "On edit" | |
| * - Click "Save". You will be asked to authorize the script (allow it to access Sheets and external services). | |
| */ | |
| // --- CONFIGURATION --- | |
| const CONFIG = { | |
| // Your Groups.io API Key (found at https://groups.io/settings/apikeys) | |
| API_KEY: 'my-api-key', | |
| // The unique name of your group (e.g., 'umjrd-announce' from https://groups.io/g/umjrd-announce) | |
| GROUP_NAME: 'umjrd-announce', | |
| // Column number where the email is located (A=1, B=2, ... I=9) | |
| EMAIL_COLUMN: 9, | |
| // Optional: Only trigger if the edit specifically happened in the email column? | |
| // Set to true to prevent sending invites when you edit other columns in the same row. | |
| // Set to false to send invite if ANY column in the row is edited (provided email exists). | |
| TRIGGER_ON_EMAIL_COL_ONLY: true | |
| }; | |
| /** | |
| * The main function triggered by the Installable Trigger. | |
| * @param {Object} e - The event object provided by Google Sheets. | |
| */ | |
| function processEdit(e) { | |
| if (!e) { | |
| console.error('This function must be run by a trigger, not manually.'); | |
| return; | |
| } | |
| const range = e.range; | |
| const sheet = range.getSheet(); | |
| const row = range.getRow(); | |
| const col = range.getColumn(); | |
| // 1. Basic Validation | |
| // Skip if it's the header row (assuming row 1 is header) | |
| if (row === 1) return; | |
| // If configured, check if the edited column was actually the email column | |
| if (CONFIG.TRIGGER_ON_EMAIL_COL_ONLY && col !== CONFIG.EMAIL_COLUMN) { | |
| return; | |
| } | |
| // 2. Get the Email Address | |
| // We fetch the value specifically from the configured column in the active row | |
| const emailCell = sheet.getRange(row, CONFIG.EMAIL_COLUMN); | |
| const email = emailCell.getValue(); | |
| // 3. Validate Email | |
| if (!email || typeof email !== 'string' || !isValidEmail_(email)) { | |
| console.log(`Row ${row}: No valid email found in column ${CONFIG.EMAIL_COLUMN}. Value: ${email}`); | |
| return; | |
| } | |
| // 4. Send the Invite | |
| sendInvite_(email); | |
| } | |
| /** | |
| * Sends the invitation request to Groups.io | |
| * @param {string} email - The email address to invite. | |
| */ | |
| function sendInvite_(email) { | |
| // Endpoint for inviting members: https://groups.io/api/v1/groups/:group_name/invitations | |
| const url = `https://groups.io/api/v1/invite`; | |
| // Groups.io uses Basic Auth. The API Key is usually used as the password. | |
| // Sometimes it works as the username. The standard is often 'api_key:password'. | |
| // We encode "user:password" to Base64. | |
| // For Groups.io, often just the API key is needed. | |
| // We will try using the API Key as the password and a dummy user, or just the key. | |
| // Common pattern: Authorization: Basic Base64(API_KEY + ":") | |
| //const authHeader = Utilities.base64Encode(CONFIG.API_KEY + ':'); | |
| const payload = { | |
| 'group_name': CONFIG.GROUP_NAME, | |
| 'emails': email | |
| }; | |
| console.log(objectToQueryParams(payload)); | |
| const options = { | |
| 'method': 'POST', | |
| 'headers': { | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| 'Authorization': 'Bearer ' + CONFIG.API_KEY | |
| }, | |
| 'payload': objectToQueryParams(payload), | |
| 'muteHttpExceptions': true // Prevents script crash on API error so we can log it | |
| }; | |
| console.log(options); | |
| try { | |
| const response = UrlFetchApp.fetch(url, options); | |
| const responseCode = response.getResponseCode(); | |
| const responseBody = response.getContentText(); | |
| if (responseCode === 200 || responseCode === 201) { | |
| console.log(`Success: Invited ${email}`); | |
| // Optional: Mark the row as invited to prevent duplicates? | |
| // sheet.getRange(row, 10).setValue("Invited"); | |
| } else { | |
| console.error(`Error inviting ${email}: ${responseCode} - ${responseBody}`); | |
| } | |
| } catch (error) { | |
| console.error(`Network error inviting ${email}: ${error.toString()}`); | |
| } | |
| } | |
| function objectToQueryParams(obj) { | |
| return ( | |
| Object.entries(obj) | |
| .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) | |
| .join('&') | |
| ); | |
| } | |
| /** | |
| * simple regex email validation | |
| */ | |
| function isValidEmail_(email) { | |
| const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | |
| return emailRegex.test(email); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment