Send email from QuickBooks without relying on a mailbox rule or manually forwarding invoices. The reliable route is QuickBooks Online webhooks into a secure middleware service, which reads the changed record and sends the resulting transactional message through Volanea.
QuickBooks does not provide a native Volanea app, a QuickBooks Marketplace plugin, or a standard in-product workflow action for making arbitrary outbound HTTP requests to an email API. QuickBooks Online Advanced workflows can create reminders, approvals, notifications, sends, and updates, but they are not a general-purpose HTTP automation builder. For a production integration, build a QuickBooks Online developer app, subscribe it to the relevant webhook event, and let your server call Volanea. (quickbooks.intuit.com)
This guide uses an Invoice Create webhook as the concrete trigger. When QuickBooks Online reports that an Invoice entity was created, your webhook receiver validates the notification, queues it, fetches the current invoice record using the QuickBooks Online Accounting API, and maps its customer and invoice fields into a Volanea email request. The result is useful for sending a branded invoice-created notice, an internal accounting alert, or a customer follow-up that links to your own payment portal.
What the QuickBooks-to-Volanea architecture looks like
The important distinction is that QuickBooks is the system of record and event source. Volanea is the sending service. Your middleware sits between them because the QuickBooks webhook is a notification rather than a fully populated email-ready invoice object.
The flow has five steps:
- A user or connected app creates an Invoice in QuickBooks Online.
- QuickBooks sends an
Invoiceevent with theCreateoperation to the webhook endpoint configured in your Intuit developer app. - Your endpoint validates the
intuit-signatureheader against the raw body, records the event, and returns HTTP 200 quickly. - A background worker uses the stored OAuth connection for that QuickBooks company to retrieve the invoice by ID.
- The worker maps
BillEmail,CustomerRef,DocNumber,DueDate, andTotalAmtinto a VolaneaPOST /v1/sendrequest with a deterministic idempotency key.
QuickBooks describes webhooks as notifications about entity changes delivered to developer-created applications. The notification tells you the entity type, entity ID, operation, realm ID, and last-updated timestamp; it does not contain all invoice fields needed to compose a customer email. (help.developer.intuit.com)
That separation is an advantage. It lets you keep email copy, sender identity, compliance decisions, and retry behavior in infrastructure you control rather than inside an accounting UI. It also prevents a common mistake: treating a webhook payload as a final financial document when it is only a signal that the authoritative record has changed.
The real QuickBooks trigger: Invoice Create
For this integration, configure a QuickBooks Online webhook subscription for the Invoice entity and the Create operation. In the Intuit developer dashboard, webhook settings are configured per app and separately for development and production environments. QuickBooks Online sends notifications only for companies that have connected to and authorized your app through OAuth 2.0. (developer.intuit.com)
The business trigger is therefore specific:
An Invoice record is created in an authorized QuickBooks Online company.
This is not the same as an invoice being paid, viewed, emailed by QuickBooks, or becoming overdue. If your intended message is a payment receipt, subscribe to the appropriate payment-related event and then fetch the payment and linked transaction details. If your intended message is an overdue reminder, evaluate DueDate and Balance from a scheduled worker instead of sending a reminder the moment an invoice is created.
What QuickBooks actually posts
A QuickBooks Online webhook payload is an event envelope. It can contain one or more event notifications, and each notification can contain more than one entity change. A representative Invoice Create notification looks like this:
{
"eventNotifications": [
{
"realmId": "123456789012345",
"dataChangeEvent": {
"entities": [
{
"name": "Invoice",
"id": "145",
"operation": "Create",
"lastUpdated": "2026-10-12T14:22:18Z"
}
]
}
}
]
}
The payload shape above is deliberately small. realmId identifies the QuickBooks company connection. name is the changed entity type, id is the entity identifier, operation indicates the action, and lastUpdated is the event timestamp. QuickBooks documents this legacy event-notification structure and notes that notifications can be aggregated. (help.developer.intuit.com)
Do not expect BillEmail.Address, TotalAmt, DocNumber, line items, or the customer display name inside that notification. Fetch the invoice after receiving the event. This also means that the data used to compose your email should come from the API response you retrieved, not from a value a user may have edited before the webhook arrived.
Choose the right sending pattern before you write code
There are two legitimate ways to send email from QuickBooks with Volanea. Which one you choose depends on whether you own a small backend service.
Pattern one: direct QuickBooks webhook to your middleware
Use this pattern when you can deploy a Node.js, Python, PHP, serverless, or container-based service.
Your service is a custom QuickBooks Online app. It handles OAuth 2.0, receives webhooks, fetches invoice data, and calls Volanea. This is the strongest option for messages that matter because you control signature validation, queueing, deduplication, audit logs, templates, and error handling.
It is also the correct pattern when you need a deterministic relationship between one QuickBooks record and one customer email. The invoice ID becomes part of your idempotency key and event history.
Pattern two: QuickBooks Online to Zapier or Make, then a secure relay
Use a middleware platform when you do not want to build and host the QuickBooks connection yourself. QuickBooks lists a Zapier integration, and Zapier can use QuickBooks triggers such as a new customer or transaction event as the start of a workflow. (quickbooks.intuit.com)
Even in this no-code route, do not put a Volanea secret key into a browser-visible form, public webhook URL, invoice memo, or customer-facing custom field. Prefer a server-side relay that receives only the minimum mapped fields from the automation platform and holds the Volanea secret in its deployment secret store. The relay can validate an inbound shared secret, enforce an allowlist of senders, and create a stable idempotency key.
For high-value invoice communications, the direct webhook pattern is usually better. Zapier or Make may be appropriate for internal notifications or lower-risk operational email, but a purpose-built service gives you clearer observability when an accounting record is edited, replayed, or retried.
Set up the QuickBooks Online side safely
A direct integration begins in the Intuit developer environment, not in a QuickBooks Marketplace installation flow.
Create and authorize the app
Create a QuickBooks Online app in the Intuit Developer Portal and implement the OAuth 2.0 authorization flow. Your user authorizes the app to access a specific QuickBooks Online company, producing tokens tied to that company’s realmId. OAuth is required for API access and for receiving webhook notifications for that connected company. (developer.intuit.com)
Store the QuickBooks access token, refresh token, realm ID, and token expiry server-side in an encrypted database or secrets-backed connection store. Do not treat these as static configuration. QuickBooks access tokens expire after one hour, while refresh tokens have a rolling lifetime and must be replaced with the latest refresh token returned during a refresh. (developer.intuit.com)
Configure the webhook endpoint
In the developer dashboard, open your app’s webhook configuration for the correct environment, enter the public HTTPS endpoint for your listener, and enable the Invoice Create notification. Configure development and production separately; a sandbox webhook setup does not automatically become a production webhook setup. (developer.intuit.com)
Your receiver must preserve the raw request body before JSON parsing because the signature is calculated over the raw payload. QuickBooks instructs developers to compute an HMAC-SHA256 hash using the app-specific verifier token, then compare it to the intuit-signature header. (developer.intuit.com)
Keep Volanea out of QuickBooks configuration
There is no QuickBooks field where a Volanea API key should live. Put VOLANEA_API_KEY in the secret manager or encrypted environment-variable store attached to the webhook service or queue worker. The key is a credential that can send email, so it must never be sent to a browser, stored in invoice custom fields, copied into webhook query parameters, or placed in code committed to source control.
Your Volanea secret key is used with Bearer authentication against https://api.volanea.com/v1/send. Volanea supports secret API keys and an Idempotency-Key header for safe retries. Review the current endpoint contract and authentication guidance in the Volanea API reference and setup guides before deploying. (volanea.com)
Working webhook receiver and Volanea send call
The following Node.js example demonstrates the full important hop: verify the QuickBooks signature, acknowledge quickly, process an Invoice Create event, retrieve the invoice from QuickBooks, map fields, and call Volanea.
The webhook endpoint does not send email inline. It validates and enqueues work. The worker performs the slower API calls. That design matters because QuickBooks requires a successful HTTP 200 acknowledgement within three seconds and retries failed deliveries on a defined schedule. (developer.intuit.com)
import crypto from 'node:crypto';
import express from 'express';
const app = express();
// Preserve the raw bytes for QuickBooks signature validation.
app.use('/webhooks/quickbooks', express.raw({ type: 'application/json' }));
function validQuickBooksSignature(rawBody, signature) {
const expected = crypto
.createHmac('sha256', process.env.QUICKBOOKS_WEBHOOK_VERIFIER_TOKEN)
.update(rawBody)
.digest('base64');
if (!signature) return false;
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
app.post('/webhooks/quickbooks', async (req, res) => {
const signature = req.get('intuit-signature');
if (!validQuickBooksSignature(req.body, signature)) {
return res.status(401).send('invalid signature');
}
const payload = JSON.parse(req.body.toString('utf8'));
for (const notification of payload.eventNotifications ?? []) {
for (const entity of notification.dataChangeEvent?.entities ?? []) {
if (entity.name === 'Invoice' && entity.operation === 'Create') {
await queue.add('send-invoice-created-email', {
realmId: notification.realmId,
invoiceId: entity.id,
lastUpdated: entity.lastUpdated
}, {
jobId: `quickbooks:${notification.realmId}:invoice:${entity.id}:created`
});
}
}
}
// Acknowledge before fetching an invoice or calling an email API.
return res.sendStatus(200);
});
async function sendInvoiceCreatedEmail({ realmId, invoiceId }) {
const accessToken = await getFreshQuickBooksAccessToken(realmId);
const invoiceResponse = await fetch(
`https://quickbooks.api.intuit.com/v3/company/${realmId}/invoice/${invoiceId}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/json'
}
}
);
if (!invoiceResponse.ok) {
throw new Error(`QuickBooks invoice fetch failed: ${invoiceResponse.status}`);
}
const { Invoice: invoice } = await invoiceResponse.json();
const recipient = invoice.BillEmail?.Address;
// Do not send if the invoice does not carry a usable billing email.
if (!recipient || !recipient.includes('@')) {
return { skipped: true, reason: 'invoice_has_no_bill_email' };
}
const customerName = invoice.CustomerRef?.name || 'there';
const invoiceNumber = invoice.DocNumber || invoice.Id;
const dueDate = invoice.DueDate || 'the due date shown on your invoice';
const total = Number(invoice.TotalAmt || 0).toFixed(2);
const text = [
`Hello ${customerName},`,
'',
`Invoice ${invoiceNumber} has been created.`,
`Amount: ${total}`,
`Due date: ${dueDate}`,
'',
'Please use your normal payment process or contact us with questions.'
].join('\n');
const html = `
<p>Hello ${escapeHtml(customerName)},</p>
<p>Invoice <strong>${escapeHtml(String(invoiceNumber))}</strong> has been created.</p>
<ul>
<li>Amount: <strong>${escapeHtml(total)}</strong></li>
<li>Due date: <strong>${escapeHtml(String(dueDate))}</strong></li>
</ul>
<p>Please use your normal payment process or contact us with questions.</p>
`;
const volaneaResponse = await fetch('https://api.volanea.com/v1/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VOLANEA_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': `quickbooks:${realmId}:invoice:${invoiceId}:created`
},
body: JSON.stringify({
from: process.env.EMAIL_FROM,
to: recipient,
subject: `Invoice ${invoiceNumber} has been created`,
text,
html
})
});
if (!volaneaResponse.ok) {
throw new Error(`Volanea send failed: ${volaneaResponse.status}`);
}
return volaneaResponse.json();
}
function escapeHtml(value) {
return String(value)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
The field mapping is explicit:
invoice.BillEmail.Addressbecomes the Volanea recipient into.invoice.CustomerRef.namebecomes the customer greeting.invoice.DocNumberbecomes the displayed invoice number, withinvoice.Idas a fallback.invoice.TotalAmtbecomes the amount in the email body.invoice.DueDatebecomes the due-date text.realmIdandinvoiceIdbecome part of theIdempotency-Key.
QuickBooks invoice records expose billing-email, customer-reference, document-number, total-amount, and due-date concepts, though field availability can depend on how the invoice was created and what data exists in the company file. In particular, BillEmail may be absent even when the customer exists, so it must be treated as optional input rather than an assumption. (developer.intuit.com)
Why queueing and idempotency are non-negotiable
A webhook is not a one-time command. It is an at-least-once delivery signal that can be delayed, retried, or received alongside subsequent updates. If you call Volanea synchronously inside the webhook route, a slow invoice API request or a temporary email API failure can push your acknowledgement beyond QuickBooks’s deadline.
QuickBooks Online expects your listener to return HTTP 200 within three seconds. If it does not receive a successful acknowledgement, it retries after 10, 20, and 30 seconds, then after 5 minutes, 20 minutes, 2 hours, 4 hours, 6 hours, and at six-hour intervals thereafter. It may also stop advancing that company’s event queue until you acknowledge the earlier notification. (developer.intuit.com)
The practical implication is simple: acknowledge the verified webhook, then work asynchronously.
Use two layers of duplicate protection:
- Queue-level deduplication. Use a job identifier such as
quickbooks:{realmId}:invoice:{invoiceId}:createdso repeated incoming notifications do not create multiple active jobs. - Email-level idempotency. Send the same event-specific
Idempotency-Keywith every retry of the Volanea request. A network timeout after the email provider receives your request should not turn into two customer messages.
Do not make the idempotency key too broad. quickbooks:{realmId}:invoice:{invoiceId} would prevent a legitimate future email such as a payment reminder or paid receipt from being sent for the same invoice. Scope it to the intended business event, such as created, payment-received, or overdue-reminder:2026-10-12.
Make the email useful without misrepresenting QuickBooks
An Invoice Create message should say that the invoice was created or is available. It should not say that payment was received, that the invoice was delivered by QuickBooks, or that a payment link is valid unless your system has verified those facts.
Recommended customer message content
For most businesses, an invoice-created notice should include:
- Your verified sender name and reply address.
- The customer-facing invoice number.
- The total and due date, formatted for the company’s currency and locale.
- A clear next step, such as a link to your hosted payment page or an instruction to use the attached or separately delivered invoice.
- A support route for questions.
Avoid including a full line-item breakdown unless there is a concrete business need. Accounting information can be sensitive, and a billing email address is not always a personal inbox controlled only by the intended customer.
Use a payment URL from a trusted system
The QuickBooks webhook example intentionally does not invent a payment link. If you use a payment portal, generate or retrieve its URL from the system that owns payment authorization. Do not synthesize a URL from realmId and invoice ID unless your own application has explicitly designed and secured that route.
For internal alerts, send to a fixed finance or operations mailbox rather than BillEmail.Address. The same webhook and invoice fetch still apply, but the recipient is configuration owned by your business, not a QuickBooks customer field.
Deliverability and sender identity
The email may be technically accepted by an API and still fail to reach the recipient’s inbox. Use a sending domain you control, authenticate it, and send a recognizable operational message from a stable address such as billing@example.com or receipts@example.com.
The EMAIL_FROM value in the example must be an address authorized for your Volanea account and should align with the domain you have configured for sending. Keep invoice notifications separate from bulk promotional messages in both copy and operational process. A customer who receives an invoice notification expects a transactional message, not a marketing campaign with unrelated offers.
Before enabling the automation for every customer, test it with internal recipients and a sandbox QuickBooks company. Confirm that the final rendered email has correct values for names, invoice numbers, totals, due dates, and sender identity. Verify the destination mailbox rather than assuming an API success response equals delivered mail; API acceptance is only the beginning of delivery processing.
If invoice records originate from imports or multiple connected systems, validate the address format before sending. A missing or malformed BillEmail.Address should be a visible skipped event in your logs, not a silent failure and not a reason to substitute an unrelated customer address.
When this breaks
Every reliable integration needs an operating plan for failure modes specific to this hop.
QuickBooks retries create duplicate-send risk
QuickBooks retries if your endpoint does not return HTTP 200 in time. A failure can occur after your worker has already sent an email but before the webhook endpoint acknowledged the event. Without a queue job ID and Volanea idempotency key, the retry can become a second message.
Fix: return HTTP 200 immediately after validation and durable enqueueing. Keep the Idempotency-Key stable for the same Invoice Create event. Record the QuickBooks realm ID, invoice ID, operation, and email provider response in your event log.
The webhook times out
QuickBooks gives the receiver three seconds to respond. Slow database connections, cold starts, fetching the invoice synchronously, or waiting for Volanea can exceed that window. (developer.intuit.com)
Fix: make the receiver’s only responsibilities signature validation, minimal parsing, durable queueing, and acknowledgement. Move OAuth token refreshes, invoice retrieval, template rendering, and Volanea sending to a worker.
The payload is missing customer email or invoice fields
The webhook payload never includes the full invoice. The retrieved invoice may also have no BillEmail.Address, no DocNumber, or no due date when those values were not set by the business process.
Fix: use explicit fallback rules. For example, do not send without a billing email; fall back from DocNumber to the Invoice ID only if that is acceptable customer-facing language; and display a neutral due-date sentence when the date is absent. Send a structured internal alert for records that need human correction.
OAuth tokens expire or access is revoked
Your webhook can arrive successfully while the later invoice fetch returns 401 because the QuickBooks access token has expired. A revoked company connection also invalidates stored tokens. (help.developer.intuit.com)
Fix: refresh access tokens in the worker using the latest stored refresh token. When refresh fails because authorization was revoked or the refresh token is no longer valid, mark the connection unhealthy and require the QuickBooks administrator to reconnect the app. Do not endlessly retry an authorization failure.
Events arrive out of order
QuickBooks notes that notifications can arrive out of sequence, and recommends treating the event timestamp as the source of truth for event timing. (developer.intuit.com)
Fix: store lastUpdated, process a company’s event stream in a controlled order, and avoid using an Invoice Update event to overwrite the intent of an earlier Invoice Create email. For critical synchronization, run Change Data Capture reconciliation from the last successfully processed point, as QuickBooks recommends for missed-event recovery. (developer.intuit.com)
Testing checklist before production
A test plan is more valuable than a successful first request. Use a QuickBooks sandbox company for the webhook and invoice API portion, and use a controlled Volanea sending configuration with mailboxes your team can inspect.
Run these tests:
- Create an invoice with a valid billing email, document number, total, and due date. Confirm one webhook, one queued job, and one email.
- Replay the exact webhook body or enqueue the same event twice. Confirm that only one message is accepted because both the queue job and idempotency key are stable.
- Create an invoice with no billing email. Confirm that the worker records a skipped event and does not send to an invented recipient.
- Simulate a slow email API or worker outage. Confirm that the webhook endpoint still returns HTTP 200 within the required time after persisting the job.
- Force a QuickBooks access-token refresh and confirm the worker can retrieve the invoice after renewal.
- Edit an invoice after creation. Confirm that your chosen behavior is intentional: Invoice Create sends the initial notice, while Invoice Update does nothing unless you have separately designed an update-notice policy.
- Revoke the QuickBooks connection in a controlled environment. Confirm that your alerting identifies the failed connection rather than silently accumulating retries.
Keep test and production configuration separate. QuickBooks exposes separate development and production webhook configurations, so use different endpoint URLs, verifier tokens, OAuth credentials, and Volanea test or production keys as appropriate. (developer.intuit.com)
A practical deployment model
For a small business application, one serverless webhook endpoint and one queue worker are enough. The endpoint receives and verifies QuickBooks notifications. A durable queue stores work. The worker retrieves invoice data, formats the email, and sends through Volanea.
For a multi-company product, model each QuickBooks company as a connection record keyed by realmId. Store encrypted OAuth credentials per connection, a status value, the most recently processed event timestamp, and an audit trail of every outgoing email event. This makes it possible to answer operational questions such as: which invoice generated this email, which connection sent it, which Volanea request was used, and was the event retried?
You can also add policy controls before sending:
- Allow only Invoice Create events from selected companies.
- Send only when
BillEmail.Addressexists and the invoice total is positive. - Restrict messages to approved sender domains.
- Require a known customer ID or payment-status condition.
- Route unusually high-value invoices to manual review.
Those controls turn a simple webhook into a dependable accounting communication workflow rather than an uncontrolled email trigger.
Conclusion
To send email from QuickBooks reliably, do not look for a nonexistent native Volanea plugin or try to expose an email API key inside QuickBooks. Use the integration surface QuickBooks Online actually provides: a developer app, OAuth 2.0, and webhooks.
Subscribe to the Invoice Create event, validate the intuit-signature, acknowledge within three seconds, queue the work, retrieve the invoice by ID, and call Volanea from server-side code with a deterministic idempotency key. That design handles the realities of QuickBooks event delivery, protects your API key, and gives customers one accurate message even when systems retry.
FAQ
Can QuickBooks Online send directly to the Volanea API?
Not as a standard in-product arbitrary HTTP action. QuickBooks Online webhooks send change notifications to a developer-controlled HTTPS endpoint. Your middleware then calls Volanea from the server side.
What QuickBooks event should trigger an invoice email?
Use the Invoice entity with the Create operation when the message should be sent when an invoice record is created. Use a different event or scheduled evaluation for payment receipts, overdue reminders, or update notices.
Does a QuickBooks webhook include the customer email and invoice total?
No. The webhook identifies the changed entity and its ID. Fetch the Invoice through the QuickBooks Online Accounting API, then read optional fields such as BillEmail.Address, TotalAmt, DocNumber, and DueDate.
Where should the Volanea API key be stored?
Store it as a server-side deployment secret, such as VOLANEA_API_KEY, in the middleware service or worker that sends the email. Never store it in client-visible configuration, invoice fields, public webhook URLs, or frontend code.
How do I stop QuickBooks webhook retries from sending duplicate email?
Acknowledge verified webhooks quickly, process them through a deduplicating queue, and use the same event-specific Idempotency-Key whenever you retry the corresponding Volanea send request.