Google Forms can auto-grade a multiple-choice quiz, but its built-in email notifications leave a lot to be desired.
If you want to send a customized certificate, include dynamic feedback, or format the score email to match your school's branding, the default settings fall short.
Apps Script bridges that gap.
By connecting a custom script to your form's spreadsheet, you can intercept the quiz results the second a student hits submit.
This guide walks through writing, triggering, and troubleshooting the code needed to take full control of your automated grading workflow.
What are the prerequisites for auto-grading with Apps Script?
Before writing any code, your Google Form and its destination spreadsheet must be configured correctly. Apps Script relies on a predictable data structure; if columns shift or settings change, the script will pull the wrong data.
Ensure your form meets these foundational requirements before opening the script editor.
Enable quiz mode: Open your Google Form, navigate to the
Settingstab, and toggleMake this a quizto the on position. This activates Google's native grading engine, which calculates the total points behind the scenes so your script does not have to grade each individual question.Disable native email notifications: Under the quiz settings, look for the
Release markssection. Change this fromImmediately after each submissiontoLater, after manual review. If you leave it on immediate release, Google Forms will send its own standard email, and your custom Apps Script email will act as a confusing duplicate.Collect verified email addresses: In the
Responsessettings, setCollect email addressestoVerified(if your school uses Google Workspace) orResponder input. Your script needs a reliable email address to send the final score to. If this is left off, the script will fail when it tries to find a recipient.Link a new Google Sheet: Go to the
Responsestab in your form and clickLink to Sheets. Create a new spreadsheet. This spreadsheet acts as the database for your script.Lock the column order: Once the spreadsheet is generated, submit one test response yourself. Look at the column headers. The script you write will target specific columns by their numerical order (e.g., column B for Email, column C for Score). Do not drag, drop, or rearrange these columns later, or your script will send scores to the wrong variable.
In education, consistency is critical for automation. A script is completely blind to context; it only knows that the email address is supposed to be in the second column and the score in the third.
How do you write the Apps Script to auto-grade and email scores?
With your spreadsheet linked and populated with one test row, you are ready to write the automation code. You will write this code in the spreadsheet, not the form.
Open your linked Google Sheet and click Extensions > Apps Script from the top menu. This opens the Apps Script editor in a new tab. Delete any placeholder code in the editor and replace it with the script below.
function onFormSubmit(e) {
// 1. Verify the event object exists to prevent manual run errors
if (!e || !e.values) {
Logger.log("Error: This script must be triggered by a form submission, not run manually.");
return;
}
// 2. Map the submitted data to variables based on column order
// e.values is an array of the submitted row. Arrays are zero-indexed.
// [0] = Timestamp, [1] = Email Address, [2] = Score, [3] = First Name
var timestamp = e.values[0];
var studentEmail = e.values[1];
var rawScore = e.values[2];
var studentName = e.values[3];
// 3. Clean and calculate the score
// Google Forms outputs scores as a string like "8 / 10"
var scoreParts = rawScore.split(" / ");
var pointsEarned = parseInt(scoreParts[0], 10);
var totalPoints = parseInt(scoreParts[1], 10);
// Calculate percentage to determine pass/fail status
var percentage = (pointsEarned / totalPoints) * 100;
// 4. Define dynamic feedback logic
var statusMessage = "";
if (percentage >= 80) {
statusMessage = "Excellent work! You have mastered this material.";
} else if (percentage >= 60) {
statusMessage = "Good job, but please review the chapters on cellular biology.";
} else {
statusMessage = "Please schedule a time during office hours to review this topic together.";
}
// 5. Construct the email subject and plain text body
var emailSubject = "Quiz Results: Biology Midterm - " + studentName;
var emailBody = "Hello " + studentName + ",\n\n" +
"Your quiz was successfully processed on " + timestamp + ".\n\n" +
"Your Score: " + rawScore + " (" + percentage.toFixed(1) + "%)\n\n" +
"Instructor Feedback: " + statusMessage + "\n\n" +
"Thank you,\nScience Department";
// 6. Send the email
MailApp.sendEmail({
to: studentEmail,
subject: emailSubject,
body: emailBody
});
}
The script above relies heavily on the e event object. When Google Forms pushes a new row into your spreadsheet, it simultaneously triggers this script and passes all the data from that row into the e.values array.
Because arrays in Apps Script are zero-indexed, e.values[0] corresponds to column A (the Timestamp), e.values[1] corresponds to column B (the Email Address), and so on. If your spreadsheet has the student's name in column E instead of column D, you must change the variable to e.values[4].
A common technical hurdle is how Google Forms formats the score. It does not output a clean integer like 8. It outputs a text string like 8 / 10. The script uses the split(" / ") method to cut that string in half, turning it into two separate numbers so it can calculate a real percentage.
Once the percentage is calculated, the if/else block checks the number and assigns a specific feedback message. This logic allows you to differentiate your communication automatically, giving high achievers a congratulatory note while gently directing struggling students to review specific materials.
How do you configure the on-form-submit trigger?
Writing the code is only the first half of the process. If you stop here, the script will sit dormant in your Google Sheet and do nothing when students submit the quiz.
You must explicitly tell Google to run your onFormSubmit function every time a new row is added. This is done by setting up an installable trigger.
In the Apps Script editor, look at the left-hand sidebar and click the Triggers icon (it looks like an alarm clock).
Click the blue + Add Trigger button in the bottom right corner of the screen.
A configuration menu will appear. Set the following parameters carefully:
- Choose which function to run: Select
onFormSubmit. - Choose which deployment should run: Leave as
Head. - Select event source: Choose
From spreadsheet. - Select event type: Choose
On form submit.
- Choose which function to run: Select
Click Save.
A Google authorization window will pop up. Select your Google account.
You will likely see a warning screen stating "Google hasn't verified this app." This is normal for custom scripts you write yourself. Click Advanced at the bottom, then click Go to Untitled project (unsafe).
Review the permissions requested (usually reading your spreadsheet and sending emails) and click Allow.
Your trigger is now active. The next time a form is submitted, Google will capture the data, run your code, and send the email entirely in the background.
How can you customize the email template sent to students?
The basic script uses plain text for the email body. While functional, plain text lacks visual hierarchy and can feel unpolished.
By utilizing HTML, you can format the automated email to include your school colors, bold typography for the score, and structured layouts. To do this, you replace the plain text body parameter in your script with the htmlBody parameter.
When writing automated email subjects and headers, clarity is more important than cleverness. The student should know exactly what the email is before opening it.
Email subject lines
- ❌ Weak: Quiz Results
- ✅ Strong: Your Score: Biology Midterm - Maria Hyökki
- ✅ Strong: Action Required: Review Your Biology Midterm Score
Below is a modified version of the sending logic that constructs an HTML email instead of plain text. You would replace steps 5 and 6 in the original script with this block.
// 5. Construct the HTML email body
var emailSubject = "Your Score: Biology Midterm - " + studentName;
// Use a template literal (backticks) for easier multi-line HTML formatting
var htmlTemplate = `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e0e0e0; padding: 25px; border-radius: 8px;">
<h2 style="color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; margin-top: 0;">Midterm Results</h2>
<p style="font-size: 16px; color: #333333;">Hello <strong>${studentName}</strong>,</p>
<p style="font-size: 16px; color: #333333;">Your recent submission has been graded. Here is your final score:</p>
<div style="background-color: #f8f9fa; padding: 20px; text-align: center; margin: 25px 0; border-radius: 6px; border-left: 5px solid #3498db;">
<span style="font-size: 28px; font-weight: bold; color: #2c3e50;">${rawScore}</span>
<br>
<span style="font-size: 16px; color: #666666;">(${percentage.toFixed(1)}%)</span>
</div>
<p style="font-size: 16px; color: #333333; line-height: 1.5;"><strong>Instructor Note:</strong> ${statusMessage}</p>
<hr style="border: none; border-top: 1px solid #eeeeee; margin: 30px 0;">
<p style="font-size: 12px; color: #999999; text-align: center;">
This is an automated message generated by Apps Script. Please contact your instructor directly with any questions about your grade.
</p>
</div>
`;
// 6. Send the email using the htmlBody parameter
MailApp.sendEmail({
to: studentEmail,
subject: emailSubject,
htmlBody: htmlTemplate
});
Because email clients (like Gmail, Outlook, and Apple Mail) have notoriously strict rendering rules, you cannot link to an external CSS stylesheet. All styling must be written as inline CSS within the HTML tags, as shown in the template above.
Using backticks (`) instead of standard quotation marks allows you to create multi-line strings in JavaScript, which makes writing and reading the HTML structure significantly easier. You can inject your variables directly into the HTML using the ${variableName} syntax.
Why is my Apps Script not sending quiz emails?
Even with a perfect script, environmental variables and trigger misconfigurations can cause the automation to fail. When an Apps Script fails, it usually sends a failure notification to the email address of the person who owns the script.
Use the table below to diagnose and resolve the most common auto-grading errors.
| Error Message | Root Cause | Step-by-Step Fix |
|---|---|---|
TypeError: Cannot read property 'values' of undefined |
You clicked the Run button inside the script editor manually. |
Stop clicking Run. The script requires data from a form submission (e). Submit a test response through the actual Google Form to trigger the script properly. |
Exception: Invalid email: undefined |
The script is looking at the wrong column for the email address. | Check your spreadsheet columns. If email is in column C, change var studentEmail = e.values[1] to e.values[2]. Remember arrays start at zero. |
Exception: Limit Exceeded: Email Daily |
You have hit Google's daily quota for outgoing automated emails. | Wait 24 hours for the quota to reset. If you are on a free Gmail account, consider upgrading to a Workspace account for higher limits. |
| No error message, but no email arrives | The installable trigger was never created, or it was paused. | Go to the Triggers menu (clock icon) in Apps Script. Ensure a trigger exists for onFormSubmit and that its status is not marked "Disabled". |
| Email arrives, but score says "undefined" | Google Forms is not set to quiz mode, so the score column is blank. | Open the Google Form settings, toggle Make this a quiz on, and ensure an answer key is assigned to your questions. |
In practice, the values of undefined error is the single most common stumbling block for beginners. Apps Script functions that rely on an event object (e) cannot be tested by clicking the "Run" or "Debug" buttons in the toolbar. The editor has no way to simulate a fake form submission, so the e variable remains completely empty, causing the script to crash immediately.
To test your code, you must fill out the live Google Form and press Submit.
What are the daily limits and security considerations for classroom automation?
Google places hard limits on how many emails you can send via Apps Script per day. These limits are designed to prevent spam, but they can easily catch a teacher off guard during finals week if an entire grade level submits a test on the same day.
The limits differ drastically depending on the type of Google account you are using to host and run the script.
| Account Type | Daily Email Quota (Recipients) | Script Execution Time Limit | Note |
|---|---|---|---|
Standard consumer (@gmail.com) |
100 per day | 6 minutes per run | Reaching this limit throws a fatal error and stops all remaining emails until the next day. |
| Google Workspace for Education | 1,500 per day | 6 minutes per run | Quotas are per user, not per script. All scripts on your account share this pool. |
If you are teaching a large lecture hall or managing a district-wide assessment, a standard Gmail account will not suffice. You must host the script on your official school Workspace account.
Be aware that Workspace administrators have the power to block third-party scripts or restrict emails from being sent to addresses outside of the school's domain. If your script runs without errors but students never receive the emails, check with your IT department. They may have a data loss prevention (DLP) rule in place that silently drops automated emails sent to personal addresses.
Additionally, the script runs under the authority of the person who created the trigger. The emails will come from your email address, and they will appear in your Gmail "Sent" folder.
FAQ
Can I send the graded emails from a shared department inbox instead of my personal account?
Yes, but you must use the GmailApp service instead of MailApp, and your personal account must have the shared inbox configured as an official alias in your Gmail settings. Once the alias is set up, you can add the from: "[email protected]" parameter to your GmailApp.sendEmail options.
How does the script handle manual grading or open-ended essay questions?
The script fires instantaneously when the form is submitted, meaning it will only capture the score of the multiple-choice questions that Google Forms auto-graded in that exact second. If your quiz requires manual grading for essays, you should not use an onFormSubmit trigger. Instead, you should add a custom menu to your Google Sheet and run the script manually only after you have finished grading the essays.
What happens if a student resubmits the quiz multiple times?
Because the trigger listens for every new row added to the spreadsheet, the script will run and send a new email for every single submission. If you want to limit this, you must either restrict the Google Form to "Limit to 1 response" in the settings, or write additional Apps Script logic to search previous rows and abort the email if the student's address already exists.
Can I attach a PDF summary of the graded quiz to the student email?
Yes, but it requires significantly more complex code. You would need to use DriveApp to duplicate a Google Doc template, populate it with the student's answers using replaceText(), convert that temporary document into a PDF blob, and pass that blob into the attachments array of the MailApp.sendEmail method.
Building automated workflows takes time upfront, but the payoff in reclaimed hours is substantial. If you are currently staring at stacks of physical worksheets or outdated PDFs and want to bring them into this automated ecosystem, Doc2Form can convert your existing files directly into Google Forms in minutes. From there, your new Apps Script can take over the grading and communication, allowing you to step away from data entry and focus on actual instruction.