Google Sheets can be a practical event source for lightweight operational email: a request arrives, a form response becomes a row, and the right person gets a confirmation. This guide shows how to send email from Google Sheets with Volanea using a Google Apps Script installable trigger—not a native Sheets add-on, Marketplace listing, or client-side API key.
The integration pattern: Google Sheets, Apps Script, then Volanea
Google Sheets does not need a native email-delivery plug-in to call an external email API. A spreadsheet-bound Google Apps Script can use UrlFetchApp to make outbound HTTPS requests, and an installable trigger can run that script in response to a Google Sheets event. UrlFetchApp is Google’s built-in service for fetching external URLs; it requires authorization because it can communicate with outside hosts. (developers.google.com)
For the most reliable no-extra-platform setup, use a Google Form connected to a Google Sheet. Each submitted Form response creates a new row in the response destination sheet, and the spreadsheet’s installable On form submit trigger invokes the Apps Script function. Google calls this a form-submit event; the event object supplied to a Sheets-bound script includes the submitted row values and named values. (developers.google.com)
The flow is deliberately simple:
- A person submits a Google Form.
- Google Forms writes the response into its linked Google Sheet as a new row.
- The spreadsheet’s installable On form submit trigger starts Apps Script.
- Apps Script maps the event fields into a Volanea email payload.
UrlFetchApp.fetch()sendsPOST https://api.volanea.com/v1/sendwith a Volanea secret key and anIdempotency-Keyheader.- Volanea accepts the message for its sending pipeline, which includes suppression handling, contact upsert, rendering, tracking instrumentation, and dispatch. (volanea.com)
This is a server-side automation pattern. The key never belongs in a spreadsheet cell, browser-side JavaScript, form prefill URL, or an email template. It is read only while the authorized Apps Script execution runs.
What you need before you send an email
Prepare the sending side before writing the script. A Google Sheet can produce the trigger, but it cannot turn an arbitrary sender address into a trusted sending identity. Use a domain and From address that are configured and verified in Volanea before testing the automation.
You need the following:
- A Google Form linked to a Google Sheet. The form should collect at least an email address and whatever fields will be used in the message.
- Editor access to the Sheet and permission to create an Apps Script project and trigger.
- A Volanea secret API key, which uses the
sk_…orsk_test_…format. - A verified Volanea sender address, such as
Operations <updates@example.com>. - A clear definition of one email per form response. If one form response should notify multiple people, decide whether that means one message with multiple recipients or separate messages before implementing it.
Volanea’s REST API base URL is https://api.volanea.com, and the send endpoint is POST /v1/send. Its send endpoint supports one recipient or up to 50 recipients, and its API accepts the Idempotency-Key request header for safe retries. (volanea.com)
For this tutorial, create a Google Form whose question titles are exactly:
Email AddressFirst NameRequest TypeReference Number
The exact headers matter because the script reads the event object’s namedValues map by the form question title. You can choose different labels, but then you must change the field names in the code to match them exactly.
The concrete Google Sheets trigger that starts the send
The trigger for this integration is the spreadsheet-bound installable On form submit trigger. It fires after a respondent submits the Google Form and the linked response Sheet receives the form submission.
This distinction matters. A plain onEdit(e) trigger is not a good substitute for a form-response workflow: an edit trigger can fire when someone corrects, pastes, or reformats cells. A form-submit trigger ties the send to the intended business event—one submitted response—not to every worksheet change.
Apps Script’s spreadsheet form-submit event provides data in a shape conceptually like this:
{
namedValues: {
"Timestamp": ["9/20/2026 10:42:19"],
"Email Address": ["maya@example.net"],
"First Name": ["Maya"],
"Request Type": ["Demo request"],
"Reference Number": ["REQ-1042"]
},
values: [
"9/20/2026 10:42:19",
"maya@example.net",
"Maya",
"Demo request",
"REQ-1042"
],
range: /* Range for the new response row */,
source: /* Spreadsheet */,
triggerUid: "…"
}
namedValues is preferable for email content because it maps question names to arrays of submitted answers. values is positional, so it becomes fragile when a form owner reorders questions or inserts a new column. The range identifies the row written by that individual submission, which gives us a stable source for an idempotency key. Google documents that trigger functions receive an event object, and the Sheets form-submit event includes namedValues, range, and values; installable events also include triggerUid. (developers.google.com)
Create the trigger in Apps Script
Open the response spreadsheet, then choose Extensions → Apps Script. Paste the script from the next section and save it. In the Apps Script editor:
- Select Triggers in the left navigation.
- Select Add Trigger.
- Choose the function
sendVolaneaOnFormSubmit. - Select From spreadsheet as the event source.
- Select On form submit as the event type.
- Save and complete Google’s authorization prompts.
Installable triggers, unlike simple triggers, can call services that need authorization, including external requests through UrlFetchApp. They run under the account of the person who created the trigger, which is important for ownership and access planning. (developers.google.com)
Do not use the Run button in the editor to test sendVolaneaOnFormSubmit. That function expects a real form-submit event object, which only exists when a form response reaches the Sheet. Test by submitting an actual form response to the linked form.
Store the Volanea API key safely on the Google Sheets side
The Volanea key should live in Apps Script Script Properties, not in the Sheet, Form, source code, or a front-end configuration value. Script Properties are persistent key-value settings scoped to a script and are meant for application-wide configuration. Google’s documentation specifically describes Script Properties as appropriate for configuration such as external-service credentials. (developers.google.com)
In the Apps Script editor:
- Open Project Settings from the left navigation.
- Under Script Properties, select Add script property.
- Add
VOLANEA_API_KEYwith your secret key as its value. - Add
VOLANEA_FROMwith a verified sender value, for exampleOperations <updates@example.com>. - Save the properties.
Keep the key out of Code.gs. A hard-coded API key can leak through copied scripts, downloaded source, revision history, a shared Git repository, screenshots, or a collaborator’s browser. A value in a Sheet cell is worse: any editor may be able to read it, export it, copy it into another workbook, or accidentally include it in a CSV.
Script Properties are not a complete secrets-management boundary against project editors. Anyone trusted with editor access to the Apps Script project is part of the trusted administration group. Use least privilege: only people who need to maintain the automation should have editor access to the Sheet and its bound script. If a maintainer leaves, transfer ownership or use a shared-drive arrangement so the trigger and project remain maintainable. Google notes that collaborators with editor access can see and change Apps Script code, and recommends planning ownership and shared-drive access for team scripts. (developers.google.com)
Working Apps Script: field mapping and the Volanea REST call
Paste this code into the spreadsheet-bound Apps Script project. It reads the submitted form event, maps each field, escapes form values before inserting them in HTML, and sends the email through Volanea’s POST /v1/send endpoint.
/**
* Install this as a spreadsheet trigger:
* Event source: From spreadsheet
* Event type: On form submit
*
* Required Script Properties:
* - VOLANEA_API_KEY = sk_... or sk_test_...
* - VOLANEA_FROM = Operations <updates@example.com>
*/
function sendVolaneaOnFormSubmit(e) {
if (!e || !e.namedValues || !e.range) {
throw new Error('This function must run from a spreadsheet On form submit trigger.');
}
const props = PropertiesService.getScriptProperties();
const apiKey = props.getProperty('VOLANEA_API_KEY');
const from = props.getProperty('VOLANEA_FROM');
if (!apiKey || !from) {
throw new Error('Missing VOLANEA_API_KEY or VOLANEA_FROM Script Property.');
}
// Google Sheets form-submit field mapping.
// Each namedValues entry is an array because a question can have multiple values.
const to = requiredValue_(e.namedValues, 'Email Address').trim();
const firstName = valueOr_(e.namedValues, 'First Name', 'there').trim();
const requestType = valueOr_(e.namedValues, 'Request Type', 'your request').trim();
const referenceNumber = valueOr_(e.namedValues, 'Reference Number', '').trim();
if (!isEmail_(to)) {
throw new Error('The submitted Email Address is missing or does not look valid: ' + to);
}
const subject = 'We received your ' + requestType;
const text = [
'Hi ' + firstName + ',',
'',
'We received your ' + requestType + '.',
referenceNumber ? 'Reference number: ' + referenceNumber : '',
'',
'We will be in touch shortly.'
].filter(Boolean).join('\n');
const html = [
'<p>Hi ' + escapeHtml_(firstName) + ',</p>',
'<p>We received your <strong>' + escapeHtml_(requestType) + '</strong>.</p>',
referenceNumber
? '<p>Reference number: <strong>' + escapeHtml_(referenceNumber) + '</strong></p>'
: '',
'<p>We will be in touch shortly.</p>'
].filter(Boolean).join('');
// One form response writes one row. Sheet ID + row number creates a stable
// logical message ID for retries of that same response.
const sheet = e.range.getSheet();
const logicalMessageId = [
e.source.getId(),
sheet.getSheetId(),
e.range.getRow(),
'request-confirmation-v1'
].join(':');
const idempotencyKey = sha256Hex_(logicalMessageId);
// Volanea POST /v1/send request body.
const payload = {
from: from,
to: [to],
subject: subject,
html: html,
text: text
};
const response = UrlFetchApp.fetch('https://api.volanea.com/v1/send', {
method: 'post',
contentType: 'application/json',
headers: {
Authorization: 'Bearer ' + apiKey,
'Idempotency-Key': idempotencyKey
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
const status = response.getResponseCode();
const responseBody = response.getContentText();
if (status < 200 || status >= 300) {
console.error(JSON.stringify({
message: 'Volanea send failed',
status: status,
logicalMessageId: logicalMessageId,
responseBody: responseBody
}));
throw new Error('Volanea API returned HTTP ' + status + ': ' + responseBody);
}
console.log(JSON.stringify({
message: 'Volanea accepted email',
status: status,
logicalMessageId: logicalMessageId,
responseBody: responseBody
}));
}
function requiredValue_(namedValues, fieldName) {
const value = valueOr_(namedValues, fieldName, '');
if (!value) {
throw new Error('Missing required form field: ' + fieldName);
}
return value;
}
function valueOr_(namedValues, fieldName, fallback) {
const values = namedValues[fieldName];
return values && values.length ? String(values[0]) : fallback;
}
function isEmail_(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
function escapeHtml_(value) {
return String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function sha256Hex_(value) {
const bytes = Utilities.computeDigest(
Utilities.DigestAlgorithm.SHA_256,
value,
Utilities.Charset.UTF_8
);
return bytes.map(function(byte) {
const unsigned = byte < 0 ? byte + 256 : byte;
return unsigned.toString(16).padStart(2, '0');
}).join('');
}
The API call uses Authorization: Bearer <secret key>, Content-Type: application/json, and Idempotency-Key. Volanea’s sending documentation identifies an authenticated authorization header, JSON content type, a verified sender, recipient, subject, HTML body, and idempotency header as the core pieces of a REST send. (volanea.com)
The text field provides a usable plain-text alternative. Even when HTML is your primary format, a plain-text version gives recipients and mail clients a simpler fallback and makes the message understandable in tools that strip HTML.
For additional request fields, scheduling, templates, recipient metadata, or other send options, consult the Volanea API reference and setup guides rather than guessing property names. The public API reference covers the complete endpoint surface, while the send endpoint documents the specific message contract. (volanea.com)
Why the field mapping is designed this way
The mapping in the example is intentionally explicit:
| Google Sheets form-submit field | Volanea email field | Purpose |
|---|---|---|
Email Address | to[0] | The recipient address |
First Name | html and text | Greeting personalization |
Request Type | subject, html, and text | Makes the email explain the event |
Reference Number | html and text | Lets the recipient refer to the request |
Script Property VOLANEA_FROM | from | Keeps sender identity out of user-editable spreadsheet data |
Do not allow form respondents to set the from address. The sender should be a verified, controlled identity. Similarly, do not use a respondent-supplied subject line without a purpose and sanitization rules. A controlled subject format, such as We received your demo request, is more predictable for recipients, support teams, and inbox filtering.
Use named mappings for business fields and leave Timestamp out of your message unless you truly need it. Google’s sheet-form event represents each named value as an array. That means namedValues['Email Address'][0] is normal even for a single-answer question; treating it as a string without selecting an element is a common source of accidental output like maya@example.net in one scenario and an array representation in another.
The code also escapes user-supplied values before HTML interpolation. Without escaping, a response such as <img src=x> becomes markup inside your outbound HTML instead of visible text. Escaping does not replace input validation, but it prevents a spreadsheet field from changing the structure of the message body.
Test the workflow before using real recipients
Start with a test recipient you control and a sender verified in Volanea. Submit the Google Form once with a unique reference number, such as TEST-2026-001, then inspect the Apps Script execution record and the received message.
A useful test sequence is:
- Submit a valid response using your own mailbox.
- Confirm that the email arrives once, has the expected subject, and contains the mapped values.
- Submit with a blank optional field and confirm that the email still reads naturally.
- Submit with an invalid address and confirm that the script fails visibly rather than silently sending malformed data.
- Review Executions in Apps Script for the function log and any response body from Volanea.
- Deliberately use a bad API key in a temporary test project to confirm that authentication failures are reported.
- Restore the correct key and submit a new response rather than reusing old test data.
Do not treat a successful HTTP response as proof of inbox placement. It proves the API request was accepted. Delivery is a later process involving recipient validity, mailbox policy, suppression state, sender authentication, and other deliverability factors. Before a large launch, validate imported or manually entered recipient addresses with an email address verification tool, then send a small controlled test set.
When this breaks: failure modes in the Google Sheets-to-email hop
Every automation has two separate reliability questions: did the source event run, and did the remote service receive the intended action? Google Sheets and Volanea are separated by a network request, so your design must account for ambiguous outcomes.
Retries can create duplicate sends
The most important failure mode is a timeout or transient error after Volanea receives the request but before Apps Script receives the response. If the automation retries blindly, the recipient could receive two confirmations.
The script prevents that outcome by deriving one Idempotency-Key from the spreadsheet ID, sheet ID, response row, and message-version string. If the same logical form response is retried, it sends the same key with the same payload. Volanea supports this header specifically for safe retries of the send call. (volanea.com)
Do not generate a fresh random idempotency key on every retry. A fresh key tells the API that this is a new logical send, which defeats duplicate protection. If you change the template materially and intentionally want a second email, change the final version segment—for example, from request-confirmation-v1 to request-confirmation-v2—and make that decision explicit.
A webhook-style timeout is not proof that no email was sent
Apps Script is making an outbound HTTP request, not receiving a provider webhook in this setup, but the same distributed-systems rule applies: a client timeout does not prove the remote server did nothing. The request may have reached Volanea and the response may have been delayed or lost.
That is why the script uses muteHttpExceptions: true to capture non-2xx response content, then throws after logging it. For retryable operational handling, preserve the exact logical message ID and retry with the exact same idempotency key. Do not copy the form row into a new row as a workaround, because that produces a new row number and therefore a new logical email identity.
Fields can be missing, renamed, or unavailable in a different trigger model
The example depends on the spreadsheet On form submit event and its namedValues object. An edit trigger does not have the same event shape. A script execution started manually also does not have a form-submit event. Google documents different event fields for different trigger types, so code written for a form-submit event should not be repurposed as generic sheet-edit code without redesigning its input logic. (developers.google.com)
Form changes can also break mappings. Renaming Email Address to Work Email means namedValues['Email Address'] is no longer populated. Keep question titles stable, or store expected field names in Script Properties and update them through a controlled change process. Required-field checks in the script make this break loud instead of quietly sending an email with missing data.
Google Apps Script quotas and execution limits can stop sends
Apps Script imposes quotas that vary between consumer accounts and Google Workspace accounts. Google currently lists URL Fetch quotas of 20,000 calls per day for consumer accounts and 100,000 for Workspace accounts; trigger runtime limits are also different by account type. Quotas are per user, reset after a rolling 24-hour period, and can change. (developers.google.com)
A one-email-per-response workflow uses one URL Fetch call per response, so it can be appropriate for modest operational volume. It is not a substitute for a queue-based application workflow at high volume. If you expect bursts, campaigns, or more requests than the Apps Script quota allows, use Sheets as data input and move the sending decision into a backend or automation system that can queue work, retain retry state, and control rate.
Trigger ownership can become an operational risk
Installable triggers run as the account that created them. If that person leaves the organization, loses access, or their account is disabled, the automation may become difficult to maintain or fail. Put the Sheet and script under an appropriately managed team account or shared-drive process, document the owner, and test after ownership changes. (developers.google.com)
Choose direct Apps Script or middleware deliberately
Direct Apps Script is the best fit when a form response should create a straightforward transactional message, the workflow has modest volume, and the team can maintain a small script. It has fewer moving parts: no third-party automation account, no separate webhook endpoint, and no native add-on to install.
Use middleware such as an automation platform or a small backend when the process needs any of the following:
- Multi-step approval before sending.
- A state machine such as
Draft,Approved,Sent, andFailed. - A durable retry queue and detailed operational alerting.
- Data enrichment from CRM, billing, inventory, or support systems.
- High-volume sends or burst handling.
- Per-user authorization instead of one trigger owner’s execution identity.
- A reviewable deployment workflow with tests and versioned environments.
A common next step is to make Sheets the intake layer rather than the delivery engine: Apps Script sends the form-submit event to your backend, the backend validates and queues it, and the backend calls Volanea. That architecture adds infrastructure, but it makes retries, audit logs, change control, and concurrent updates easier to manage.
For a simple request acknowledgment, however, adding middleware first can create more operational surface than value. Start with the smallest system that has explicit input validation, protected credentials, idempotency, and observable failures.
Operational practices that keep the integration trustworthy
Treat a spreadsheet-triggered email as production software, even if it begins as an internal workflow. A form confirmation can expose customer information, create support commitments, or become a vector for accidental bulk sending.
First, maintain a small configuration record outside the form responses: sender identity, expected form question titles, message version, integration owner, and test recipient. This makes a future edit safer because the next maintainer can see what the script expects.
Second, separate transactional and promotional purposes. A confirmation that a person submitted a request is transactional. A newsletter, sales announcement, or ongoing promotional sequence has different consent and unsubscribe expectations. Do not turn a form-submit confirmation into a marketing email simply by adding a promotional paragraph.
Third, preserve observability. The script logs the logical message ID, HTTP status, and API response body. Review Apps Script’s Executions view after deployment and set an internal process for handling failed executions. If a send fails because of a missing field, fix the form/schema mismatch rather than repeatedly resubmitting the form.
Finally, rotate credentials when access changes or a key may have been exposed. A leaked email API key can be used to send unwanted messages under your configured account, creating both cost and deliverability consequences. Keep one key per environment where possible, revoke obsolete keys, and update the Script Property rather than editing code.
Conclusion
To send email from Google Sheets without pretending there is a native Sheets marketplace integration, use the capability Google actually provides: Apps Script plus an installable spreadsheet On form submit trigger. The trigger receives the submitted response event, UrlFetchApp makes the authenticated Volanea REST request, and Script Properties keep the API key out of spreadsheet data and source code.
The durable version of this workflow is not just a POST request. It uses named field mappings rather than column positions, a verified sender, escaped HTML, a stable idempotency key, logged API failures, and an owner who can maintain the trigger. Those details turn a convenient spreadsheet automation into a dependable transactional email path.
FAQ
Can Google Sheets call the Volanea API directly?
Yes. A spreadsheet-bound Google Apps Script can make outbound HTTPS calls with UrlFetchApp, and an installable trigger can run that code after a Google Sheets event such as a linked Google Form submission. (developers.google.com)
Does Volanea have a native Google Sheets add-on or Marketplace app?
No. This integration uses Google Apps Script and Volanea’s REST API directly. There is no need to claim or depend on a native Google Sheets app, Marketplace installation, or plug-in flow.
What trigger should I use to send one email for each new response?
Use the spreadsheet installable On form submit trigger when responses come from a Google Form linked to that Sheet. It is more specific and safer than an edit trigger because it responds to the submission event rather than arbitrary worksheet changes.
Where should I store my Volanea API key in Apps Script?
Store it as a Script Property named VOLANEA_API_KEY in Project Settings → Script Properties. Do not put it in a spreadsheet cell, form response, client-visible configuration, or source code. (developers.google.com)
How do I prevent duplicate emails when a request retries?
Send a stable Idempotency-Key for the same logical form response. In this guide, the key is deterministically derived from the spreadsheet ID, sheet ID, response row, and message version, so a retry reuses the same identity instead of creating a second send. (volanea.com)