Google Forms is the go-to for quick data collection, but its biggest weakness can put your data integrity at risk as your processes become more complex.
Google Forms is an undeniably powerful tool. It’s the go-to solution for whipping up surveys, registration forms, and simple data collection tools in minutes. It’s accessible, free, and integrates seamlessly into the [Automatically create new folders in Google Drive, generate templates in new folders, fill out text automatically in new files, and save info in [Automated Web Scraping with [Multilingual Text-to-Speech Tool with SocialSheet Streamline Your Social Media Posting 123](https://votuduc.com/Multilingual-Text-to-Speech-Tool-with-Google-Workspace-p809282)](https://votuduc.com/Automated-Web-Scraping-with-Google-Sheets-p292968)](https://workspace.google.com/marketplace/app/auto_create_folder_and_files/430076014869) ecosystem. But as your processes become more sophisticated, you start to bump into the ceiling of its capabilities, particularly when it comes to data quality. The raw, unfiltered data that flows into your connected Google Sheet can quickly become a liability, requiring hours of manual cleanup, follow-up emails, and corrections. This is the core challenge: ensuring the data you collect is not just present, but also meaningful, contextual, and immediately usable.
Google Forms does offer a set of built-in validation rules, and for simple cases, they work perfectly. You can ensure a field contains a valid email address, a number within a specific range, or a certain number of characters. This is rule-based validation, and it’s fundamentally rigid.
Where does it break down? It falls short the moment you need to validate quality or intent, not just format. Consider these scenarios:
Subjective Content: You ask for “Project Feedback.” How do you validate that the user’s response is constructive and not just “It was fine”? Standard rules can’t differentiate between a detailed paragraph and a two-word dismissal.
Complex Criteria: You have a form for submitting project proposals. You need the “Project Description” to include a problem statement, a proposed solution, and key metrics. A simple “text contains” rule is too brittle and easily bypassed.
[How to build a Custom Sentiment Analysis System for Operations Feedback Using Google Forms OSD App Clinical Trial Management and Building Self Correcting Agentic Workflows with Vertex AI](https://votuduc.com/How-to-build-a-Custom-Sentiment-Analysis-System-for-Operations-Feedback-Using-Google-Forms-AppSheet-and-Vertex-AI-p428528): You’re collecting customer reviews. You want to automatically flag negative reviews for immediate follow-up. Google Forms has no native concept of sentiment.
Contextual Relevance: You ask users to describe their technical issue. How do you ensure the description is actually about a technical problem and not a billing question? You can’t.
Standard validation operates on a black-and-white, yes-or-no basis. It can check the what, but it has no ability to understand the how or the why behind the user’s input. This limitation is the primary driver for seeking a more intelligent approach.
This is where we graduate from static rules to dynamic intelligence. By combining the [Automated Job Creation in Real Time Jobber and Google Sheets Integration from Gmail](https://votuduc.com/Automated-Job-Creation-in-Jobber-from-Gmail-p115606) power of [AI Powered Cover Letter Automated Quote Generation and Delivery System for Jobber Engine](https://votuduc.com/AI-Powered-Cover-Letter-Automated Work Order Processing for UPS-Engine-p111092) with the cognitive capabilities of Google’s Vertex AI, we can build a validation layer that understands context, nuance, and quality.
Here’s how the two components create a powerful synergy:
**Genesis Engine AI Powered Content to Video Production Pipeline: The Conductor. Apps Script acts as the essential bridge. It lives within your AC2F Streamline Your Google Drive Workflow environment and can be triggered by events, such as a new Google Form submission. When a user submits a form, our Apps Script code will intercept the data before it’s finalized, giving us a crucial window to perform our custom validation. It’s the mechanism that lets us connect our form to an external brain.
Vertex AI: The Brain. This is where the magic happens. Vertex AI provides access to Google’s state-of-the-art Generative AI models, like Gemini. Instead of checking if a text field contains a specific word, we can send the user’s entire response to the AI and ask sophisticated questions in plain English:
“Is the sentiment of this feedback positive, negative, or neutral?”
“Does this project description clearly outline a problem and a solution? Provide a simple ‘yes’ or ‘no’ answer.”
“Based on this user’s request, classify it into one of the following categories: Technical Support, Sales Inquiry, or Account Management.”
This combination transforms our form from a simple data receptacle into an intelligent gatekeeper. We are no longer limited by predefined rules; our validation logic is now as flexible and powerful as the language model we use.
To make this concept concrete, we’re going to build a practical, real-world solution. By the end of this tutorial, you will have created an intelligent “Project Idea Submission” form that actively vets the quality of submissions.
Here’s the step-by-step breakdown of our final product:
A Google Form: We’ll set up a simple form with fields like “Your Name,” “Project Title,” and a crucial paragraph field for the “Project Description.”
An Apps Script Trigger: We will write a script that automatically runs every time a new response is submitted to our form.
Vertex AI Integration: The script will take the text from the “Project Description” field and send it to the Vertex AI Gemini API.
AI-Powered Validation Logic: We will craft a specific prompt for the AI, asking it to evaluate whether the description meets a set of quality criteria (e.g., “Does it mention a clear goal? Does it identify the target audience?”).
Conditional Automated Feedback: Based on the AI’s “pass” or “fail” assessment, our script will perform an action:
On Success: The data is written to a Google Sheet, perhaps with an extra column automatically populated with “AI-Approved.”
**On Failure: The script will automatically send a personalized email to the submitter, explaining why their submission was rejected and providing the AI-generated feedback on how to improve and resubmit their idea.
Alright, before we dive into the code and start orchestrating our AI-powered validation, we need to lay the groundwork. A solid setup is the foundation of any successful project, and this one is no different. We’ll walk through configuring your cloud environment, creating the user-facing form, and linking everything together with Apps Script. Let’s get our digital hands dirty.
Our Apps Script won’t be able to communicate with Vertex AI on its own; it needs a properly configured Google Cloud Project to act as the bridge. This project handles authentication, permissions, and, crucially, billing for the API calls we’ll be making.
Navigate to the Google Cloud Console.
If you have an existing project you’d like to use, select it from the project dropdown at the top of the page.
If not, click the dropdown and select* “NEW PROJECT”**. Give it a descriptive name like form-validation-ai and click “CREATE”.
From your project’s dashboard, navigate to the* “Billing”** section in the left-hand menu.
In the search bar at the top of the console, type* “Vertex AI API”** and select it from the results.
You’ll land on the API’s dashboard. If it’s not already enabled, click the blue* “ENABLE”** button. This process might take a minute or two.
Once you see the API dashboard with traffic and latency metrics (they’ll all be zero for now), you’re all set on the Google Cloud side.
Next, we need the user-facing component: the Google Form itself. This is where we’ll collect the input that our AI will later validate. We also need a place to store these responses, which is where Google Sheets comes in.
Go to forms.google.com and create a new blank form.
Give your form a title, such as “Project Idea Submission”.
For our example, let’s add a single question. Set the question type to* “Paragraph”** and enter the question text: Please describe your project idea in 1-2 sentences. This gives us a nice chunk of free-form text for our AI to analyze.
At the top of the Form editor, click on the* “Responses”** tab.
Click the green* “Link to Sheets”** icon.
A dialog will appear. Select* “Create a new spreadsheet”**. You can keep the default name or give it a custom one. Click “CREATE”.
A new browser tab will open with your brand-new Google Sheet. You’ll see columns for Timestamp and the question you created. Every time a user submits the form, a new row will be added here automatically. This sheet is now the source of truth for our form’s data.
Finally, let’s create the Apps Script project that will house our logic. By creating it directly from the Form, it becomes “bound” to the form, making it easier to set up triggers and interact with form events.
Go back to your Google Form editor.
Click the three-dot menu (More) in the top-right corner.
Select* “Script editor”** from the dropdown menu.
The entire process kicks off the moment a user hits “Submit” on your Google Form. To intercept this action and get our hands on the data, we’ll use [Architecting Multi Tenant AI Workflows in Building Modular Agentic Apps Script with Gemini Function Calling](https://votuduc.com/architecting-multi-tenant-ai-workflows-in-google-apps-script-p-20260321290501). Specifically, we’ll leverage an installable trigger that runs a function automatically every time a new submission comes in. This is the foundational step that connects our form to the powerful validation logic we’re about to build.
When a trigger fires in response to a form submission, it doesn’t just run our function in a vacuum. It passes along a crucial piece of information: the event object. This object is a treasure trove of contextual data about the event that just occurred. For a form submission, it contains everything we need to know about the form itself and the specific answers the user provided.
The event object, conventionally named e in the function parameter, has several key properties. The most important one for our purpose is response. This property is an instance of the FormResponse class, which holds all the answers from a single submission.
Here’s a simplified look at the structure of the event object e:
{
"authMode": "LIMITED",
"response": { / *A FormResponse object* / },
"source": { / *A Form object* / },
"triggerUid": "123456789"
}
response: This is our primary target. It contains a method called getItemResponses() which returns an array of all the individual question-and-answer pairs for that submission.
source: This refers to the Google Form itself, allowing you to programmatically get the form’s title, ID, or other properties if needed.
authMode: This tells you about the authorization level under which the script is running.
By accessing e.response, we can systematically extract every piece of data submitted by the user.
Let’s translate this theory into practice. We’ll write our first Apps Script function to capture the submission and log the data. This allows us to verify that our trigger is working correctly and see the exact structure of the data we’re receiving.
Open your Google Form.
Click the three-dot menu (More) in the top-right corner and select Script editor. This will open the Apps Script IDE.
Replace any boilerplate code with the following function:
/**
* This function is triggered automatically whenever the Google Form is submitted.
* It iterates through the user's responses and logs them.
*
* @param {Object} e The event object passed by the onFormSubmit trigger.
*/
function onFormSubmitHandler(e) {
// Get the FormResponse object from the event object
const formResponse = e.response;
// Get an array of all the ItemResponse objects
const itemResponses = formResponse.getItemResponses();
// Log the email of the respondent (if collected)
const respondentEmail = formResponse.getRespondentEmail();
Logger.log(`Processing submission from: ${respondentEmail}`);
// Loop through each ItemResponse to get the question and answer
for (let i = 0; i < itemResponses.length; i++) {
const itemResponse = itemResponses[i];
const question = itemResponse.getItem().getTitle();
const answer = itemResponse.getResponse();
// Log the question-answer pair for debugging
Logger.log(`Question: "${question}" | Answer: "${answer}"`);
}
}
Setting up the Trigger:
This function won’t run on its own. You need to tell Google Forms to execute it upon every submission.
In the Apps Script editor, click the Triggers icon (a clock) on the left-hand sidebar.
Click the + Add Trigger button in the bottom-right.
Configure the trigger with the following settings:
Choose which function to run: onFormSubmitHandler
Choose which deployment should run: Head
Select event source: From form
Select event type: On form submit
Now, go back to your form, submit a test response, and then check the logs in the Apps Script editor (by clicking the Executions icon) to see your questions and answers printed out.
Raw data is great for logging, but AI models, particularly Large Language Models (LLMs) like those in Vertex AI, perform best when they receive clear, well-structured input. Simply sending a jumble of answers isn’t enough. We need to format the user’s submission into a coherent prompt or data structure that the AI can easily parse and understand.
A highly effective method is to convert the question-and-answer pairs into a simple JSON object or a formatted string. This preserves the context of each answer by keeping it tied to its original question.
Let’s modify our function to build this structured object. This object will be the payload we eventually send to our Vertex AI endpoint.
/**
* This function is triggered automatically whenever the Google Form is submitted.
* It captures the user's responses and structures them into a JSON object
* suitable for processing by an AI model.
*
* @param {Object} e The event object passed by the onFormSubmit trigger.
* @returns {Object} A structured object containing the form responses.
*/
function onFormSubmitHandler(e) {
const formResponse = e.response;
const itemResponses = formResponse.getItemResponses();
// Initialize an empty object to hold our structured data
const structuredData = {};
// Loop through each response and populate the object
itemResponses.forEach(itemResponse => {
const question = itemResponse.getItem().getTitle();
const answer = itemResponse.getResponse();
// Use the question as the key and the answer as the value
// A simple transformation can make keys more programmatic (e.g., lowercase, no spaces)
const key = question.replace(/\s+/g, '_').toLowerCase();
structuredData[key] = answer;
});
// Log the final structured object to see the result
Logger.log("Structured data for AI processing:");
Logger.log(JSON.stringify(structuredData, null, 2));
// In the next steps, we will send this 'structuredData' object to Vertex AI
// For now, we'll return it for potential chaining or testing
return structuredData;
}
After submitting another test response, your logs will now show a clean, predictable JSON object. For a form with questions “Your Name” and “Project Description”, the output would look something like this:
{
"your_name": "Ada Lovelace",
"project_description": "I want to build an analytical engine to perform complex calculations and automate computational tasks."
}
This structured format is perfect. It’s machine-readable, maintains the crucial link between each question and its answer, and is ready to be sent as a payload to our AI validation service.
With our Apps Script function triggered and the user’s input in hand, it’s time for the main event: sending that data to a powerful AI model for analysis. This is where we bridge the gap between a simple form and an intelligent validation system. We’ll use Google’s Vertex AI platform, specifically the Gemini Pro model, to evaluate the substance of the user’s response. This process involves four key stages: authenticating our script, crafting a precise request for the AI, executing the API call, and interpreting the model’s response.
Before our script can talk to Vertex AI, it needs permission. We can’t just send an anonymous request to a powerful cloud service. Thankfully, Apps Script has a streamlined, built-in mechanism for handling this using OAuth2. It allows the script to act on behalf of the user who is running it (or who installed the trigger).
The magic happens with ScriptApp.getOAuthToken(). This method generates a short-lived access token that we can include in our API request, proving to Google Cloud that our script is authorized.
To enable this, you must explicitly declare the necessary permission scope in your script’s manifest file.
In the Apps Script editor, go to Project Settings (the gear icon ⚙️).
Check the box for “Show ‘appsscript.json’ manifest file in editor”.
Return to the Editor (the <> icon) and open the newly visible appsscript.json file.
Add the cloud-platform scope to the oauthScopes array. This scope grants the necessary permissions to call Vertex AI APIs.
Your appsscript.json file should look something like this:
{
"timeZone": "America/New_York",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/forms",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/cloud-platform"
]
}
The first time you run a function that uses this scope, Google will prompt you (or the user) with a consent screen to authorize these permissions. This is a one-time setup step for each user.
The quality of your AI’s analysis is directly proportional to the quality of your prompt. We aren’t just asking a simple question; we are instructing the model on its role, the context of the task, and—most importantly—the exact format for its response. A well-structured prompt ensures we get back predictable, machine-readable data.
Let’s assume our form has a long-answer question asking for “Project Feedback.” We want to validate that the feedback is substantive and not just a one-word answer like “good” or “ok”.
Here’s how we can construct a powerful prompt to guide Gemini:
/**
* Creates a structured prompt for the Gemini model to evaluate text quality.
* @param {string} feedbackText The user's input from the Google Form.
* @return {string} A formatted prompt string.
*/
function createValidationPrompt(feedbackText) {
// We instruct the model on its role, the criteria, and the desired output format.
const prompt = `
You are an AI assistant responsible for validating the quality of user-submitted project feedback.
**Context:**
High-quality feedback is constructive, specific, and provides enough detail to be actionable. It should be more than a few words.
Low-quality feedback is vague, generic, non-constructive, or too short to be useful (e.g., "it was good", "no comment", "ok").
**Task:**
Analyze the following user feedback and classify its quality.
**User Feedback:**
"${feedbackText}"
**Output Format:**
Respond ONLY with a valid JSON object with two keys:
1. "quality": A string that is either "high" or "low".
2. "reason": A brief, one-sentence explanation for your classification.
Example Response:
{"quality": "low", "reason": "The feedback is too vague and does not provide specific details."}
`;
return prompt;
}
This prompt is effective because:
It defines a persona: “You are an AI assistant…”
It provides clear context: It explains what constitutes “high” vs. “low” quality.
It isolates the user input: The feedback is clearly demarcated.
It enforces a strict output format: Requesting a JSON object is crucial for making the response easy to parse in our code later.
Now we combine our authentication token and our crafted prompt to make a POST request to the Vertex AI API endpoint. We’ll use Apps Script’s built-in UrlFetchApp service for this.
The endpoint for the Gemini Pro model follows this structure:
https://us-central1-aiplatform.googleapis.com/v1/projects/YOUR_PROJECT_ID/locations/us-central1/publishers/google/models/gemini-pro:predict
Remember to replace YOUR_PROJECT_ID with your actual Google Cloud Project ID.
Here is the function that brings it all together:
/**
* Calls the Vertex AI Gemini API to get an analysis of the provided text.
* @param {string} prompt The fully constructed prompt for the model.
* @return {object|null} The parsed JSON response from the API, or null on error.
*/
function callVertexAI(prompt) {
const projectId = 'your-gcp-project-id'; // <-- IMPORTANT: Replace with your Project ID
const location = 'us-central1';
const modelId = 'gemini-pro';
const endpoint = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/${modelId}:predict`;
const token = ScriptApp.getOAuthToken();
const payload = {
"instances": [
{ "prompt": prompt }
],
"parameters": {
"temperature": 0.2,
"maxOutputTokens": 256,
"topK": 40,
"topP": 0.95
}
};
const options = {
'method': 'post',
'contentType': 'application/json',
'headers': {
'Authorization': 'Bearer ' + token
},
'payload': JSON.stringify(payload),
'muteHttpExceptions': true // Important for custom error handling
};
try {
const response = UrlFetchApp.fetch(endpoint, options);
const responseCode = response.getResponseCode();
const responseBody = response.getContentText();
if (responseCode === 200) {
return JSON.parse(responseBody);
} else {
Logger.log(`Error calling Vertex AI API. Status: ${responseCode}, Body: ${responseBody}`);
return null;
}
} catch (e) {
Logger.log(`Exception during API call: ${e.message}`);
return null;
}
}
If the API call was successful, the callVertexAI function returns a parsed JSON object. However, the actual content generated by Gemini is nested inside this object. Furthermore, because we asked the model to give us a JSON string, we essentially have a JSON object containing a string that is also JSON.
Our final step is to safely parse this nested structure to get our final quality and reason values.
A typical successful response body from the API looks like this:
{
"predictions": [
{
"content": "```json\n{\"quality\": \"low\", \"reason\": \"The feedback is too short and lacks any actionable detail.\"}\n```",
"safetyAttributes": { ... },
"citationMetadata": { ... }
}
]
}
Notice the content field contains our desired JSON, but it’s wrapped in a string, sometimes with Markdown code fences (```json). We need to extract this and parse it again.
Here’s a robust function to handle the parsing:
/**
* Parses the raw response from the Vertex AI API to extract the model's structured output.
* @param {object} apiResponse The parsed JSON object from the callVertexAI function.
* @return {object|null} An object with 'quality' and 'reason' keys, or null if parsing fails.
*/
function parseGeminiResponse(apiResponse) {
if (!apiResponse || !apiResponse.predictions || !apiResponse.predictions[0]) {
Logger.log('Invalid API response structure.');
return null;
}
try {
let contentString = apiResponse.predictions[0].content;
// Clean up potential markdown code fences from the model's output
contentString = contentString.replace(/```json\n/g, '').replace(/\n```/g, '');
// The model's output is a string that needs to be parsed into a JSON object
const validationResult = JSON.parse(contentString);
if (validationResult.quality && validationResult.reason) {
return validationResult; // Success! e.g., { quality: 'low', reason: '...' }
} else {
Logger.log('Parsed JSON is missing required keys "quality" or "reason".');
return null;
}
} catch (e) {
Logger.log(`Failed to parse nested JSON from model response: ${e.message}`);
Logger.log(`Original content: ${apiResponse.predictions[0].content}`);
return null;
}
}
With these functions in place, we now have a complete workflow. We can take user input, send it to a world-class AI for nuanced analysis, and get back a clean, structured object that tells us whether the input is high-quality and why. The next step is to use this result to take action back in our Google Form.
What’s the point of an automated validation system if it keeps the results to itself? The final, crucial step in closing the loop is to communicate the outcome of the AI’s analysis back to the user or a designated reviewer. This immediate feedback transforms a simple data collection tool into an interactive, intelligent system. Fortunately, Google Apps Script makes this incredibly straightforward with its native mail services.
Automated Client Onboarding with Google Forms and Google Drive. provides a suite of built-in services accessible directly within Apps Script, and MailApp is one of the most powerful. It allows your script to send emails on your behalf, without needing to configure SMTP servers or handle complex authentication protocols.
At its core, the service is elegantly simple. The primary method you’ll use is MailApp.sendEmail().
// A basic example of sending an email
MailApp.sendEmail("[email protected]", "Submission Status", "Your submission has been processed.");
The first time you run a script that uses MailApp, Google will prompt you for permission. You must grant the script authorization to “Send email as you.” This is a one-time setup step for each script project.
The sendEmail method is highly versatile. While the simple version takes a recipient, subject, and plain-text body, you can also pass a single JavaScript object for more advanced options, including sending richly formatted HTML emails, adding CC/BCC recipients, and including attachments.
// An advanced example using an options object
MailApp.sendEmail({
to: "[email protected]",
cc: "[email protected]",
subject: "Submission Status Update",
htmlBody: "<h1>Submission Processed</h1><p>Your submission has been reviewed. See below for details.</p>"
});
For our use case, this service is the perfect tool to instantly deliver the AI’s verdict.
Our system now has a clear decision point. The Vertex AI model has analyzed the form data and provided a structured response—let’s assume it returns a JSON object with a status (e.g., “Approved” or “Rejected”) and a reason. Our script’s job is to act as the dispatcher, routing this decision into the correct notification.
This is a classic use case for an if...else statement. We’ll check the status from the AI’s response and prepare a different email template for each outcome.
Let’s see how this looks in practice. We’ll build on our previous function, taking the AI’s parsed response as an input.
function sendNotification(formResponse, aiAnalysis) {
const recipientEmail = formResponse.namedValues['Email Address'][0];
let emailSubject = "";
let emailBody = "";
if (aiAnalysis.status === 'Approved') {
// Logic for the approval email
emailSubject = "✅ Your Submission has been Approved!";
emailBody = "Congratulations! Your recent submission has been reviewed and approved by our system.";
} else {
// Logic for the rejection email
emailSubject = "❌ Action Required: Your Submission Needs Revision";
emailBody = "Thank you for your submission. After a review, our system has determined that it requires revision.";
}
// The sendEmail call will happen after we construct the message
MailApp.sendEmail(recipientEmail, emailSubject, emailBody);
}
This structure cleanly separates the two possible paths. The if block handles the “happy path” (approval), while the else block manages the rejection scenario. Now, we can make these emails far more useful by injecting specific details from the form and the AI’s feedback.
Static emails are good, but personalized, dynamic emails are great. They provide context, clarity, and actionable feedback. We can easily achieve this by using JavaScript’s template literals (the backtick ` character) to construct our email strings, embedding variables directly into them.
We have two primary sources of dynamic data:
The Form Response (formResponse): This object contains all the data the user submitted, like their name, the title of their submission, or any other relevant fields.
**The AI Analysis (aiAnalysis): This object contains the verdict from Vertex AI, crucially including the reason for a rejection.
Let’s upgrade our sendNotification function to create rich, contextual emails.
/**
* Sends a dynamic notification email based on AI analysis.
* @param {Object} formResponse - The form submission event object.
* @param {Object} aiAnalysis - The parsed JSON response from the Vertex AI API.
*/
function sendNotification(formResponse, aiAnalysis) {
// Extract dynamic data from the form submission
const recipientEmail = formResponse.namedValues['Email Address'][0];
const userName = formResponse.namedValues['Your Name'][0];
const submissionTitle = formResponse.namedValues['Submission Title'][0];
let emailSubject = "";
let htmlEmailBody = ""; // We'll use HTML for better formatting
if (aiAnalysis.status === 'Approved') {
emailSubject = `✅ Your Submission '${submissionTitle}' has been Approved!`;
htmlEmailBody = `
<p>Hi ${userName},</p>
<p>Great news! Your submission titled "<b>${submissionTitle}</b>" has passed our automated validation and has been approved.</p>
<p>No further action is required from you at this time.</p>
<p>Thank you,<br />The Automated System</p>
`;
} else {
// Extract the specific reason for rejection from the AI response
const rejectionReason = aiAnalysis.reason;
emailSubject = `❌ Action Required: Your Submission '${submissionTitle}' Needs Revision`;
htmlEmailBody = `
<p>Hi ${userName},</p>
<p>Thank you for your submission titled "<b>${submissionTitle}</b>".</p>
<p>Our automated validation system has flagged it for revision. Please review the feedback below and resubmit the form.</p>
<hr>
<p><b>Reason for Rejection:</b></p>
<p style="color: #D32F2F; font-family: monospace; background-color: #FBE9E7; padding: 10px; border-radius: 4px;">
${rejectionReason}
</p>
<hr>
<p>We appreciate you taking the time to make the necessary corrections.</p>
<p>Thank you,<br />The Automated System</p>
`;
}
// Send the dynamically generated email using the advanced options
MailApp.sendEmail({
to: recipientEmail,
subject: emailSubject,
htmlBody: htmlEmailBody
});
}
With this implementation, the user receives an email that is not only immediate but also deeply personal and helpful. An approval email confirms the exact item that was approved. More importantly, a rejection email provides the specific, AI-generated reason, empowering the user to correct the submission without requiring manual intervention from an administrator. This completes the automated feedback loop, making the entire process efficient and user-friendly.
With the high-level architecture established, let’s dive into the core of our solution: the Google Apps Script code. This script acts as the central nervous system, orchestrating the data flow from the form submission to Vertex AI and back. We’ll break it down into logical, reusable functions.
This is the heart of our operation. The onFormSubmit function is a special type of function in Apps Script known as a trigger. We’ll configure it to run automatically every time a user submits our Google Form. Its primary role is to act as a controller—gathering data, calling helper functions, and directing traffic based on the results.
Here’s the step-by-step logic:
Capture the Event: The function is triggered with an event object (e) that contains all the information about the form submission, including the user’s answers.
Extract and Prepare Data: We’ll pull the specific responses we need for validation from the event object. For this example, let’s assume our form has a field named “Product Description” that we want to validate.
Construct the Prompt: This is a critical step. We will craft a precise prompt for the Vertex AI model, combining our validation rules with the user’s submitted text. This prompt engineers the AI to act as our validation engine.
Invoke Vertex AI: The function calls our dedicated helper function (callVertexAI), passing the constructed prompt to it.
Process the Result: Upon receiving the AI’s response, it parses the result. We’ll design our prompt to ask for a JSON response (e.g., {"isValid": boolean, "reason": "string"}) for easy and reliable parsing.
Take Action: Based on the validation result, it calls another helper (sendNotificationEmail) to alert an administrator or the user of the outcome.
/**
* Main function triggered on every form submission.
* Acts as the controller for the validation workflow.
*
* @param {Object} e The event object passed by the form submission trigger.
*/
function onFormSubmit(e) {
try {
// 1. Get the form response object
const formResponse = e.response;
const itemResponses = formResponse.getItemResponses();
// 2. Extract specific answers by question title.
// IMPORTANT: Replace "Product Description" and "Submitter Email" with the exact titles of your form questions.
let productDescription = '';
let userEmail = formResponse.getRespondentEmail(); // Gets email if form collects it automatically
for (let i = 0; i < itemResponses.length; i++) {
const question = itemResponses[i].getItem().getTitle();
const answer = itemResponses[i].getResponse();
if (question === 'Product Description') {
productDescription = answer;
}
// You can add more 'if' blocks here to capture other fields
}
if (!productDescription) {
throw new Error("The 'Product Description' field was not found or is empty.");
}
// 3. Construct a detailed prompt for the AI model
const prompt = `
You are a content validation API. Analyze the following product description.
The description must meet these criteria:
1. It must be at least 20 words long.
2. It must not contain any profanity or offensive language.
3. It must mention a specific feature of the product.
4. It must not contain any placeholder text like "Lorem Ipsum" or "TBD".
Analyze the following text:
"${productDescription}"
Return your response ONLY as a valid JSON object with two keys:
- "isValid": a boolean (true if all criteria are met, false otherwise).
- "reason": a brief string explaining why it failed, or "All criteria met." if it passed.
`;
// 4. Call the Vertex AI helper function
const aiResponse = callVertexAI(prompt);
Logger.log(`Raw AI Response: ${aiResponse}`);
// 5. Parse the JSON response from the AI
const validationResult = JSON.parse(aiResponse);
const isValid = validationResult.isValid;
const reason = validationResult.reason;
Logger.log(`Validation Result: ${isValid}, Reason: ${reason}`);
// 6. Take action based on the result
sendNotificationEmail(userEmail, isValid, reason, productDescription);
} catch (error) {
// Centralized error handling (more on this later)
Logger.log(`ERROR in onFormSubmit: ${error.toString()}`);
sendNotificationEmail('[email protected]', false, `An unexpected error occurred: ${error.message}`, `Error processing submission.`);
}
}
To keep our main function clean and adhere to the single-responsibility principle, we’ll abstract the entire API interaction into its own function, callVertexAI. This function handles the technical details of authenticating and communicating with the Vertex AI endpoint.
Its responsibilities include:
Configuration: Fetching necessary details like the GCP Project ID and the specific model endpoint from PropertiesService, which is a secure way to store script properties.
Authentication: Getting a valid OAuth2 token to authorize the API request.
Payload Construction: Building the JSON payload in the exact format required by the Vertex AI API. This includes the prompt and model parameters like temperature (creativity) and maxOutputTokens.
API Request: Using Apps Script’s built-in UrlFetchApp service to send the POST request to the API endpoint.
Response Handling: Receiving the response, extracting the generated content, and returning it to the calling function.
/**
* Calls the Vertex AI API to get a prediction.
*
* @param {string} prompt The prompt to send to the model.
* @returns {string} The content of the AI's response.
*/
function callVertexAI(prompt) {
// Store these in Script Properties for security and manageability
// Go to Project Settings > Script Properties
const scriptProperties = PropertiesService.getScriptProperties();
const projectId = scriptProperties.getProperty('GCP_PROJECT_ID');
const modelId = 'gemini-1.0-pro'; // Or your specific model
const location = 'us-central1'; // Or your model's location
const apiEndpoint = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/${modelId}:predict`;
// Get the active user's OAuth2 token to authenticate the request
const accessToken = ScriptApp.getOAuthToken();
// Construct the request payload in the format Vertex AI expects
const payload = {
"instances": [{
"content": prompt
}],
"parameters": {
"temperature": 0.1, // Low temperature for deterministic, factual responses
"maxOutputTokens": 256,
"topK": 1,
"topP": 0
}
};
// Set up the options for the UrlFetchApp call
const options = {
'method': 'post',
'contentType': 'application/json',
'headers': {
'Authorization': `Bearer ${accessToken}`
},
'payload': JSON.stringify(payload),
'muteHttpExceptions': true // Important for custom error handling
};
// Make the API call
const response = UrlFetchApp.fetch(apiEndpoint, options);
const responseCode = response.getResponseCode();
const responseBody = response.getContentText();
if (responseCode === 200) {
const jsonResponse = JSON.parse(responseBody);
// Navigate the response structure to get the actual content
return jsonResponse.predictions[0].content;
} else {
// Throw a detailed error if the API call fails
throw new Error(`Vertex AI API request failed with code ${responseCode}: ${responseBody}`);
}
}
Once the validation is complete, we need a way to communicate the result. This helper function, sendNotificationEmail, is a simple but effective utility for this purpose. It dynamically constructs and sends an email, making the system’s feedback loop complete.
/**
* Sends an email notification about the validation result.
*
* @param {string} recipient The email address to send the notification to.
* @param {boolean} isValid The result of the validation.
* @param {string} reason The reason provided by the AI.
* @param {string} submittedContent The original content that was validated.
*/
function sendNotificationEmail(recipient, isValid, reason, submittedContent) {
const subject = isValid ? '✅ Form Submission Approved' : '❌ Action Required: Form Submission Rejected';
const status = isValid ? 'APPROVED' : 'REJECTED';
const body = `
A new form submission has been processed with the following result:
Status: ${status}
Reason: ${reason}
------------------------------------
Submitted Content:
------------------------------------
${submittedContent}
------------------------------------
This is an automated notification.
`;
// Use MailApp service to send the email
MailApp.sendEmail(recipient, subject, body);
Logger.log(`Notification email sent to ${recipient}`);
}
A production-ready script must be resilient. What happens if the Vertex AI API is down? What if the model returns an unexpected format? What if the form changes and our script can’t find a field? This is where robust error handling comes in.
Our primary strategy is the try...catch block within the main onFormSubmit function.
try block: This wraps our entire “happy path” logic. If any error occurs inside this block (e.g., a failed API call from callVertexAI or a JSON parsing error), execution immediately jumps to the catch block.
catch (error) block: This is our safety net.
It logs the detailed error message using Logger.log() for debugging purposes. You can view these logs in the Apps Script editor under “Executions”.
It calls the sendNotificationEmail function to send an alert to a designated administrator. This ensures that a system failure doesn’t go unnoticed. The submission can then be reviewed manually.
muteHttpExceptions: In our callVertexAI function, setting 'muteHttpExceptions': true is crucial. By default, UrlFetchApp will throw an error and halt execution on non-200 HTTP responses. Setting this to true allows our code to receive the error response, check the status code, and throw our own, more descriptive error, which is then caught by the onFormSubmit function’s catch block. This gives us full control over the error handling flow.
We’ve journeyed from a simple Google Form to a sophisticated, AI-powered gatekeeper for data quality. But the true power of this architecture isn’t just about rejecting bad data—it’s about fundamentally changing how you interact with information at its point of entry. What we’ve built is more than a validator; it’s a blueprint for intelligent automation within the Automated Discount Code Management System ecosystem.
Let’s take a step back. Traditional form validation is rigid. It checks for patterns (@ in an email) or presence (a required field). It’s effective but lacks understanding. By integrating Vertex AI via Apps Script, we’ve transcended these limitations.
**From Syntactic to Semantic: We’ve moved beyond checking if the data looks right to understanding if the data is right in its given context. This is the leap from syntax to semantics, and it’s a game-changer for data integrity.
The “Intelligence Layer”: Apps Script acted as the crucial orchestrator, the “glue” connecting the user-friendly interface of Google Forms with the immense analytical power of Vertex AI. This creates an invisible but powerful intelligence layer for your everyday tools.
Proactive, Not Reactive: This solution tackles the “garbage in, garbage out” problem at the source. Instead of cleaning messy data later, we ensure high-quality, contextually-aware data is captured from the very first submission, saving countless hours of downstream data wrangling.
The form validation example is just the beginning. The pattern you’ve learned—Interface (Forms) -> Orchestrator (Apps Script) -> Intelligence (Vertex AI)—is a remarkably flexible and reusable framework. Consider these possibilities:
Automated Content Categorization: Imagine a form for submitting internal blog ideas. Vertex AI could analyze the submitted abstract and automatically assign categories like “Engineering,” “Marketing,” or “Product Update,” populating a spreadsheet column without any manual intervention.
Sentiment Analysis & Routing: A customer feedback form could use Vertex AI to perform sentiment analysis on open-ended comments. Positive feedback could be logged for testimonials, while negative feedback could automatically trigger a high-priority ticket in a project management tool.
Entity Extraction for Structured Data: A project intake form could have a single field for “Project Description.” The AI could parse this text to extract key entities like Project Name, Client, Due Date, and Key Deliverables, and then neatly populate them into separate columns in your Google Sheet.
Real-Time Translation & Summarization: For global teams, a form could accept input in any language. Apps Script could send the text to Vertex AI to be translated into a common language (e.g., English) and even generate a one-sentence summary before it’s stored, making the data immediately accessible to everyone.
This architecture effectively turns your simple Google Form into an intelligent front-end for any number of complex workflows.
Building custom solutions with Apps Script is incredibly powerful, but it requires development time and ongoing maintenance. If the idea of infusing your Automated Email Journey with Google Sheets and Google Analytics with AI excites you, but you want to accelerate the process, you’re ready for the next step.
The principles we’ve discussed—automating categorization, extracting information, and creating intelligent workflows—are at the core of what we’ve built with the ContentDrive app. It’s a no-code solution that connects directly to your Google Drive, using AI to automatically process, tag, and organize your files and the information within them.
If you’re ready to move beyond custom scripts and implement powerful, production-ready AI automation across your entire Automated Google Slides Generation with Text Replacement, take a look at what you can achieve.
**➡️ Explore the ContentDrive app and supercharge your Google Drive today!**Whether you choose to build your own custom solution or leverage a ready-made platform, the key takeaway is this: the barrier to entry for powerful AI integration is lower than ever. The tools are at your fingertips, waiting to turn manual processes into intelligent, automated workflows. Now is the time to start transforming your data from a passive asset into an active, intelligent partner in your daily work.
Quick Links
Legal Stuff
