Skip to content

Instantly share code, notes, and snippets.

@bdunnette
Last active December 17, 2025 15:49
Show Gist options
  • Select an option

  • Save bdunnette/2dfee84776ec73633ae27c2b1eb5b213 to your computer and use it in GitHub Desktop.

Select an option

Save bdunnette/2dfee84776ec73633ae27c2b1eb5b213 to your computer and use it in GitHub Desktop.
/**
* 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