Available for Hire
ZB

Memuat...

Kembali ke Blog
Backend 5 menit baca ยท 977 kata

Mengirim Notifikasi WhatsApp dari Aplikasi Bisnis

Di Indonesia, email sering tidak dibuka tapi WhatsApp hampir pasti dibaca. Catatan soal memilih gateway, mengantre pengiriman, menangani nomor yang tidak valid, dan tidak berubah jadi spam.

#whatsapp #notifikasi #backend #integrasi

Untuk aplikasi bisnis di Indonesia, notifikasi email punya masalah mendasar: banyak yang tidak pernah dibuka. Pelanggan toko, staf gudang, dan pengelola koperasi umumnya tidak memantau inbox. WhatsApp beda cerita, pesannya hampir pasti terbaca dalam hitungan menit.

Konsekuensinya, kebutuhan โ€œkirim notifikasi ke pelangganโ€ di sini hampir selalu berarti WhatsApp, bukan email. Tulisan ini soal membangunnya supaya tidak rapuh.

Dua Jalur, Dua Konsekuensi

WhatsApp Cloud API resmi dari Meta. Legal dan stabil, tapi butuh verifikasi bisnis, nomor khusus yang tidak dipakai di aplikasi WhatsApp biasa, dan template pesan yang harus disetujui lebih dulu. Untuk pesan yang dimulai oleh bisnis di luar jendela 24 jam, hanya template yang boleh dikirim.

Gateway pihak ketiga tidak resmi seperti Fonnte dan sejenisnya. Jauh lebih cepat dipasang, tidak perlu persetujuan template, dan bisa memakai nomor biasa. Konsekuensinya, nomor berisiko diblokir Meta kalau pola pengirimannya dianggap spam, dan layanannya bisa berubah sewaktu-waktu di luar kendali Anda.

Untuk sistem internal dan notifikasi operasional dengan volume wajar, jalur kedua sering cukup dan jauh lebih murah. Untuk aplikasi yang mengirim ke ribuan pelanggan asing, jalur resmi lebih aman dalam jangka panjang.

Apa pun pilihannya, bungkus di balik satu antarmuka sendiri. Suatu hari Anda mungkin pindah penyedia, dan tidak ada yang ingin mencari-cari pemanggilan API yang tersebar di lima puluh berkas.

// src/notifications/whatsapp.js
export interface WhatsAppProvider {
  send(to: string, message: string): Promise<{ id: string }>;
}

// Implementasi Fonnte, Cloud API, atau mock untuk pengujian
// semuanya memenuhi antarmuka yang sama.

Jangan Kirim di Dalam Request

Godaan pertama adalah memanggil API WhatsApp langsung di dalam handler HTTP. Jangan.

Pengiriman bisa memakan beberapa detik, bisa gagal karena jaringan, dan bisa kena rate limit. Kalau itu terjadi di dalam request pembuatan order, pelanggan melihat error padahal ordernya sebenarnya berhasil dibuat.

Pisahkan menjadi dua langkah. Simpan pesan ke basis data dengan status PENDING, lalu biarkan pekerja terpisah yang mengirim.

await prisma.waMessage.create({
  data: {
    to: normalizePhone(customer.phone),
    body: renderTemplate('order_paid', { name: customer.name, id: order.id }),
    status: 'PENDING',
    attempts: 0,
  },
});

Cara ini memberi tiga hal sekaligus: request tetap cepat, pesan tidak hilang kalau proses mati, dan Anda punya riwayat lengkap apa yang pernah dikirim ke siapa.

Normalisasi Nomor Lebih Dulu

Nomor telepon yang masuk dari formulir akan berantakan. Orang menulis 0895..., +62 895..., 62895..., dengan tanda hubung, dengan spasi, kadang dengan huruf O menggantikan angka nol.

Gateway umumnya menuntut format internasional tanpa tanda plus. Normalisasi sekali di satu tempat:

export function normalizePhone(raw) {
  const digits = String(raw).replace(/\D/g, '');

  if (digits.startsWith('62')) return digits;
  if (digits.startsWith('0')) return '62' + digits.slice(1);
  if (digits.startsWith('8')) return '62' + digits;

  return null; // biarkan pemanggil memutuskan
}

Kembalikan null untuk yang tidak dikenali, jangan menebak. Nomor yang salah tebak berarti pesan pelanggan A terkirim ke orang asing, dan itu masalah privasi, bukan sekadar bug.

Simpan versi ternormalisasi di basis data, bukan versi mentahnya. Kalau tidak, Anda akan punya tiga baris pelanggan yang sebenarnya orang yang sama.

