At Quicko, we use short links for almost everything: marketing campaigns, emails and advertisements. Managing these links involves more than shortening URLs. We need to route visitors to the right destination, track visits, update links as campaigns change, and use QR codes for these links.
The tools we tried didn't give our marketing team the freedom to manage this entire process on their own. Even routine tasks required help from the tech team, making a process that should take minutes much longer than it needed to.
We wanted to change that. So, we built Linq, a self-hosted link manager that lets our marketing team instantly create and track short links independently.
Why we built it
We previously used Shlink, an open-source, self-hosted URL shortener. It worked well for a while, but two limitations became harder to work around as our usage grew.
First, Shlink's access controls didn't give us the flexibility we needed. API keys effectively had admin-level access, so allowing the marketing team to create links also meant granting access to administrative actions that could affect the entire instance. Routine link creation therefore depended on DevOps.
Second, analytics became a reliability concern. Over two years, our links recorded more than 10 million visits. Loading analytics across that volume could crash the instance and put redirects at risk.
We evaluated alternatives such as YOURLS and Kutt, but none met our combination of requirements: granular permissions, analytics that could handle our traffic, and control over the full link-management workflow.
Those gaps led us to build Linq.
Tech Stack
Linq is packaged as a single Docker image containing the API, redirect handler and web interface. It uses PostgreSQL as its database.
The backend uses Bun and Hono to handle API requests and redirects. We use Kysely for type-safe SQL queries and database migrations, without the overhead of a full ORM. The frontend uses Next.js for the web interface and Tailwind CSS for styling.
Redirect lookups are cached in memory by default. Redis is also supported as an optional, configurable cache.
Linq also integrates with Caddy to automate custom-domain setup. Adding a domain in Linq configures its route in Caddy, which handles HTTPS certificate issuance and renewal without requiring manual configuration changes.

Figure 1. Linq architecture: a single Bun container backed by PostgreSQL, with Redis and Caddy as optional services.
Access and permissions
We chose API keys as the primary way to grant access to Linq, rather than introducing individual user accounts, login flows and session management. This provides a lightweight way to manage access in a self-hosted setup, while still allowing different levels of access through roles assigned to each key.
We use CASL to define permissions by role and enforce them consistently across the API and interface. The same rules determine which operations a key can perform and which actions are available in the interface, keeping permission logic in one place.
Linq has three roles: Viewer for read-only access, Editor for creating and updating links, and Admin for managing domains, keys and destructive operations.
Links belong to the team rather than the key used to create them, so revoking a key doesn't affect existing links.
Our marketing team uses an Editor key to manage links independently, without access to domain configuration or administrative operations.

Figure 2. Every request is checked against the permissions on its key.
Managing links
To create a short link, enter the destination URL, choose a domain, and add a custom slug or let Linq generate one. You can also add tags, set an expiry date, and choose whether query parameters should be forwarded to the destination.
A link can route visitors to different destinations based on their platform (Android, iOS or desktop) or query parameters. Linq evaluates rules in order and redirects to the first match. If no rule matches, it uses the default destination.
Each link can have a QR code with custom patterns and background colors. The QR code encodes the short URL, so you can change the destination without reprinting it.
Links that are no longer needed can be archived. This stops redirects while retaining the slug and visit history. Only admins can permanently delete archived links.
What happens on a click
When someone opens a short link, Linq checks its cache for the domain and slug. On a cache miss, it looks up the link and its routing rules in PostgreSQL, then caches the result.
Lookups for nonexistent slugs are cached too, so repeated requests for invalid URLs don't hit the database every time.
Linq evaluates the rules in order and forwards query parameters if the link is configured to do so. If the slug doesn't exist, or the link is archived or expired, the visitor is sent to the domain's fallback URL.

Figure 3. What happens when someone opens a short link.
Tracking visits
Linq records each visit in a visits table, including the visitor's platform, operating system, browser, referrer and the destination they were redirected to.
It also maintains a daily aggregate table that groups visit counts by day and these dimensions. The analytics view queries this table instead of scanning individual visit records and recalculating totals for every request.
That keeps analytics responsive even for links with millions of visits.

Figure 4. The analytics view.
Adoption at Quicko
We've migrated around 100 links and 4 domains from Shlink. The team now uses Linq for email campaigns and Tax Q&A digests.
Creating a link previously took 30 minutes or more when the process depended on DevOps. With Linq, the team can do it in about 5 minutes.
What's next
We're also working on a few additions to Linq:
Link trees: Hosted pages that bring multiple links together, similar to a link-in-bio page.
OpenTelemetry: A standard way to emit visit data, store it in our database, and optionally forward it to other tools.
MCP server: Let AI agents create and manage links directly through Linq.
The MCP server is the addition we're most excited about. An agent drafting a campaign email could create a short link, configure its routing rules, add tags, and insert it into the draft. It would use the same API-key permissions as Linq, keeping the agent's access limited to its assigned role.






