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

August 13, 2025

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

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:

  1. A Google Form with responses linked to a Google Sheet
  2. A Google account with access to Google Apps Script
  3. Slack webhook URL (for Slack notifications)
  4. Discord webhook URL (for Discord notifications)

Setting Up Your Google Form

First, let's ensure your Google Form is properly configured:

  1. Create or open your Google Form
  2. Go to the "Responses" tab
  3. Click the Google Sheets icon to link responses to a spreadsheet
  4. 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

  1. Go to script.google.com
  2. Click "New Project"
  3. 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

  1. Go to your Slack workspace
  2. Navigate to AppsManage apps
  3. Search for "Incoming Webhooks" and install it
  4. Click "Add Configuration"
  5. Choose a channel and click "Add Incoming WebHooks Integration"
  6. Copy the webhook URL and update SLACK_WEBHOOK_URL in the script

Discord Webhook Setup

  1. Go to your Discord server
  2. Right-click on the channel where you want notifications
  3. Select Edit ChannelIntegrationsWebhooks
  4. Click "New Webhook"
  5. Give it a name and copy the webhook URL
  6. Update DISCORD_WEBHOOK_URL in the script

Configuring the Trigger

Now we need to set up the trigger to run our script automatically:

  1. In Google Apps Script, click on the clock icon (Triggers)
  2. Click "Add Trigger"
  3. 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
  4. 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

  1. Test the script manually:
    • Run the testNotifications() function in the script editor
    • Check that you receive email, Slack, and Discord notifications
  2. Test with a real form submission:
    • Submit a test response to your Google Form
    • Verify that notifications are sent automatically
  3. 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:

  1. Verify webhook URLs are correct
  2. Check that webhooks are still active
  3. Test webhook URLs manually
  4. Add error handling and retry logic

Permission Issues

If you get permission errors:

  1. Ensure the script has access to Gmail, Sheets, and URL Fetch
  2. Authorize the script when prompted
  3. 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!