How We Built an AI-Powered Public Tender Alert System in n8n
At AAI Labs, we needed a way to monitor public tenders from Lithuania's procurement portal (http://viesiejipirkimai.lt ) without manually reviewing every email. Here's how we automated it.
The Problem
Tender notification emails arrive in Lithuanian, contain dense procurement language, and often get buried in inboxes. Our team was spending 10-15 minutes per email translating and summarizing before sharing with the team.
The Solution
We built a simple workflow:
[Mailgun Webhook] → [Extract Email] → [ChatGPT] → [Slack]Step-by-Step Guide
Step 1: Set Up Mailgun Route
We configured Mailgun to forward emails to our n8n webhook:
- Go to Mailgun → Receiving → Create Route
- Set **Expression Type **to "Match Recipient"
- Set Recipient to [email protected]
- Enable Store and notify
- Add Webhook URL: https://n8n.k8s.aai-labs.com/webhook/your-webhook-id
- Save the route
Step 2: Create n8n Webhook Node
- In n8n, add a Webhook node
- Set **HTTP Method **to POST
- Copy the webhook URL and paste it in Mailgun (Step 1)
- This node receives incoming emails from Mailgun
Step 3: Add Extract Email Content Node
- Add a Code node after the Webhook
- Paste this code:
const body = $input.first().json.body;
const subject = body.subject || 'No subject';
const sender = body.sender || body.from || 'Unknown sender';
const emailContent = body['stripped-text'] || body['body-plain'] || body['body-html'] || '';
const urlRegex = /(https?:\/\/[^\s<>"]+)/g;
const urls = emailContent.match(urlRegex) || [];
const tenderUrls = urls.filter(url =>
url.includes('viesiejipirkimai.lt') ||
url.includes('cvpp') ||
url.includes('tender')
);
return {
subject: subject,
sender: sender,
emailContent: emailContent,
tenderUrls: tenderUrls.join('\n')
};Step 4: Add ChatGPT Node
- Add an **OpenAI **node after Extract Email Content
- Set** Credential** to your OpenAI account
- Set Resource to "Text"
- Set** Operation** to "Message a Model"
- Set Model to "CHATGPT-4O-LATEST"
- Click Add Message and configure:
- Role: System
- Prompt: You are a procurement assistant helping a team review public tender opportunities from Lithuania's public procurement portal (viesiejipirkimai.lt). Your job is to create clear, scannable summaries of tender notifications.
- Click Add Message again:
- **Role: **User
- Toggle to **Expression **mode
- Prompt:
Summarize this tender notification in a clear, readable format. Extract and present:
• Tender Title - The name/title of the procurement
• Contracting Authority - Who is issuing the tender
• Deadline - Submission deadline (if mentioned)
• Estimated Value - Budget or contract value (if mentioned)
• Category - Type of goods/services being procured
• Brief Description - 2-3 sentences on what's being procured
• Link - Direct link to the tender (if available)
If any information is not available, note it as "Not specified".
---
Email Subject: {{ $json.subject }}
Email Content:
{{ $json.emailContent }}
Detected Tender URLs:
{{ $json.tenderUrls }}Step 5: Add Slack Node
- Add a **Slack **node after ChatGPT
- Set **Credential **to your Slack account
- Set Resource to "Message"
- Set Operation to "Send"
- Select your Channel
- Set **Text **to:
📝 *New Public Tender Alert*{{ $json.message.content }}Step 6: Test & Activate
- Save the workflow
- Send a test email to [email protected]
- Check Slack for the summary
- Toggle the workflow to Active
The Result
Now our team sees this in Slack within seconds:
📝 New Public Tender Alert
• Tender Title: School Catering Services Procurement 2026-2028
• Contracting Authority: Kaunas City Municipality Administration
• Deadline: 2026-03-01 14:00 (Lithuanian time)
• Estimated Value: €2,450,000 (excluding VAT)
• Category: Food/Catering Services
• Brief Description: Kaunas City Municipality seeks a provider
for quality breakfast and lunch services to 45 city schools.
• Link: https://viesiejipirkimai.lt/pirkimas/VP-2026-0089Key Takeaways
•** Let AI do the heavy lifting **— no need for complex parsing logic
• **Always check your data structure **— Mailgun nests data inside body
• Toggle "Expression" mode in n8n when using dynamic variables
•** Save before testing** — we learned this the hard way!
Built with n8n, Mailgun, OpenAI, and Slack at AAI Labs.