Antrean, Percobaan Ulang, dan Batas

Pekerja pengirim perlu tiga perilaku.

Batasi laju. Mengirim lima ratus pesan dalam semenit adalah cara tercepat membuat nomor diblokir. Beri jeda antar pengiriman, dan beri jeda lebih panjang untuk blast.

Coba ulang dengan mundur bertahap. Kegagalan jaringan biasanya sementara. Coba lagi setelah satu menit, lalu lima, lalu tiga puluh. Tapi bedakan jenis kegagalannya.

Berhenti pada kegagalan permanen. Nomor tidak terdaftar di WhatsApp tidak akan berubah dengan mencoba lagi seribu kali. Tandai FAILED dan berhenti.

const PERMANENT = ['invalid_number', 'not_registered', 'blocked'];

async function deliver(message) {
  try {
    const res = await provider.send(message.to, message.body);
    await markSent(message.id, res.id);
  } catch (err) {
    const permanent = PERMANENT.includes(err.code);
    const exhausted = message.attempts + 1 >= 5;

    await prisma.waMessage.update({
      where: { id: message.id },
      data: {
        status: permanent || exhausted ? 'FAILED' : 'PENDING',
        attempts: { increment: 1 },
        lastError: err.code ?? String(err).slice(0, 200),
        nextAttemptAt: permanent ? null : backoff(message.attempts),
      },
    });
  }
}

Menyimpan lastError terlihat sepele, tapi inilah yang membuat Anda bisa menjawab pertanyaan โ€œkenapa Bu Siti tidak dapat notifikasiโ€ tanpa menebak.

Isi Pesan Menentukan Apakah Dibaca

Notifikasi WhatsApp muncul di ruang yang sama dengan percakapan keluarga. Pesan yang terlalu panjang atau terasa seperti robot akan diabaikan, bahkan diblokir.

Beberapa hal yang membuat perbedaan besar:

  • Sebut nama penerima di awal
  • Letakkan informasi terpenting di baris pertama, karena itu yang terlihat di notifikasi tanpa membuka aplikasi
  • Satu pesan satu tujuan, jangan menggabungkan tagihan dan promo
  • Sertakan cara menindaklanjuti, misalnya nomor order atau tautan lacak
  • Tutup dengan identitas bisnis yang jelas

Pesan yang berguna terbaca seperti ini:

Halo Pak Andi, pembayaran order #INV-2291 sudah kami terima.

Total: Rp 450.000
Estimasi kirim: 2 hari kerja
Lacak: https://contoh.id/t/INV-2291

Terima kasih, Toko Sejahtera

Simpan template di basis data atau berkas konfigurasi, jangan tersebar sebagai string di dalam kode. Suatu saat pemilik bisnis akan minta ubah kalimatnya, dan itu tidak seharusnya butuh deploy.

Hormati Hak Menolak

Ini sering dilewatkan di aplikasi internal, padahal penting. Sediakan cara pelanggan berhenti menerima pesan pemasaran, dan patuhi itu.

Pisahkan dua jenis pesan. Transaksional seperti konfirmasi pembayaran atau status pengiriman umumnya diharapkan penerima. Promosional butuh persetujuan dan harus bisa dihentikan.

Satu kolom marketingOptOut pada tabel pelanggan, ditambah pemeriksaan di pekerja pengirim, sudah cukup untuk memulai. Yang penting jangan mencampur keduanya dalam satu saluran, karena satu keluhan spam bisa membuat nomor bisnis diblokir dan semua notifikasi transaksional ikut mati.

Perhatikan Webhook Status

Kebanyakan gateway bisa mengirim balik status pengiriman: terkirim, sampai, dibaca, atau gagal. Manfaatkan itu untuk melengkapi catatan Anda.

Yang paling berguna bukan status โ€œdibacaโ€, melainkan pola kegagalan. Kalau tiba-tiba banyak pesan berstatus gagal dalam waktu berdekatan, kemungkinan besar nomor pengirim sedang bermasalah, dan Anda ingin tahu itu sebelum pelanggan yang memberi tahu.

Penutup

Notifikasi WhatsApp terasa sepele sampai dipakai sungguhan. Yang membuatnya andal bukan API-nya, melainkan hal-hal di sekitarnya: nomor yang dinormalisasi, pesan yang diantrekan, kegagalan yang dibedakan, riwayat yang tersimpan, dan isi pesan yang menghormati ruang pribadi penerimanya.

Bagikan artikel ini:

Suka artikel ini?

0 reactions