Document generator. Training certificates at scale
01. Starting point
The client runs product trainings that end with issuing a certificate to every participant. Documents are produced in three language versions and must include training data, the participant’s company details, a unique number, and an expiry date.
Certificates were prepared by hand in PowerPoint. After each training someone opened a template file, swapped participant data, assigned the next number, exported a PDF, and sent it in a separate message. For a group of a dozen people that meant a dozen repeats of the same sequence of steps.
A process based on manual file editing carried the usual risks. Numbers were assigned from memory or a helper list, so a duplicate or a skipped number depended only on the attention of the person issuing the certificate. Copies of the template started to diverge. There was also no send log: who received a certificate and when lived scattered across an inbox, and a message that never arrived simply disappeared.
02. Solution
We built a dedicated application that covers the full document lifecycle: from template design, through the recipient base, to the generated PDF and confirmed delivery.
The person running trainings prepares a template in an editor with a live preview, enters participants manually or via CSV import, then starts sending for the whole group. The system assigns numbers, generates documents, sends them by email, and stores the result of every operation in history. Work that used to grow linearly with headcount no longer depends on it.
03. Process flow
- Prepare or select a certificate template.
- Enter recipients manually or import a list from a CSV file.
- Start sending for the selected group of participants.
- Check statuses and retry failed deliveries if needed.
One template as the source of truth
The editor preview and the final file are rendered from the same component. A change made in the template is immediately visible on screen and identical in the generated document. There is no separate draft and production version, so the certificate look cannot drift no matter how many documents have already been issued.
The template covers title, subtitle, training name, logo, signatory details, and footer. Labels are prepared in three language versions, and the numbering pattern and validity period are defined per template.
Safe numbering for bulk sending
Numbers are assigned by a counter that runs atomically, so when documents for a whole group are generated at once, no number is duplicated or skipped. The number format is configurable and can include date markers and a sequence. Control over numbering continuity moved from a person to the system.
Recipient import and lists
Participants can be added one by one or uploaded as a CSV through a three-step wizard with column mapping. An imported group is saved as a list, so the next send to the same group is a single click.
Control over delivery
Every document moves through statuses from creation, through generation, to confirmed delivery or error. Failed sends are flagged on the dashboard and can be retried without regenerating the document. There is also a no-send mode in which the system prepares PDF files only, for delivery another way.
Technologies used
- Frontend: React 19, Vite 6, TailwindCSS, TanStack Router and Query, Radix UI, react-hook-form
- Backend: Node 22, Hono 4, TypeScript
- Database: Prisma 6, SQLite
- Document generation: Puppeteer 24 with Chromium, HTML render from React components
- Delivery: Nodemailer, SMTP
- Security: bcrypt, sessions in the database, httpOnly cookie, login attempt limits
- Deployment: Docker, nginx, Let’s Encrypt certificate
The application is maintained as a monorepo where types, validation, and the template component are shared between frontend and backend. One validation definition applies on both sides, which eliminates a class of bugs caused by drifting rules.
04. Results
Issuing certificates for an entire training group comes down to choosing a template, pointing to a participant list, and starting the send. Steps once done separately for each participant now run in bulk, and training admin time no longer depends on group size.
Numbering and document consistency stopped being a matter of someone’s attention. The system assigns numbers, and the certificate look comes from one template, not from yet another file copy.
The history of every issued document lives in one place, with delivery status and the option to resend. A failed send no longer means losing track of a certificate that never reached the participant.
The whole stack runs on light infrastructure: one server, Docker containers, a file-based database. Maintaining the system does not require a large server footprint or dedicated database administration.
