Automate Notifications with Google Apps Script: Email and Slack/Discord Alerts for Form Submissions
August 13, 2025

Google Forms are incredibly useful for collecting data, but what happens after someone submits a form? Often, you need to be notified immediately so you can respond quickly. In this guide, I'll show you how to use Google Apps Script to automatically send notifications via email, Slack, and Discord when a Google Form is submitted.
Why Google Apps Script?
Google Apps Script is a powerful automation platform that integrates seamlessly with Google Workspace applications. It's perfect for this use case because:
- No server setup required: Runs entirely in Google's cloud
- Free tier available: Generous quotas for personal and small business use
- Easy integration: Direct access to Google Forms, Sheets, and Gmail
- JavaScript-based: Familiar syntax for web developers
- Trigger-based: Can run automatically on form submissions
Prerequisites
Before we start, you'll need:
- A Google Form with responses linked to a Google Sheet
- A Google account with access to Google Apps Script
- Slack webhook URL (for Slack notifications)
- Discord webhook URL (for Discord notifications)
Setting Up Your Google Form
First, let's ensure your Google Form is properly configured:
- Create or open your Google Form
- Go to the "Responses" tab
- Click the Google Sheets icon to link responses to a spreadsheet
- Choose to create a new spreadsheet or link to an existing one
This creates a spreadsheet where each form submission becomes a new row, with the first row containing the question headers.
Creating the Google Apps Script
Now let's create the automation script:
Step 1: Access Google Apps Script
- Go to script.google.com
- Click "New Project"
- Give your project a name (e.g., "Form Notification System")
Step 2: Write the Main Function
Here's the complete script that handles form submissions:
// Configuration - Update these values
const CONFIG = {
// Email settings
EMAIL_RECIPIENTS: ["your-email@example.com", "another-email@example.com"],
EMAIL_SUBJECT: "New Form Submission",
// Slack settings
SLACK_WEBHOOK_URL: "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
SLACK_CHANNEL: "#general", // Optional: override default channel
// Discord settings
DISCORD_WEBHOOK_URL: "https://discord.com/api/webhooks/YOUR/DISCORD/WEBHOOK",
// Form settings
FORM_NAME: "Contact Form", // Update with your form name
SPREADSHEET_ID: "your-spreadsheet-id-here", // Get this from your spreadsheet URL
};
/**
* Main function triggered when form is submitted
*/
function onFormSubmit(e) {
try {
// Get the form response data
const formResponse = e.response;
const itemResponses = formResponse.getItemResponses();
// Extract form data
const formData = extractFormData(itemResponses);
// Send notifications
sendEmailNotification(formData);
sendSlackNotification(formData);
sendDiscordNotification(formData);
console.log("All notifications sent successfully");
} catch (error) {
console.error("Error processing form submission:", error);
// Optionally send error notification
sendErrorNotification(error);
}
}
/**
* Extract and format form data
*/
function extractFormData(itemResponses) {
const data = {
timestamp: new Date().toLocaleString(),
formName: CONFIG.FORM_NAME,
responses: {},
};
itemResponses.forEach((itemResponse) => {
const question = itemResponse.getItem().getTitle();
const answer = itemResponse.getResponse();
data.responses[question] = answer;
});
return data;
}
/**
* Send email notification
*/
function sendEmailNotification(formData) {
const subject = `${CONFIG.EMAIL_SUBJECT} - ${formData.formName}`;
const body = createEmailBody(formData);
CONFIG.EMAIL_RECIPIENTS.forEach((recipient) => {
GmailApp.sendEmail(recipient, subject, body);
});
}
/**
* Create email body content
*/
function createEmailBody(formData) {
let body = `New submission received for: ${formData.formName}\n`;
body += `Time: ${formData.timestamp}\n\n`;
body += "Form Responses:\n";
body += "================\n\n";
Object.entries(formData.responses).forEach(([question, answer]) => {
body += `Question: ${question}\n`;
body += `Answer: ${answer}\n\n`;
});
return body;
}
/**
* Send Slack notification
*/
function sendSlackNotification(formData) {
const payload = {
channel: CONFIG.SLACK_CHANNEL,
text: `New form submission received!`,
attachments: [
{
color: "#36a64f",
title: `${formData.formName} - New Submission`,
title_link: `https://docs.google.com/spreadsheets/d/${CONFIG.SPREADSHEET_ID}`,
fields: createSlackFields(formData),
footer: "Google Apps Script",
ts: Math.floor(Date.now() / 1000),
},
],
};
const options = {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
};
UrlFetchApp.fetch(CONFIG.SLACK_WEBHOOK_URL, options);
}
/**
* Create Slack message fields
*/
function createSlackFields(formData) {
const fields = [];
Object.entries(formData.responses).forEach(([question, answer]) => {
fields.push({
title: question,
value: answer || "No answer provided",
short: answer && answer.length < 50,
});
});
return fields;
}
/**
* Send Discord notification
*/
function sendDiscordNotification(formData) {
const embed = {
title: `${formData.formName} - New Submission`,
description: `A new form submission was received at ${formData.timestamp}`,
color: 0x00ff00, // Green color
fields: createDiscordFields(formData),
timestamp: new Date().toISOString(),
footer: {
text: "Google Apps Script",
},
};
const payload = {
embeds: [embed],
};
const options = {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
};
UrlFetchApp.fetch(CONFIG.DISCORD_WEBHOOK_URL, options);
}
/**
* Create Discord embed fields
*/
function createDiscordFields(formData) {
const fields = [];
Object.entries(formData.responses).forEach(([question, answer]) => {
fields.push({
name: question,
value: answer || "No answer provided",
inline: true,
});
});
return fields;
}
/**
* Send error notification (optional)
*/
function sendErrorNotification(error) {
const subject = "Form Notification Error";
const body = `An error occurred while processing a form submission:\n\n${error.toString()}`;
CONFIG.EMAIL_RECIPIENTS.forEach((recipient) => {
GmailApp.sendEmail(recipient, subject, body);
});
}
/**
* Test function to manually trigger notifications
*/
function testNotifications() {
const testData = {
timestamp: new Date().toLocaleString(),
formName: CONFIG.FORM_NAME,
responses: {
Name: "John Doe",
Email: "john@example.com",
Message: "This is a test message",
},
};
sendEmailNotification(testData);
sendSlackNotification(testData);
sendDiscordNotification(testData);
console.log("Test notifications sent");
}
Setting Up Webhooks
Slack Webhook Setup
- Go to your Slack workspace
- Navigate to Apps → Manage apps
- Search for "Incoming Webhooks" and install it
- Click "Add Configuration"
- Choose a channel and click "Add Incoming WebHooks Integration"
- Copy the webhook URL and update
SLACK_WEBHOOK_URL
in the script
Discord Webhook Setup
- Go to your Discord server
- Right-click on the channel where you want notifications
- Select Edit Channel → Integrations → Webhooks
- Click "New Webhook"
- Give it a name and copy the webhook URL
- Update
DISCORD_WEBHOOK_URL
in the script
Configuring the Trigger
Now we need to set up the trigger to run our script automatically:
Method 1: Form Submit Trigger (Recommended)
- In Google Apps Script, click on the clock icon (Triggers)
- Click "Add Trigger"
- Configure the trigger:
- Choose which function to run:
onFormSubmit
- Choose which deployment should run:
Head
- Select event source:
From spreadsheet
- Select event type:
On form submit
- Select spreadsheet: Choose your form's response spreadsheet
- Choose which function to run:
- Click "Save"
Method 2: Time-based Trigger (Alternative)
If the form submit trigger doesn't work, you can use a time-based trigger:
/**
* Set up a time-based trigger to check for new submissions
*/
function setupTimeTrigger() {
// Delete existing triggers
const triggers = ScriptApp.getProjectTriggers();
triggers.forEach((trigger) => {
if (trigger.getHandlerFunction() === "checkForNewSubmissions") {
ScriptApp.deleteTrigger(trigger);
}
});
// Create new trigger to run every minute
ScriptApp.newTrigger("checkForNewSubmissions")
.timeBased()
.everyMinutes(1)
.create();
}
/**
* Check for new form submissions
*/
function checkForNewSubmissions() {
const sheet = SpreadsheetApp.openById(CONFIG.SPREADSHEET_ID).getActiveSheet();
const lastRow = sheet.getLastRow();
// Get the last processed row from PropertiesService
const properties = PropertiesService.getScriptProperties();
const lastProcessedRow = parseInt(
properties.getProperty("lastProcessedRow") || "0"
);
if (lastRow > lastProcessedRow) {
// Process new submissions
for (let row = lastProcessedRow + 1; row <= lastRow; row++) {
const rowData = sheet
.getRange(row, 1, 1, sheet.getLastColumn())
.getValues()[0];
const headers = sheet
.getRange(1, 1, 1, sheet.getLastColumn())
.getValues()[0];
const formData = {
timestamp: new Date().toLocaleString(),
formName: CONFIG.FORM_NAME,
responses: {},
};
headers.forEach((header, index) => {
if (header && rowData[index]) {
formData.responses[header] = rowData[index];
}
});
// Send notifications
sendEmailNotification(formData);
sendSlackNotification(formData);
sendDiscordNotification(formData);
}
// Update last processed row
properties.setProperty("lastProcessedRow", lastRow.toString());
}
}
Customization Options
Conditional Notifications
You can add logic to send notifications only for specific conditions:
/**
* Check if notification should be sent based on form data
*/
function shouldSendNotification(formData) {
// Example: Only send notifications for urgent requests
const message = formData.responses["Message"] || "";
const priority = formData.responses["Priority"] || "";
return (
message.toLowerCase().includes("urgent") ||
priority.toLowerCase() === "high"
);
}
// Update the main function
function onFormSubmit(e) {
const formData = extractFormData(e.response.getItemResponses());
if (shouldSendNotification(formData)) {
sendEmailNotification(formData);
sendSlackNotification(formData);
sendDiscordNotification(formData);
}
}
Rich Notifications
Enhance your notifications with more formatting:
/**
* Enhanced Slack notification with buttons
*/
function sendEnhancedSlackNotification(formData) {
const payload = {
text: `New form submission received!`,
attachments: [
{
color: "#36a64f",
title: `${formData.formName} - New Submission`,
title_link: `https://docs.google.com/spreadsheets/d/${CONFIG.SPREADSHEET_ID}`,
fields: createSlackFields(formData),
actions: [
{
name: "view_responses",
text: "View All Responses",
type: "button",
url: `https://docs.google.com/spreadsheets/d/${CONFIG.SPREADSHEET_ID}`,
},
],
footer: "Google Apps Script",
ts: Math.floor(Date.now() / 1000),
},
],
};
const options = {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
};
UrlFetchApp.fetch(CONFIG.SLACK_WEBHOOK_URL, options);
}
Testing Your Setup
- Test the script manually:
- Run the
testNotifications()
function in the script editor - Check that you receive email, Slack, and Discord notifications
- Run the
- Test with a real form submission:
- Submit a test response to your Google Form
- Verify that notifications are sent automatically
- Check logs:
- In Google Apps Script, go to Executions to see run logs
- Look for any errors or issues
Troubleshooting Common Issues
Quota Limits
Google Apps Script has daily quotas:
- Email: 1,500 emails per day
- URL Fetch: 20,000 calls per day
- Execution time: 6 hours per day
For high-volume forms, consider batching notifications or using alternative solutions.
Webhook Failures
If webhooks fail:
- Verify webhook URLs are correct
- Check that webhooks are still active
- Test webhook URLs manually
- Add error handling and retry logic
Permission Issues
If you get permission errors:
- Ensure the script has access to Gmail, Sheets, and URL Fetch
- Authorize the script when prompted
- Check that your Google account has the necessary permissions
Advanced Features
Rate Limiting
To avoid hitting API limits:
/**
* Rate-limited notification sender
*/
function sendRateLimitedNotifications(formData) {
// Add delay between notifications
sendEmailNotification(formData);
Utilities.sleep(1000); // Wait 1 second
sendSlackNotification(formData);
Utilities.sleep(1000);
sendDiscordNotification(formData);
}
Notification Templates
Create reusable templates:
const TEMPLATES = {
email: {
subject: "New {formName} Submission",
body: `
New submission received for: {formName}
Time: {timestamp}
Form Responses:
================
{responses}
View all responses: https://docs.google.com/spreadsheets/d/{spreadsheetId}
`,
},
};
function sendTemplatedEmail(formData) {
const subject = TEMPLATES.email.subject.replace(
"{formName}",
formData.formName
);
const body = TEMPLATES.email.body
.replace("{formName}", formData.formName)
.replace("{timestamp}", formData.timestamp)
.replace("{responses}", formatResponses(formData.responses))
.replace("{spreadsheetId}", CONFIG.SPREADSHEET_ID);
CONFIG.EMAIL_RECIPIENTS.forEach((recipient) => {
GmailApp.sendEmail(recipient, subject, body);
});
}
Conclusion
Google Apps Script provides a powerful and cost-effective way to automate notifications for Google Form submissions. By combining email, Slack, and Discord notifications, you can ensure that important form submissions never go unnoticed.
The key benefits of this approach are:
- Immediate notifications: Get alerted as soon as someone submits a form
- Multiple channels: Reach team members wherever they are
- Customizable: Tailor notifications to your specific needs
- Cost-effective: Free tier covers most use cases
- Reliable: Google's infrastructure ensures high availability
Whether you're managing customer inquiries, collecting feedback, or processing applications, this automation will help you respond faster and provide better service to your users.
Remember to test thoroughly and monitor your usage to stay within Google Apps Script's quotas.
This covers only some of the possibilities around Google Apps Script form notifications. For more advanced features like database integration or custom analytics, check Google Apps Script's additional APIs and services. Happy Hacking!