Membangun Sistem Tiket Support

Keluhan pelanggan masuk dari WhatsApp, email, DM Instagram, dan kadang telepon. Tidak ada yang tahu siapa sedang menangani apa, keluhan yang sama dijawab dua kali, dan yang lain te...

Membangun Sistem Tiket Support

Keluhan pelanggan masuk dari WhatsApp, email, DM Instagram, dan kadang telepon. Tidak ada yang tahu siapa sedang menangani apa, keluhan yang sama dijawab dua kali, dan yang lain terlewat sampai pelanggan marah. Masalah ini bukan soal tim yang malas, tapi soal tidak adanya satu tempat untuk mencatat dan melacak setiap permintaan.

Sistem tiket menyelesaikan itu: setiap permintaan menjadi satu catatan dengan nomor, status, penanggung jawab, dan tenggat. Artikel ini membahas fitur yang benar-benar dibutuhkan, desain database, potongan kode Laravel 11 untuk bagian intinya, serta kapan lebih masuk akal memakai layanan helpdesk siap pakai.

Fitur Inti yang Wajib Ada

Jangan mulai dari daftar fitur helpdesk besar. Untuk tim 1-10 orang, enam hal ini sudah menutup sebagian besar kebutuhan:

  1. Nomor tiket unik yang mudah disebut, misalnya TKT-2610-0042, agar pelanggan dan tim merujuk hal yang sama.
  2. Status yang jelas: open, in progress, waiting customer, resolved, closed.
  3. Prioritas: low, normal, high, urgent, dengan definisi tertulis.
  4. Penugasan ke satu agen, bukan "tim", supaya ada yang bertanggung jawab.
  5. Percakapan berurutan (thread) termasuk catatan internal yang tidak terlihat pelanggan.
  6. SLA: tenggat respons pertama dan tenggat penyelesaian berdasarkan prioritas.

Fitur seperti basis pengetahuan, rating kepuasan, dan laporan bisa ditambahkan setelah fondasi ini berjalan.

Bangun Sendiri atau Pakai Helpdesk Jadi?

Sebelum menulis satu baris kode, putuskan dulu apakah Anda memang perlu membangun. Perbandingan berikut membantu:

KriteriaHelpdesk SaaS (Zendesk, Freshdesk, Zoho Desk)Open source (osTicket, FreeScout)Bangun di Laravel
Waktu siap pakaiHitungan jam1-2 hari instalasiBeberapa minggu
BiayaLangganan per agen per bulanHosting sajaWaktu developer + hosting
Integrasi dengan data internal (order, member)Lewat API/plugin, terbatasPerlu modifikasiLangsung ke tabel yang sama
Kustomisasi alurSebatas pengaturanBisa, tapi kode orang lainPenuh
Cocok untukTim support murniTim dengan budget minimTiket yang menempel ke produk/akun Anda

Membangun sendiri masuk akal jika tiket harus terhubung langsung dengan data aplikasi, misalnya pelanggan membuka tiket dari halaman pesanan dan agen langsung melihat status pembayaran serta lisensi tanpa pindah sistem.

Sebaliknya, jika tim Anda hanya butuh kotak masuk bersama untuk email dan belum punya aplikasi sendiri, helpdesk SaaS atau FreeScout akan jauh lebih hemat waktu. Waktu developer yang dipakai membangun sistem tiket adalah waktu yang tidak dipakai untuk memperbaiki produk utama.

Desain Database

Tiga tabel sudah cukup untuk versi pertama: tickets, ticket_messages, dan relasi ke users yang sudah ada. Jika Anda belum terbiasa merancang relasi, baca dulu panduan merancang ERD sederhana.

// database/migrations/2026_10_01_000000_create_tickets_tables.php
Schema::create('tickets', function (Blueprint $table) {
    $table->id();
    $table->string('number', 20)->unique();
    $table->foreignId('user_id')->constrained();               // pelanggan
    $table->foreignId('assigned_to')->nullable()->constrained('users');
    $table->string('subject');
    $table->string('category', 50)->nullable();
    $table->enum('priority', ['low', 'normal', 'high', 'urgent'])->default('normal');
    $table->enum('status', ['open', 'in_progress', 'waiting_customer', 'resolved', 'closed'])->default('open');
    $table->timestamp('first_response_due_at')->nullable();
    $table->timestamp('resolution_due_at')->nullable();
    $table->timestamp('first_responded_at')->nullable();
    $table->timestamp('resolved_at')->nullable();
    $table->timestamps();
    $table->index(['status', 'priority']);
});

