Modern Tech Stack
Web & App Development
What gets built
InixCert is a certificate management application developed to support the operations of an IT training and consulting provider in Indonesia. Previously, the certificate issuance process relied on scattered data, spreadsheet-based records, mail merge, local file storage, and several repetitive manual steps.
In this project, I worked as a Junior Web Developer, handling requirement analysis, domain modeling, backend and user interface development, as well as automated testing for the certificate issuance workflow.
The implementation resulted in a centralized workflow that connects participant import, data validation, queue-based certificate generation, PDF rendering, private file storage, and public certificate verification.
My scope of work on InixCert included:
The branch organizing an activity is not always the institution officially issuing the certificate.
Combining these concepts into a single entity could create inconsistencies in credential history and certificate template rules.
To address this, I separated the branch and issuer concepts.
Branches are used for operational context and data access boundaries, while issuers are separate entities representing the official authority responsible for issuing certificates.
Participant names alone are not reliable identifiers for deduplication.
At the same time, the same participant may join multiple activities with different packages, data sources, or certificate numbers.
The system therefore uses a canonical participant identity within each branch, while information such as package details, import source, row number, and certificate number is stored within participation records.
This approach reduces unnecessary duplication while preserving the context of each activity.
Rendering PDF documents with headless Chromium is a relatively resource-intensive process.
Running the entire operation inside a web request would require operators to wait for completion and increase the risk of request timeouts.
For this reason, certificate generation was moved into a background queue.
Each certificate is processed through an individual job, while its progress and processing status are stored in the database.
This allows the application interface to remain responsive without depending on a long-running browser request.
Some activities use automatically generated certificate numbers, while certain training programs use official numbers imported from external sources.
Both mechanisms need to operate without creating duplicate numbers or numbering collisions.
For internally generated numbers, sequence allocation is handled inside database transactions with locking mechanisms. External certificate numbers are validated before certificate records are created.
Jobs are also designed to be idempotent so retrying an operation does not generate a new number for certificates that have already been successfully processed.
Certificates need to be publicly verifiable, but the verification page must not expose internal participant data or private certificate files.
To address this, public verification uses a dedicated public identifier and displays only the minimum information required to confirm certificate validity.
The original files remain stored in private storage and are accessible only to authorized users.
InixCert also supports workshop management for both public and private activities.
Content publication status alone is not sufficient to determine whether a workshop is safe to display publicly.
Public visibility rules are therefore applied at the database query level so corporate batches, private events, or content that is not yet ready for publication cannot appear through indexes, search results, detail pages, or aggregate counts.
The development process started by mapping the existing certificate issuance workflow.
From this analysis, I found that the main problem was not simply PDF generation, but the relationship between:
I separated master data from transactional data so future changes to participant information would not modify the historical data of certificates that had already been issued.
I selected a modular monolith architecture using Laravel to keep delivery straightforward while maintaining clear domain boundaries.
The participant import workflow was separated from the certificate generator.
Participants must already be validated and associated with an activity before an issuance batch can be created.
This approach makes certificate generation more deterministic and prevents the generator from becoming an implicit pathway for creating new participant data.
Batch and certificate states were also defined explicitly.
Automatically generated certificate numbers are allocated using database transactions and locking, while externally sourced numbers are validated before certificate records are created.
Snapshots of recipient and issuer information are stored with each certificate so historical records remain consistent even when master data changes.
CSV files are processed using a streaming approach.
Headers are normalized and each row is validated before the data is committed.
When an email address is available, it can be used to identify an existing participant within the same branch. Participants with identical names but insufficient identifiers are not automatically merged.
When an operator confirms an issuance batch, the system creates one queued job for each certificate.
Each job:
The application interface reads progress directly from the database, allowing processing status to remain available even when the browser page is refreshed.
For public verification, verification codes are directed to dedicated permanent URLs.
These pages display only the required registry information and certificate snapshot, while PDF file locations and internal data remain private.
Implementation validation was performed through unit tests and feature tests.
Test scenarios include:
Laravel Pint and production asset builds were also used as additional quality checks before delivery.
Participants are stored as canonical identities within the context of each branch.
Package and activity-specific information is stored within participation records, allowing the same participant to join multiple programs without creating unnecessary duplicate identities.
The import process provides summaries for:
The certificate generator retrieves participants from stored activity data and applies the required issuance scope.
Operators can select all eligible participants or specific participant groups before confirmation.
Batch and certificate records are created within a transaction, after which each certificate is processed through an independent queue job.
This approach allows failures to be handled at the individual certificate level without causing the entire batch to fail.
Internal certificate numbers are allocated using a locking mechanism.
For activities using external numbering, official source numbers are preserved and checked for duplication.
Certificates also store snapshots of important information, including:
These snapshots preserve historical certificate information even when the associated master data is later updated.
Blade-based print views are rendered into A4 PDF documents using Chromium.
Attendance and course completion templates use official artwork with controlled dynamic data placement.
Generated files are stored in private storage.
Authorized users can access certificates through:
Completed certificates can be verified using a public identifier.
The identifier resolves to a canonical verification URL.
The verification page displays only the minimum information required to confirm certificate validity without exposing private file paths or unnecessary participant information.
Queries, policies, middleware, and relational constraints are used to restrict access according to the user's branch.
Specific roles can be granted explicit cross-branch capabilities when required.
Important actions such as data imports, certificate generation, deletion, and configuration changes are logged to support traceability and auditing.
In addition to credential operations, InixCert includes a Workshop CMS foundation covering:
For communication workflows, the application also provides reusable email templates, controlled personalization tags, HTML sanitization, activity-specific drafts, subscriber lists, and a queue-based mailing foundation.
Certificate issuance previously depended on several disconnected tools and files.
Participant data, certificate numbers, templates, and generated PDF files were not managed within a single traceable lifecycle.
As the number of activities and participants increased, the manual workflow resulted in more repetitive work and a greater risk of data inconsistencies.
I concluded that improving PDF rendering speed alone would not solve the underlying problem.
The system needed to validate data before rendering, maintain relationships between participants and activities, manage certificate numbering, track the status of individual certificates, and provide public verification without exposing private files.
I developed a sequential certificate issuance workflow:
At the application level, I also implemented database transactions, unique constraints, idempotent job behavior, branch-scoped authorization, audit logging, and minimum disclosure principles for public verification.
InixCert provides a centralized certificate issuance workflow connecting participant data, generated PDF files, and public verification pages.
Operators do not need to wait for PDF rendering to complete within the browser request.
Each certificate can be tracked individually, and a failure affecting one certificate does not remove visibility into other items within the same batch.
The underlying data structure also supports multiple issuance contexts, including internal events, training programs with external numbering, workshops, and gradual expansion into multi-branch operations.
The InixCert project originated from a certificate issuance process that involved multiple disconnected files and operational steps.
Participant data was managed through spreadsheet-based documents, certificate generation relied on mail merge, generated files were stored locally, and operators had to perform several repetitive tasks manually.
This process introduced several challenges:
InixCert was developed to create a centralized workflow capable of storing activity and participant data, validating information before certificate issuance, processing certificates asynchronously, and preserving the history of every generated certificate.
The system was also designed to support multiple branches without mixing operational data between units while separating the branch responsible for organizing an activity from the institution officially issuing the credential.
From an architectural perspective, the application uses a modular monolith approach. This architecture was selected to maintain clear domain boundaries without introducing the additional complexity of a distributed system that was not yet necessary.
The implementation focuses not only on generating PDF files, but also on maintaining the integrity of the entire credential lifecycle, from participant data to public certificate verification.