A GL and auto package renews on the first. Forty-one holders hold a certificate that expires with it. Some of them have a contract that says the certificate must arrive before the expiration date, some will email on day three, and a few will not notice until the general contractor locks the crew out of the site.
In most agencies this is handled twice: once badly in advance, from a spreadsheet somebody maintains, and once reactively, as the holders email in and the CSR fills each request from scratch. The first pass misses holders. The second pass is the four expensive minutes, forty-one times, in the first week of the month.
This tutorial builds the batch. The agent watches for renewed policies in the AMS, rebuilds the holder list for each one, drafts a fresh ACORD 25 per holder, and queues the whole batch for one CSR to review in a single pass. It does not send. A licensed person approves, and the send happens after that approval, the same as every other certificate workflow we build.
What you need before you start
- Read access to policies, the certificate history, and account documents in Applied Epic, HawkSoft, or EZLynx.
- A working single-certificate path already: given an account, a policy, and a holder, you can produce an ACORD 25 and queue it. If you do not have that yet, build it first.
- Somewhere to store a holder registry. A small table is enough.
- An approval queue that a CSR already looks at.
The new work here is the registry and the trigger. The document generation is the part you already have.
Step 1: build the holder registry from certificate history, not from a spreadsheet
The question "who holds a certificate on this policy" has no clean answer in any of the three systems. What you have is history: every certificate issued off that account, with a holder name, an address, a description of operations, and whatever endorsement boxes were checked at the time.
So derive the registry. For each commercial account, pull every certificate issued in the last 18 months and normalize each one into a row:
holder_key hash(normalized name + normalized address line 1 + zip)
account_id AMS account
policies[] which policies appeared on the cert (GL, auto, umbrella, WC)
last_issued_at
requirements additional insured, waiver of subrogation, primary and
non-contributory, 30-day notice, per-project aggregate
evidence_doc_id the contract or request that established those requirements
source_cert_ids[]
Normalize before you hash. Uppercase, strip punctuation, collapse STREET/ST, SUITE/STE, drop ITS OFFICERS, AGENTS AND EMPLOYEES and the other trailing boilerplate into a separate field. "ABC Construction Co., Inc." and "ABC CONSTRUCTION CO INC" are one holder. "ABC Construction, 400 Main St" and "ABC Construction, 1200 Industrial Pkwy" are two, and you must keep them two, because the addresses go on different certificates and sometimes belong to different projects.
Expect the merge to be imperfect. Give every registry row a confidence score and let anything below your threshold land as a separate row rather than silently merging two holders. A duplicate certificate is an annoyance. A merged holder that drops one party's additional insured status is an E&O claim.
Step 2: decide who is still a holder
Not every historical holder should get a renewal certificate. A job that closed in 2024 does not need a 2026 certificate, and blasting stale holders is how an agency ends up certifying coverage to somebody it no longer has a contractual reason to certify.
Classify each registry row into three buckets with rules, not with the model:
- Active. Issued in the last 12 months, or issued in the last 18 with a related open activity, or the account is flagged as a standing requirement (landlord, lender, franchisor, primary GC).
- Dormant. Last issued 12 to 18 months ago, no recent activity. Do not auto-draft. Put it on a separate list for the account manager: "still a holder?"
- Closed. Beyond 18 months, or the account has an explicit end date on the job. Exclude.
The rule set will be wrong for some agencies. Make the windows configuration, not code, and expect to change them after the first month of real batches.
Step 3: trigger off the AMS, not off a calendar
A calendar job that fires 15 days before expiration drafts certificates against a policy nobody has bound yet, with last term's limits. Trigger on the renewal actually landing in the AMS instead:
- Applied Epic: poll the policy list for the account and watch for a policy whose effective date equals the prior policy's expiration date and whose status has moved to bound or issued. Epic's activity log will usually carry the renewal activity too; use the policy record as truth and the activity as a hint.
- HawkSoft: the Partner API exposes policies per client; compare terms on a schedule and detect the new term the same way.
- EZLynx: work from the policy list on the applicant record and the renewal task.
Then hold. Do not draft the instant the term appears. Wait until three facts are stable: limits, the named insured, and the endorsement forms attached to the new term. In practice that means re-reading the policy 24 hours later and only proceeding if nothing changed. Certificates drafted off a half-entered renewal are worse than late certificates, because they look finished.
If the endorsements are not on the new term yet, that is not a blocker to draft, it is a blocker to send. Draft the certificate, flag it endorsements unverified, and let the CSR decide.
Step 4: re-check requirements against the new term
A holder's requirements were satisfied by last term's policy. That does not carry forward. Run each registry row's requirements against the renewed policy, the same check you run on a one-off request:
- Additional insured: is the blanket AI endorsement still attached, and does its wording still reach this holder (written contract required, ongoing versus completed operations)?
- Waiver of subrogation: attached on GL, auto, WC separately. Carriers drop WC waivers on renewal more often than you would like.
- Primary and non-contributory: present as an endorsement form, not as a sentence somebody typed in the description box last year.
- Limits: does the renewed limit still meet the number in the contract on file?
Every row comes out of this as satisfied, not satisfied, or cannot determine. Only satisfied gets a clean draft. Not satisfied gets a draft plus a note naming the missing endorsement and the holder's requirement, so the CSR can go to the carrier before the certificate goes out. Cannot determine goes to a human, always. We wrote the mechanics of that check up in checking an additional insured request against the policy.
An agent that quietly downgrades a requirement to make the batch look clean is the single worst failure mode in this workflow. Make not satisfied loud and make it common in your test data.
Step 5: batch the approval, not the send
Forty-one drafts should not become forty-one approval tasks. Group them:
- One approval item per policy renewal, containing the full batch.
- Inside it, three sections: clean drafts, drafts with an unmet requirement, and rows that need a human decision on whether the holder is still a holder.
- Clean drafts collapsed by default, with the diff against last term's certificate shown per row: limits changed, form number changed, nothing changed.
- Approve-all available for the clean section, per-row approval elsewhere.
A CSR working a well-built batch of forty-one moves through it in under 20 minutes, because 35 of them say "nothing changed but the dates." That is the whole economic argument for this workflow, and it disappears if you make them click forty-one times.
After approval, send on a queue with a rate limit and write each send back to the AMS as its own activity against the account and the policy, with the holder name, the form, and the approving user. Use the idempotency key pattern from writing agent output back to the AMS without duplicates, keyed on policy_term_id + holder_key. Renewal batches get re-run. The second run must produce nothing.
Step 6: measure it on real batches
Run this silently for one renewal cycle before anyone approves anything. For each policy that renewed, have the account manager tell you who they actually certified, and compare:
- Holders the agent found that the human missed.
- Holders the human sent to that the agent classified dormant or closed.
- Requirement checks the agent got wrong in either direction.
- Inbound holder requests in the 30 days after renewal, which is the number that should fall.
That last one is the metric a principal cares about. If the batch works, the reactive certificate requests in the first two weeks of the month drop, and they drop in a way you can count in the activity log.
What this does not do
It does not decide that a holder no longer needs a certificate. It does not add an endorsement, contact the carrier, or interpret a contract it has not been given. It does not send anything before a licensed person approves the batch. And it will not fix a book where the certificate history is thin, because a registry derived from three certificates is a registry with three holders in it.
If you want to see this run against your own book, tell us which AMS you are on and roughly how many certificates you issue a month.