Schema::create('ticket_messages', function (Blueprint $table) {
    $table->id();
    $table->foreignId('ticket_id')->constrained()->cascadeOnDelete();
    $table->foreignId('user_id')->constrained();
    $table->text('body');
    $table->boolean('is_internal')->default(false);   // catatan antar-agen
    $table->timestamps();
});

Kolom first_responded_at dan resolved_at sengaja dipisah dari updated_at. Tanpa keduanya, Anda tidak bisa mengukur apakah SLA terpenuhi, karena updated_at berubah setiap ada perubahan kecil.

Nomor Tiket dan SLA Otomatis

Nomor tiket dan tenggat SLA sebaiknya dihitung di satu tempat agar konsisten, misalnya pada event creating di model.

// app/Models/Ticket.php
class Ticket extends Model
{
    protected $guarded = [];

    protected $casts = [
        'first_response_due_at' => 'datetime',
        'resolution_due_at'     => 'datetime',
        'first_responded_at'    => 'datetime',
        'resolved_at'           => 'datetime',
    ];

    // jam: [respons pertama, penyelesaian]
    public const SLA_HOURS = [
        'urgent' => [1, 8],
        'high'   => [4, 24],
        'normal' => [8, 72],
        'low'    => [24, 120],
    ];

    protected static function booted(): void
    {
        static::creating(function (Ticket $ticket) {
            $ticket->number ??= 'TKT-' . now()->format('ym') . '-' . strtoupper(Str::random(5));

            [$respond, $resolve] = self::SLA_HOURS[$ticket->priority ?? 'normal'];
            $ticket->first_response_due_at = now()->addHours($respond);
            $ticket->resolution_due_at     = now()->addHours($resolve);
        });
    }

    public function messages()
    {
        return $this->hasMany(TicketMessage::class);
    }
}

Angka SLA di atas hanya contoh. Sesuaikan dengan kapasitas tim Anda yang sebenarnya. SLA yang tidak realistis lebih buruk daripada tidak ada SLA, karena tim akan terbiasa melanggarnya.

Catat respons pertama

Saat agen membalas (bukan catatan internal), isi first_responded_at jika masih kosong dan ubah status menjadi in_progress atau waiting_customer:

if (! $message->is_internal && $message->user_id !== $ticket->user_id) {
    $ticket->first_responded_at ??= now();
    $ticket->status = 'waiting_customer';
    $ticket->save();
}

Notifikasi dan Pengingat SLA

Tiket yang tidak ada yang tahu sama saja dengan pesan WhatsApp yang tenggelam. Minimal kirim tiga jenis notifikasi:

  • Ke pelanggan: tiket diterima (berisi nomor tiket), ada balasan baru, tiket diselesaikan.
  • Ke agen: tiket baru ditugaskan kepadanya, pelanggan membalas.
  • Ke supervisor: tiket mendekati atau melewati tenggat SLA.

Kirim email lewat queue agar halaman tidak lambat; penjelasannya ada di queue dan job di Laravel. Untuk pengingat SLA, jadwalkan pengecekan berkala di routes/console.php:

Schedule::call(function () {
    Ticket::whereIn('status', ['open', 'in_progress'])
        ->whereNull('first_responded_at')
        ->where('first_response_due_at', '<=', now()->addMinutes(30))
        ->each(fn ($t) => Notification::route('mail', config('support.supervisor_email'))
            ->notify(new SlaWarning($t)));
})->everyFifteenMinutes();

Pada implementasi nyata, tambahkan kolom penanda (misalnya sla_warned_at) agar peringatan yang sama tidak terkirim berulang setiap 15 menit.

