Stel je voor: een potentiele klant vult je aanvraagformulier in op je website. Tien minuten later ontvangt hij een professionele, gepersonaliseerde offerte in zijn inbox. Jij hebt er niets aan hoeven doen. Dat is precies wat je in deze handleiding gaat bouwen.
De workflow die we bouwen werkt als volgt: een prospect vult een Typeform in met zijn aanvraagdetails, n8n pikt het webhook-signaal op, haalt aanvullende klantdata op uit HubSpot, stuurt alles naar Claude die een gepersonaliseerde offertetekst genereert, zet die tekst om naar een PDF via een HTML-to-PDF node, mailt de offerte via SendGrid en slaat een kopie op in Google Drive. Alles binnen vijf tot tien minuten na invullen.
Wat je nodig hebt: n8n (zelf-gehost of cloud), een Claude API-sleutel (Anthropic Console), een HubSpot-account (gratis tier volstaat), een SendGrid-account, en Google Drive API-toegang. Rekening houden met een totale opzettijd van 90 tot 120 minuten bij de eerste run.
Stap 1: Typeform aanvraagformulier aanmaken
Ga naar typeform.com en maak een nieuw formulier aan. Noem het 'Offerteaanvraag'. Voeg de volgende velden toe als Short Text of Multiple Choice, afhankelijk van het type vraag: Voornaam, Achternaam, Bedrijfsnaam, E-mailadres, Telefoonnummer, Type dienst (keuze uit jouw dienstenaanbod), Omschrijving van de opdracht (Long Text), Gewenste startdatum, Geschat budget (optioneel) en Hoe ken je ons (referral tracking).
Ga in Typeform naar 'Connect' en kopieer de Form ID. Die heb je later nodig voor de webhook-configuratie. Activeer ook 'Webhooks' in het tabblad Connect en voeg de n8n webhook-URL in die je in stap 2 aanmaakt. Sla het formulier op en publiceer het.
Stap 2: n8n workflow aanmaken en webhook configureren
Open n8n en maak een nieuwe workflow aan. Noem hem 'Offerte Generator'. Voeg als eerste node een 'Webhook' trigger toe. Stel de HTTP Method in op POST. Kopieer de gegenereerde webhook URL en plak die in Typeform (stap 1). Klik op 'Listen for test event' en vul je Typeform eenmaal in om de testdata door te sturen.
// Voorbeeld van Typeform webhook payload die n8n ontvangt
{
"event_id": "01HXYZ123ABC456DEF789GHI",
"event_type": "form_response",
"form_response": {
"form_id": "JOUW_FORM_ID",
"token": "unique_response_token",
"submitted_at": "2026-08-03T18:30:00Z",
"answers": [
{"field": {"id": "field_01", "ref": "voornaam"}, "type": "text", "text": "Joris"},
{"field": {"id": "field_02", "ref": "achternaam"}, "type": "text", "text": "Vermeer"},
{"field": {"id": "field_03", "ref": "bedrijfsnaam"}, "type": "text", "text": "Vermeer Advies BV"},
{"field": {"id": "field_04", "ref": "email"}, "type": "email", "email": "joris@vermeeradvies.nl"},
{"field": {"id": "field_05", "ref": "dienst"}, "type": "choice", "choice": {"label": "Marketing Automatisering"}},
{"field": {"id": "field_06", "ref": "omschrijving"}, "type": "text", "text": "We willen onze nieuwsbrief en social media automatiseren voor 3 medewerkers."}
]
}
}
Voeg na de webhook een 'Set' node toe om de antwoorden te mappen naar leesbare variabelen. Map elk Typeform-antwoord naar een duidelijke sleutel zoals voornaam, achternaam, email, bedrijfsnaam, dienst en omschrijving. Dit maakt de rest van de workflow leesbaarder.
Stap 3: Klantdata ophalen uit HubSpot CRM
Voeg een 'HTTP Request' node toe. Deze roept de HubSpot Contacts API aan om te controleren of de klant al bekend is in je CRM. Als de klant al bestaat, haal je extra data op zoals eerdere deals, notities en klantgeschiedenis. Als de klant nieuw is, maak je automatisch een nieuw contact aan.
// HubSpot: zoek contact op e-mailadres
// HTTP Request node configuratie:
// Method: GET
// URL: https://api.hubapi.com/crm/v3/objects/contacts/search
// Headers: Authorization: Bearer {{$env.HUBSPOT_API_KEY}}
// Body (JSON):
{
"filterGroups": [
{
"filters": [
{
"propertyName": "email",
"operator": "EQ",
"value": "{{$node['Set'].json['email']}}"
}
]
}
],
"properties": [
"firstname",
"lastname",
"company",
"email",
"phone",
"hs_lead_status",
"num_associated_deals",
"notes_last_contacted"
],
"limit": 1
}
Voeg een 'IF' node toe na de HubSpot zoekactie. Controleer of de response een resultaat bevat (results.length > 0). Als ja: haal de klanthistorie op en stuur die mee naar Claude. Als nee: maak een nieuw HubSpot-contact aan via een POST request naar de Contacts API. In beide gevallen gaat de workflow verder naar stap 4.
Stap 4: Offerte-tekst genereren met Claude
Dit is de kern van de automatisering. Voeg een 'HTTP Request' node toe die de Claude API aanroept. Gebruik het claude-opus-4-5 of claude-sonnet-4-6 model voor de beste kwaliteit. Bouw een dynamische system prompt die jouw offerte-stijl vastlegt, en een user prompt die de klantgegevens en aanvraagdetails invult.
// Claude API request configuratie
// Method: POST
// URL: https://api.anthropic.com/v1/messages
// Headers:
// x-api-key: {{$env.ANTHROPIC_API_KEY}}
// anthropic-version: 2023-06-01
// Content-Type: application/json
{
"model": "claude-opus-4-5",
"max_tokens": 2000,
"system": "Je bent een professionele offerte-schrijver voor [JOUW BEDRIJF]. Schrijf offertes in het Nederlands, zakelijk maar persoonlijk. Gebruik altijd de naam van de klant. Structuur: inleiding, onze aanpak, wat wij leveren, tijdlijn, investering, volgende stap. Geen prijzen invullen, dat doet de medewerker handmatig. Gebruik nooit em-dashes.",
"messages": [
{
"role": "user",
"content": "Schrijf een offerte voor de volgende aanvraag:\n\nKlant: {{$node['Set'].json['voornaam']}} {{$node['Set'].json['achternaam']}}\nBedrijf: {{$node['Set'].json['bedrijfsnaam']}}\nGewenste dienst: {{$node['Set'].json['dienst']}}\nOmschrijving: {{$node['Set'].json['omschrijving']}}\nBestaande klant: {{$node['IF'].json['isBestaandeKlant']}}\nEerdere contactmomenten: {{$node['HubSpot'].json['notes_last_contacted']}}\n\nMaak de offerte warm maar professioneel. Maximaal 600 woorden."
}
]
}
Voeg na de Claude-node een 'Set' node toe om de gegenereerde tekst op te slaan als variabele. Claude geeft de tekst terug in het veld content[0].text. Map dit naar een variabele genaamd offertetekst. Je hebt die variabele nodig in de volgende stap voor de HTML-template.
Stap 5: HTML-offerte bouwen en PDF genereren
Gebruik een 'Code' node in n8n om een HTML-string te bouwen met de offertegegevens. Daarna gebruik je een HTTP Request naar een PDF-generatiedienst zoals html2pdf.app, pdfshift.io of de open-source tool Gotenberg als je zelf-gehost werkt. Die dienst zet jouw HTML om naar een PDF-bestand.
// Code node: bouw HTML-template voor de offerte
const datum = new Date().toLocaleDateString('nl-NL', {day: 'numeric', month: 'long', year: 'numeric'});
const offerteNummer = 'OFF-' + new Date().getFullYear() + '-' + Math.floor(Math.random() * 9000 + 1000);
const html = `
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<style>
body { font-family: 'Helvetica Neue', sans-serif; color: #2d2d2d; margin: 60px; line-height: 1.6; }
.header { display: flex; justify-content: space-between; margin-bottom: 40px; }
.logo { font-size: 24px; font-weight: bold; color: #1a56db; }
.offerte-nr { color: #666; font-size: 14px; }
h1 { font-size: 28px; color: #1a56db; margin-bottom: 8px; }
.meta { color: #666; margin-bottom: 32px; }
.body-text { white-space: pre-wrap; font-size: 15px; }
.footer { margin-top: 60px; border-top: 1px solid #eee; padding-top: 20px; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="header">
<div class="logo">[JOUW BEDRIJFSNAAM]</div>
<div class="offerte-nr">Offerte ${offerteNummer}</div>
</div>
<h1>Offerte voor ${items[0].json.voornaam} ${items[0].json.achternaam}</h1>
<div class="meta">${datum} | ${items[0].json.bedrijfsnaam} | ${items[0].json.email}</div>
<div class="body-text">${items[0].json.offertetekst}</div>
<div class="footer">
[JOUW BEDRIJF] - KvK: XXXXXXXX - BTW: NXXXXXXXXB01
Geldigheid offerte: 30 dagen na datum
</div>
</body>
</html>
`;
return [{json: {html, offerteNummer, datum}}];
Configureer de HTTP Request node voor PDF-generatie. Stuur de HTML-string als POST body naar jouw gekozen PDF-service. De response is een binair PDF-bestand. Sla het op als binary data in n8n onder de naam offerte.pdf. Je hebt dit bestand nodig in stap 6 (e-mail) en stap 7 (Google Drive).
Stap 6: Offerte versturen via SendGrid
Voeg een 'SendGrid' node toe (of gebruik de HTTP Request node als je geen native SendGrid-node hebt). Configureer de volgende velden: From (jouw zakelijk e-mailadres), To (het e-mailadres van de aanvrager), Subject, een persoonlijke HTML-body en de PDF als bijlage.
// SendGrid API v3 - Mail Send
// Method: POST
// URL: https://api.sendgrid.com/v3/mail/send
// Headers: Authorization: Bearer {{$env.SENDGRID_API_KEY}}
{
"from": {"email": "offerte@jouwbedrijf.nl", "name": "[JOUW NAAM] van [BEDRIJF]"},
"to": [{"email": "{{$node['Set'].json['email']}}", "name": "{{$node['Set'].json['voornaam']}} {{$node['Set'].json['achternaam']}}"}],
"subject": "Jouw offerte van [JOUW BEDRIJF] - {{$node['Code'].json['offerteNummer']}}",
"content": [
{
"type": "text/html",
"value": "<p>Beste {{$node['Set'].json['voornaam']}},</p><p>Hartelijk dank voor je aanvraag. Bijgevoegd vind je onze offerte op maat. We kijken uit naar een samenwerking!</p><p>Vragen? Bel of mail ons gerust.</p><p>Met vriendelijke groet,<br>[JOUW NAAM]<br>[JOUW BEDRIJF]</p>"
}
],
"attachments": [
{
"content": "{{$binary.data.toString('base64')}}",
"type": "application/pdf",
"filename": "Offerte-{{$node['Set'].json['bedrijfsnaam']}}-{{$node['Code'].json['offerteNummer']}}.pdf",
"disposition": "attachment"
}
]
}
Test de e-mail eerst met je eigen e-mailadres als ontvanger. Controleer of de PDF correct is bijgevoegd en of de inhoud klopt. Let op dat SendGrid een geverifieerd domein vereist voor hoge deliverability. Zorg dat je domeinnaam geauthenticeerd is via de SendGrid Sender Authentication instellingen.
Stap 7: PDF opslaan in Google Drive
Voeg een 'Google Drive' node toe of gebruik de Google Drive API via HTTP Request. Zorg dat je OAuth2-credentials hebt geconfigureerd in n8n. Maak in Google Drive een overzichtsmap aan genaamd 'Offertes' met een submap per jaar. De node maakt automatisch een bestand aan met de juiste naam.
// Google Drive: upload bestand via n8n Google Drive node
// Operation: Upload
// Drive ID: My Drive
// Folder: /Offertes/2026
// File Name: Offerte-{{$node['Set'].json['bedrijfsnaam']}}-{{$node['Code'].json['offerteNummer']}}.pdf
// Binary Data: true
// Binary Property: data
// Alternatief: Google Drive REST API
// POST https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart
// Authorization: Bearer {{$oauth2.access_token}}
// Metadata part:
{
"name": "Offerte-{{bedrijfsnaam}}-{{offerteNummer}}.pdf",
"mimeType": "application/pdf",
"parents": ["GOOGLE_DRIVE_FOLDER_ID"]
}
Optioneel: voeg na de Google Drive upload een stap toe die de gedeelde link van het bestand terugstuurt. Je kunt deze link opslaan in HubSpot als notitie bij het contactrecord, zodat je team altijd terug kan vinden welke offerte naar welke klant is gestuurd.
Stap 8: Testen, foutafhandeling en live zetten
Voordat je de workflow live zet, doorloop je een volledige testrun. Vul het Typeform in met testdata. Controleer of n8n de webhook ontvangt. Verifieer de HubSpot-lookup of contactcreatie. Bekijk de Claude-output en beoordeel de kwaliteit van de offertetekst. Controleer de PDF-opmaak. Controleer de ontvangen e-mail. Verificeer het Google Drive-bestand.
Voeg foutafhandeling toe aan elke kritieke node. In n8n kun je 'Error Trigger' nodes toevoegen die je een Slack-bericht of e-mail sturen als iets misloopt. Configureer dit in ieder geval voor de Claude API-call en de SendGrid e-mail, want dat zijn de meest kwetsbare stappen.
Let ook op rate limits. Claude API heeft limieten op requests per minuut. Als je veel aanvragen tegelijkertijd verwacht, voeg dan een 'Wait' node toe of gebruik een queue-mechanisme. Voor de meeste MKB-situaties (minder dan 20 aanvragen per dag) is dit niet nodig.
Als alles werkt: zet de workflow op 'Active' in n8n. Je offerte-generator is nu live. Elke nieuwe Typeform-invulling triggert automatisch de volledige flow. Monitor de eerste tien runs handmatig om te controleren of de kwaliteit consistent is.
Veelgestelde vragen en aanpassingen
Vraag: wat als ik geen HubSpot gebruik? Geen probleem. Vervang stap 3 door een opzoekactie in jouw eigen CRM (Salesforce, Pipedrive, Notion, of een eenvoudige Google Sheet). De logica blijft hetzelfde: haal klantcontext op en stuur die mee naar Claude.
Vraag: kan ik de prijzen ook automatisch berekenen? Ja, maar dat vereist een prijstabel in je workflow. Voeg een 'Code' node toe die op basis van de gekozen dienst en het geschatte budget een richtprijs berekent. Claude kan die prijs dan verwerken in de offertetekst. Let op: laat een mens altijd de definitieve prijs goedkeuren voor verzending.
Vraag: hoe houd ik de offertetekst consistent met mijn huisstijl? Geef Claude in de system prompt concrete stijlrichtlijnen mee: jouw tone of voice, verboden woorden, gewenste zinlengte, structuur. Voeg ook een paar voorbeeldoffertes toe als few-shot voorbeelden in de prompt. Hoe specifieker de instructies, hoe consistenter de output.
Met deze workflow bespaar je gemiddeld 30 tot 60 minuten per offerteaanvraag. Bij vijf aanvragen per week is dat al snel 12 tot 24 uur per maand die je terugwint. De klant is sneller geholpen, de kwaliteit is consistent en jij kunt je focussen op de gesprekken die ertoe doen.