Aimpress_site/email-api/index.mjs
Vadym Samoilenko 43b95c84df Add markdown rendering in chat widget + opportunities for new leads
- Chat messages now render bold, italic, links, and line breaks
- Bare URLs auto-linked, XSS-safe via HTML escaping before markdown
- New leads create Opportunity with stage NEW in Twenty CRM
- Applied to both chatbot-api and email-api (contact + quote forms)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:30:49 +00:00

173 lines
6.6 KiB
JavaScript

import express from 'express';
const app = express();
app.use(express.json());
const TWENTY_CRM_URL = process.env.TWENTY_CRM_URL || 'https://crm.ai-impress.com';
const TWENTY_CRM_API_KEY = process.env.TWENTY_CRM_API_KEY || '';
async function createLeadInCRM({ fullName, workEmail, companyName, jobTitle, phoneNumber, need, source }) {
if (!TWENTY_CRM_API_KEY || !workEmail) return;
const headers = { Authorization: `Bearer ${TWENTY_CRM_API_KEY}`, 'Content-Type': 'application/json' };
try {
// Split name
const parts = fullName.trim().split(/\s+/);
const firstName = parts[0] || '';
const lastName = parts.slice(1).join(' ') || '';
// Create or find company
let companyId = null;
if (companyName) {
const compRes = await fetch(`${TWENTY_CRM_URL}/rest/companies`, { headers, method: 'GET' });
// Search not reliable, just create
const createComp = await fetch(`${TWENTY_CRM_URL}/rest/companies`, {
method: 'POST', headers, body: JSON.stringify({ name: companyName }),
});
if (createComp.status === 201) {
const cd = await createComp.json();
companyId = cd.data?.createCompany?.id;
}
}
// Create person
const personBody = {
name: { firstName, lastName },
emails: { primaryEmail: workEmail, additionalEmails: [] },
};
if (companyId) personBody.companyId = companyId;
if (jobTitle) personBody.jobTitle = jobTitle;
if (phoneNumber) personBody.phones = {
primaryPhoneNumber: phoneNumber, primaryPhoneCountryCode: '', primaryPhoneCallingCode: '', additionalPhones: [],
};
const personRes = await fetch(`${TWENTY_CRM_URL}/rest/people`, {
method: 'POST', headers, body: JSON.stringify(personBody),
});
if (personRes.status === 201) {
const pd = await personRes.json();
const personId = pd.data?.createPerson?.id;
// Create note
if (personId && need) {
const noteRes = await fetch(`${TWENTY_CRM_URL}/rest/notes`, {
method: 'POST', headers, body: JSON.stringify({ title: `${source}: ${need}` }),
});
if (noteRes.status === 201) {
const nd = await noteRes.json();
const noteId = nd.data?.createNote?.id;
if (noteId) {
await fetch(`${TWENTY_CRM_URL}/rest/noteTargets`, {
method: 'POST', headers, body: JSON.stringify({ noteId, personId }),
});
}
}
}
console.log(`CRM lead created: ${personId} (${fullName})`);
// Create opportunity in NEW stage
const oppBody = { name: `${fullName}${(need || 'Contact form').substring(0, 50)}`, stage: 'NEW' };
if (companyId) oppBody.companyId = companyId;
if (personId) oppBody.pointOfContactId = personId;
await fetch(`${TWENTY_CRM_URL}/rest/opportunities`, {
method: 'POST', headers, body: JSON.stringify(oppBody),
});
}
} catch (err) {
console.error('CRM error:', err.message);
}
}
app.post('/api/contact', async (req, res) => {
const { fullName, workEmail, companyName, jobTitle, automationNeed, phoneNumber } = req.body;
if (!fullName || !workEmail) {
return res.status(400).json({ error: 'Missing required fields' });
}
try {
const response = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'AImpress Website <noreply@ai-impress.com>',
to: ['hello@ai-impress.com'],
subject: `New lead: ${fullName}${companyName || 'N/A'}`,
html: `
<h2>New Contact Form Submission</h2>
<p><strong>Name:</strong> ${fullName}</p>
<p><strong>Email:</strong> ${workEmail}</p>
<p><strong>Company:</strong> ${companyName || 'N/A'}</p>
<p><strong>Job Title:</strong> ${jobTitle || 'N/A'}</p>
<p><strong>Automation Need:</strong> ${automationNeed || 'N/A'}</p>
<p><strong>Phone:</strong> ${phoneNumber || 'N/A'}</p>
`,
}),
});
if (!response.ok) {
const err = await response.text();
console.error('Resend error:', err);
return res.status(502).json({ error: 'Email delivery failed' });
}
// Create lead in CRM (async, don't block response)
createLeadInCRM({ fullName, workEmail, companyName, jobTitle, phoneNumber, need: automationNeed, source: 'Contact form' });
res.json({ ok: true });
} catch (err) {
console.error('Email error:', err);
res.status(500).json({ error: 'Internal error' });
}
});
app.post('/api/quote', async (req, res) => {
const { fullName, workEmail, companyName, jobTitle, phoneNumber, service, projectDescription } = req.body;
if (!fullName || !workEmail || !service || !projectDescription) {
return res.status(400).json({ error: 'Missing required fields' });
}
try {
const response = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'AImpress Website <noreply@ai-impress.com>',
to: ['hello@ai-impress.com'],
subject: `Quote request: ${fullName}${service}`,
html: `
<h2>New Quote Request</h2>
<p><strong>Name:</strong> ${fullName}</p>
<p><strong>Email:</strong> ${workEmail}</p>
<p><strong>Company:</strong> ${companyName || 'N/A'}</p>
<p><strong>Job Title:</strong> ${jobTitle || 'N/A'}</p>
<p><strong>Phone:</strong> ${phoneNumber || 'N/A'}</p>
<hr>
<p><strong>Service:</strong> ${service}</p>
<p><strong>Project Description:</strong></p>
<p>${projectDescription.replace(/\n/g, '<br>')}</p>
`,
}),
});
if (!response.ok) {
const err = await response.text();
console.error('Resend error:', err);
return res.status(502).json({ error: 'Email delivery failed' });
}
// Create lead in CRM (async, don't block response)
createLeadInCRM({ fullName, workEmail, companyName, jobTitle, phoneNumber, need: `${service}: ${projectDescription}`, source: 'Quote request' });
res.json({ ok: true });
} catch (err) {
console.error('Email error:', err);
res.status(500).json({ error: 'Internal error' });
}
});
app.listen(3001, () => console.log('Email API listening on :3001'));