about

A working desk for people who bid on construction work.

BidNext is a publication, not a product. It exists to make the mechanics of tendering — finding the work, qualifying it, pricing it, submitting it, and reviewing the result — less of a guessing game for the people who do it every week.

published byThe EstimateNext team at Softnext Solutions
audienceContractors, estimators & bid managers
coversFind · Qualify · Price · Submit · Win & learn

Why this exists

Most of what's written about construction bidding is either a vendor pitch dressed up as advice, or a procurement textbook written for people who've never stood on a site the week before a submission deadline. Neither is much use at 9pm with a BOQ half-built and a query deadline in the morning.

BidNext is written for the second group — the estimators, bid managers and site-level contractors who actually run the tender process, not just the business-development team that hands it off. The guides describe the real stages of that process and the decisions that go wrong at each one.

Who publishes it

BidNext is published by the team behind EstimateNext, an AI-assisted preconstruction estimating platform, at Softnext Solutions. We spend our working hours building software for the pricing stage of the tender pipeline — this site is where we write about the stages around it. We don't pretend BidNext is software of its own; it's a resource, and the byline is honest about who's behind it.

What we don't do

No manufactured case studies, no invented client logos, no "9 out of 10 contractors agree" statistics. Where a guide states a number, it's a stated industry fact or a plainly labelled example — not a result we made up to sell you something.

82guides mapped across the five tender stages
5stages: find, qualify, price, submit, win & learn
1product this publication funnels to — EstimateNext

Start with the tender lifecycle guide.

Five stages, organised the way a bid actually moves through your team.

Open the guides

', …), // where dollar-quote and dollar-amp are special replace tokens; and it lives in a // JS template literal that eats backslash escapes. So keep this block free of the // dollar sign and of backslash escapes: exact-match CTA texts (no regex end-anchor) // plus a space-only collapse regex. var cnxCtaTexts = ['book a demo','request a demo','get a demo','book demo','schedule a demo','talk to sales','talk to us','talk to the team','get in touch','contact us','contact sales','contact the team','contact']; function cnxIsContactCta(el){ if (el.id && el.id.indexOf('cnx-') === 0) return false; if (el.hasAttribute && el.hasAttribute('data-cnx-contact')) return true; var raw = ((el.getAttribute && el.getAttribute('href')) || '').toLowerCase(); var hh = raw.split('?')[0].split('#')[0]; if (raw === '#contact' || hh === '/contact' || hh === 'contact' || hh === 'contact.html' || (hh.length >= 8 && hh.lastIndexOf('/contact') === hh.length - 8) || (hh.length >= 13 && hh.lastIndexOf('/contact.html') === hh.length - 13)) return true; var t = (el.textContent || '').replace(/ +/g, ' ').trim().toLowerCase(); if (t.length > 24) return false; for (var k = 0; k < cnxCtaTexts.length; k++) { if (t === cnxCtaTexts[k]) return true; } return false; } document.addEventListener('click', function (e) { var el = e.target; while (el && el !== document) { if (el.tagName === 'A' || el.tagName === 'BUTTON') break; el = el.parentNode; } if (!el || el === document) return; if (cnxIsContactCta(el)) { e.preventDefault(); e.stopPropagation(); openModal(); } }, true); if (closeBtn) closeBtn.addEventListener('click', closeModal); if (scrim) scrim.addEventListener('click', function(e){ if(e.target === scrim) closeModal(); }); if (form) form.addEventListener('submit', function(e){ e.preventDefault(); var note = document.getElementById('cnx-note'), err = document.getElementById('cnx-err'), btn = document.getElementById('cnx-submit'); note.style.display='none'; err.style.display='none'; if (form.website_url && form.website_url.value){ note.style.display='block'; return; } // honeypot var payload = { name: form.name.value, email: form.email.value, company: form.company.value, phone: form.phone.value, message: form.message.value, subscribe_newsletter: form.subscribe_newsletter && form.subscribe_newsletter.checked, turnstile_token: form.turnstile_token ? form.turnstile_token.value : '', _token_ts: tsEl ? tsEl.value : tokenTs() }; if (!payload.name || !payload.email || !payload.company || !payload.phone || !payload.message){ err.textContent='Please fill in all required fields.'; err.style.display='block'; return; } if (payload.phone.replace(/[^0-9]/g, '').length < 7){ err.textContent='Please enter a valid phone number.'; err.style.display='block'; return; } btn.disabled = true; btn.textContent = 'Sending…'; post(payload).then(function(d){ if (d && d.success){ form.style.display='none'; note.style.display='block'; } else { err.textContent = (d && d.message) || 'Something went wrong — please try again.'; err.style.display='block'; btn.disabled=false; btn.textContent='Send message'; } }).catch(function(){ err.textContent='Network error — please try again.'; err.style.display='block'; btn.disabled=false; btn.textContent='Send message'; }); }); // --- Progressive enhancement: wire a site's OWN contact form to the CRM --- // Targets forms that look like contact forms (have an email field AND a // textarea/message field), aren't ours, and aren't opted out. Field mapping is // heuristic; company falls back to the entered name so the CRM never rejects. function pick(f, res){ for (var i=0;i