Certificates of insurance arrive as PDF attachments, get saved to a folder, and then nobody looks at them again until something happens. The expiry date was printed on the document the whole time. Reading it is the entire job, and no part of it needs a person.
A certificate is a photograph of coverage on the day it was issued. Policies run twelve months, vendors change carriers, and a roofer who was covered when you onboarded him in March is a different risk in April of the following year. Nobody sends you a notice when it lapses. The carrier notifies the vendor, and the vendor is the one party with no incentive to forward it.
The failure is not that you did not care. The failure is that the information was sitting in a PDF in a folder, and the only trigger to go read it was an event you were trying to prevent.
An uninsured vendor on your property is your general liability policy's problem, your deductible, and in a bad case your carrier asking why you let him on site.
// one row per coverage, not one row per certificate
{
vendor: "Southeast Roofing LLC",
property: "Oakwood",
coverage: "General Liability", // GL | Auto | Umbrella | WC
carrier: "Travelers",
policyNo: "GL-4471902",
eachOcc: 1000000,
aggregate: 2000000,
effective: "2026-03-01",
expires: "2027-03-01",
additionalInsured: true,
waiverOfSubrogation: true
}function vendorStatus(rows, today) {
const soonest = rows
.map(r => new Date(r.expires))
.sort((a, b) => a - b)[0];
const days = Math.floor((soonest - today) / 86400000);
if (days < 0) return { state: "EXPIRED", days };
if (days <= 15) return { state: "CRITICAL", days };
if (days <= 45) return { state: "WARNING", days };
return { state: "OK", days };
}A starting table rather than a standard. Set your own and write down why you picked them.
| Days to expiry | Who hears about it | Why this spacing |
|---|---|---|
| 45 | Vendor only | Long enough that a renewal certificate can be produced without anybody rushing. Most agents turn one around in a week, and the vendor has three chances to forget before it matters. |
| 15 | Vendor and whoever assigns work | The point where you stop trusting that it is handled. Adding the person who dispatches work is what changes behaviour. |
| 0 | You, and a block on new work | No judgment call left. Either a current certificate exists or the vendor does not go on site. |
| Any lapse over 30 days | Review the vendor | A vendor who cannot keep coverage current is telling you something about how the rest of the business is run. |
One expiry date for the whole certificate. The most common build error and the most expensive one. Parse each coverage row. If your extraction returns a single date, it has almost certainly taken the first one it found, and the one that matters is the earliest.
The certificate holder is wrong. A vendor sends the certificate he already had, made out to somebody else's property or to his own company. That document does not name you as certificate holder and often does not carry the additional insured endorsement, which is the part that actually extends coverage to you. Check the holder name against your entity and flag mismatches rather than filing them as current.
A renewal arrives under a different name. Southeast Roofing LLC becomes SE Roofing and Sheet Metal after a reorganization, and your matcher files it as a new vendor with no history while the old record quietly expires. Match on a normalized name plus the policy number stem, and send anything ambiguous to a human rather than creating a second row.
The kit is the finished version of everything above: the scenario blueprint, the ACORD field extraction, the vendor table with one row per coverage, and the thresholds as a spreadsheet you can edit. Email [email protected] if you would rather have it built into what you already run.
One email when a new guide goes up. Nothing else.
No sequence, no upsell drip. Unsubscribe link on every send.