
Launching ShotTech: How Carboit Built a Production-Ready SaaS to Showcase Our Technical Excellence

Building ShotTech: When Your Software Consultancy Becomes Its Own Best Client
At Carboit, we've always believed that the best way to understand our clients' challenges is to face them ourselves. That's why we built ShotTech – not just as another SaaS product, but as a living demonstration of what modern, scalable software architecture looks like when done right.
The Journey: From Internal Need to Public Service
It started with a simple frustration. While developing client applications, we repeatedly needed reliable screenshot capabilities for documentation, testing, and monitoring. Existing solutions were either too expensive, too slow, or too limited. As a boutique software consultancy in Austin, we decided to solve this problem properly – and in doing so, create a showcase of our technical capabilities.
ShotTech's clean, intuitive dashboard showcases our approach to user experience design
Why Build a SaaS Product?
Building ShotTech wasn't about pivoting our business model. It was about:
- Demonstrating expertise through real-world implementation
- Understanding the full lifecycle of SaaS operations
- Creating a technical reference for future client projects
- Contributing to the developer community through open source
User Experience: Where Backend Architecture Meets Frontend Design
One of the key lessons from building ShotTech was understanding how clean architecture enables great user experiences. Our hexagonal backend architecture allows for rapid feature development, which translates to a polished frontend.
Real-time screenshot gallery with instant status updates and detailed metadata
Technical Deep-Dive: The Architecture Behind ShotTech
The Kotlin/Ktor Foundation
We chose Kotlin with the Ktor framework for our backend – a decision that reflects our commitment to modern, efficient technologies. Our open-source backend repository showcases enterprise-grade patterns and practices.
Hexagonal Architecture in Practice
Our implementation follows true hexagonal architecture principles with clear separation between domain, application, and infrastructure layers. Here's a real example from our codebase:
Use Case (Application Layer):
kotlin1// TakeScreenshotUseCase.kt - Pure business logic 2class TakeScreenshotUseCase( 3 private val screenshotRepository: ScreenshotRepository, 4 private val queueRepository: QueueRepository, 5 private val tokenGenerationPort: TokenGenerationPort? = null 6) { 7 suspend operator fun invoke(request: Request): Response { 8 val jobId = "job_${System.currentTimeMillis()}_${UUID.randomUUID().toString().take(8)}" 9 10 // Pure domain logic - no framework dependencies 11 val job = ScreenshotJob( 12 id = jobId, 13 userId = request.userId, 14 apiKeyId = request.apiKeyId, 15 status = ScreenshotStatus.QUEUED, 16 request = request.screenshotRequest, 17 webhookUrl = request.webhookUrl, 18 createdAt = Clock.System.now() 19 ) 20 21 // Use ports (interfaces) not concrete implementations 22 screenshotRepository.save(job) 23 queueRepository.enqueue(job) 24 val secureToken = tokenGenerationPort?.generateToken(job) 25 26 return Response(jobId, ScreenshotStatus.QUEUED, "30-60s", queueRepository.size().toInt()) 27 } 28}
Domain Entity (Domain Layer):
kotlin1// ScreenshotRequest.kt - Rich domain model with validation 2data class ScreenshotRequest( 3 val url: String, 4 val width: Int = 1920, 5 val height: Int = 1080, 6 val fullPage: Boolean = false, 7 val format: ScreenshotFormat = ScreenshotFormat.PNG 8) { 9 init { 10 require(url.isNotBlank()) { "URL cannot be blank" } 11 require(width in 320..1920) { "Width must be between 320 and 1920" } 12 require(height in 240..1080) { "Height must be between 240 and 1080" } 13 } 14 15 fun isValidUrl(): Boolean = try { 16 val uri = java.net.URI(url) 17 uri.scheme in listOf("http", "https") && uri.host != null 18 } catch (e: Exception) { 19 false 20 } 21}
Port Interface (Domain Layer):
kotlin1// StorageOutputPort.kt - Contract for external dependencies 2interface StorageOutputPort { 3 suspend fun upload(data: ByteArray, filename: String): String 4 suspend fun uploadFromFile(filePath: String, filename: String): String 5 suspend fun delete(filename: String): Boolean 6 suspend fun getPresignedUrl(filename: String, expirationMinutes: Int = 60): String 7}
Adapter Implementation (Infrastructure Layer):
kotlin1// S3StorageAdapter.kt - Concrete implementation 2class S3StorageAdapter( 3 private val s3Client: S3Client, 4 private val bucketName: String, 5 private val region: String 6) : StorageOutputPort { 7 8 override suspend fun upload(data: ByteArray, filename: String): String { 9 return try { 10 val key = generateKey(filename) 11 val request = PutObjectRequest { 12 bucket = bucketName 13 this.key = key 14 body = ByteStream.fromBytes(data) 15 } 16 s3Client.putObject(request) 17 getUrl(key) 18 } catch (e: Exception) { 19 throw UploadFailedException(filename, e) 20 } 21 } 22}
This architecture ensures:
- Domain logic isolation - Business rules stay in the domain layer
- Testability - Easy to mock ports for unit testing
- Flexibility - Swap S3 for local storage without changing use cases
- Scalability - Add new features without touching existing code
Performance Optimizations
Achieving sub-2-second response times required several optimizations:
- Browser pool management for instant page rendering
- Edge server deployment for global low latency
- Intelligent caching strategies
- Async processing for non-blocking operations
Open Source Strategy: Building Community While Protecting IP
We made a strategic decision: open-source the backend, keep the frontend proprietary. This approach:
- Builds trust by showing our code quality
- Attracts contributors who improve the platform
- Demonstrates expertise to potential clients
- Protects business value through UI/UX differentiation
The screenshot-api repository has already attracted developers who've contributed improvements, validated our architecture decisions, and even become Carboit clients.
Market Positioning: Enterprise-Grade at Indie Prices
ShotTech competes with established players by offering:
| Feature | ShotTech | Competitors |
|---|---|---|
| Response Time | 1.2s average | 3-5s average |
| Pricing | From $14.99/mo | From $49/mo |
| Open Source | ✅ Backend | ❌ Proprietary |
| SLA | 99.9% uptime | 99.5% typical |
| Formats | PNG, JPEG, WebP, PDF | Limited options |
Our pricing strategy reflects our consultancy roots – we understand budget constraints while demanding enterprise reliability.
Developer Experience: API Management Done Right
Building ShotTech taught us that great architecture is meaningless without great developer experience. Our API key management system showcases security best practices while maintaining usability.
Secure API key management with granular permissions and usage tracking
Analytics and Monitoring: Data-Driven Decisions
Understanding usage patterns and performance metrics is crucial for any SaaS. Our analytics dashboard provides real-time insights that we use to optimize the service continuously.
Comprehensive analytics showing API usage, response times, and success rates
Client Value: Lessons That Transfer
Building ShotTech taught us invaluable lessons we now apply to every client project:
1. Real-World Scalability
Running a production SaaS with customers like Vercel and Stripe means we've solved real scaling challenges – not theoretical ones.
2. Cost Optimization
We've learned to balance performance with infrastructure costs, knowledge we pass to clients building their own platforms.
3. Developer Experience
Creating an API developers love taught us the importance of documentation, SDKs, and responsive support.
4. Monitoring & Reliability
Our 99.9% uptime SLA forced us to implement robust monitoring – practices we now standard in all client deployments.
Webhook Integration: Real-Time Notifications
Modern applications require real-time communication. ShotTech's webhook system demonstrates our understanding of event-driven architecture and reliable message delivery.
Robust webhook configuration with delivery tracking and automatic retry mechanisms
The Technical Stack That Powers Innovation
yaml1Backend: 2 Language: Kotlin 3 Framework: Ktor 4 Architecture: Hexagonal/Clean 5 Database: PostgreSQL with Redis caching 6 7Infrastructure: 8 Deployment: Kubernetes on multi-region clusters 9 CDN: Global edge network 10 Monitoring: Prometheus + Grafana 11 CI/CD: GitHub Actions + ArgoCD 12 13Features: 14 - RESTful API with OpenAPI documentation 15 - Webhook notifications 16 - Batch processing capabilities 17 - OCR text extraction 18 - Mobile device simulation 19 - Video capture (beta)
Future Roadmap: Continuous Innovation
ShotTech's development continues with:
- Advanced OCR Integration – Extract text from any webpage
- Team Collaboration Features – Shared projects and annotations
- Enhanced Batch Processing – Process thousands of URLs efficiently
- AI-Powered Analysis – Automatic visual regression detection
- GraphQL API – For more flexible data queries
Each feature we add to ShotTech becomes expertise we bring to client projects.
The Carboit Difference: We Build What We Run
When you partner with Carboit, you're working with a team that:
- Operates production systems serving real customers
- Understands the full stack from code to deployment
- Practices what we preach about architecture and scalability
- Contributes to open source and the developer community
ShotTech isn't just our product – it's our proof of capability.
Ready to Build Your Next SaaS?
Whether you need:
- A complete SaaS platform from scratch
- API development with enterprise-grade reliability
- Architecture consulting for scalability
- Technical leadership for your development team
We bring the same expertise that powers ShotTech to your project.
Explore ShotTech to see our technical capabilities in action, or contact Carboit to discuss how we can help build your next software venture.
At Carboit, we don't just build software – we run it, scale it, and perfect it. ShotTech is our commitment to excellence made visible.