Kesalahan Umum

  1. Terlalu banyak status. Sepuluh status membuat agen bingung memilih. Lima sudah cukup.
  2. Prioritas dipilih pelanggan. Hampir semua pelanggan akan memilih "urgent". Biarkan pelanggan memilih kategori, lalu agen atau aturan otomatis yang menentukan prioritas.
  3. Tiket tanpa pemilik. Tiket yang ditugaskan ke "tim" cenderung tidak dikerjakan. Pastikan selalu ada satu nama.
  4. Tidak ada otorisasi. Pelanggan A tidak boleh bisa membuka tiket B hanya dengan mengganti ID di URL. Gunakan Policy dan cek user_id; lihat juga best practice keamanan aplikasi web.
  5. Menutup tiket otomatis terlalu cepat. Tiket berstatus "waiting customer" wajar ditutup otomatis setelah beberapa hari tanpa balasan, tapi beri tahu pelanggan sebelumnya.

Basis Pengetahuan untuk Mengurangi Tiket

Setelah sistem berjalan satu-dua bulan, kelompokkan tiket berdasarkan kategori. Pertanyaan yang muncul berulang, misalnya cara reset password atau cara instal aplikasi, layak dijadikan artikel bantuan. Tampilkan artikel yang relevan di form pembuatan tiket berdasarkan kata kunci subjek. Sebagian pelanggan akan menemukan jawabannya sendiri sebelum menekan tombol kirim.

Metrik yang Perlu Dipantau

Begitu tiket tercatat rapi, Anda punya data yang sebelumnya tidak terlihat. Empat angka ini cukup untuk rapat mingguan:

  • Waktu respons pertama: selisih created_at dan first_responded_at. Pakai median, bukan rata-rata, agar satu tiket yang terlupa tidak merusak gambaran.
  • Persentase SLA terpenuhi: jumlah tiket yang direspons sebelum first_response_due_at dibagi total tiket.
  • Tiket terbuka per agen: membantu melihat siapa yang kelebihan beban sebelum kualitas turun.
  • Kategori terbanyak: sinyal bagian produk mana yang paling membingungkan pelanggan.

Contoh: jika kategori "instalasi" mendominasi setiap minggu, perbaikan dokumentasi atau video panduan kemungkinan menurunkan volume tiket lebih banyak daripada menambah agen.

Checklist Peluncuran

  • Definisi prioritas dan angka SLA sudah disepakati tim dan ditulis.
  • Setiap tiket punya nomor unik dan satu penanggung jawab.
  • Catatan internal tidak pernah terkirim ke pelanggan (uji secara manual).
  • Policy mencegah pelanggan melihat tiket orang lain.
  • Email dikirim lewat queue dan worker berjalan di server.
  • Scheduler aktif untuk pengingat SLA.
  • Kanal lama (WhatsApp, email) diberi pesan otomatis yang mengarahkan ke form tiket.

Sistem tiket yang sederhana tapi dipakai konsisten jauh lebih berguna daripada helpdesk lengkap yang diabaikan. Mulai dari fondasi di atas, ukur waktu respons selama sebulan, lalu tambahkan fitur berdasarkan data, bukan dugaan. Pola yang sama kami pakai untuk tiket member di GudangCode.

Ingin langsung mempraktikkannya?

Buat akun gratis untuk mengunduh contoh aplikasi Laravel, menyimpan tutorial favorit, dan mencoba kursus. Gratis, bisa daftar dengan akun Google.

Daftar Gratis Lihat katalog aplikasi
Yudhi
Ditulis oleh
Yudhi
Founder & Lead Developer, GudangCode

Yudhi adalah founder GudangCode dan developer Laravel dengan pengalaman membangun puluhan sistem informasi bisnis siap pakai — mulai dari POS, HRIS, hingga aplikasi manajemen. Ia menulis panduan dan artikel di GudangCode untuk membantu developer Indonesia menjalankan, memahami, dan men-deploy source code Laravel dengan benar.

Laravel PHP MySQL Sistem Informasi Bisnis
Lihat semua artikel Yudhi
Sistem Tiket Support Sistem Informasi
Bacaan Paling Dicari
📚 Free Learning Hub

Learn Coding for Free at DhieCoderWeb

Explore Laravel, PHP, JavaScript tutorials, source code, web development guides, and practical programming tips.

DhieCoderWeb
100+
Tutorials
Free
Learning
SEO
Tips
Visit Dhiecoderweb.com →