# Alternatif EmailJS yang menjauhkan key dari client

> Pindahkan formulir kontak dari EmailJS: mengapa key email di sisi browser berisiko, apa yang berubah di kode Anda, dan apa yang Anda lepaskan, seperti balasan otomatis.

Terbit 2026-10-25 · https://ikiform.com/id/guides/send-form-to-email/emailjs-alternative

EmailJS mengirim email dari browser, jadi service id, template id, dan public key Anda ikut terkirim di JavaScript halaman untuk disalin siapa saja. Form backend tidak butuh key di browser: formulir mengirim ke sebuah URL, lalu backend menyaring spam, menyimpan pesan, dan mengirimi Anda email. Anda kehilangan balasan otomatis EmailJS ke pengunjung, yang bisa Anda kirim dari alat email Anda sendiri.

## Apa yang berbeda di sini

EmailJS populer karena alasan yang bagus: ia mengirim email sungguhan dari situs statis tanpa server. Untuk itu, browser memanggil EmailJS langsung dengan tiga nilai: service id, template id, dan public key. Ketiganya ada di bundle Anda, bisa dibaca di source halaman atau tab network.

FAQ EmailJS sendiri menyatakan bahwa orang yang menyalinnya hanya bisa mengirim template Anda dengan konten Anda, bukan email sembarangan. Itu benar untuk teks tetap di template. Tetapi pemanggil yang mengisi variabel template, jadi key yang disalin bisa mengirim template Anda dengan nama, pesan, dan link apa pun, sesering yang diizinkan kuota Anda. Dan jika **To** pada template berupa variabel, seperti pada balasan otomatis ke alamat pengunjung, pemanggil juga yang memilih penerimanya.

Diagram: Siapa yang bisa mengirim lewat key email di browser, dan lewat endpoint formulir

1. Dengan SDK email di browser, halaman memegang semua yang dibutuhkan untuk mengirim. Skrip di tempat lain bisa menyalin ID dan key lalu mengirim template Anda dengan teksnya sendiri, hingga batas kuota Anda.
2. Jika penerima template berasal dari variabel, key yang disalin juga menentukan siapa yang menerimanya.
3. Dengan endpoint formulir, halaman hanya memegang ID formulir. Kiriman disaring dari spam dan dibatasi rate limit, dan email hanya dikirim ke penerima yang Anda atur di dashboard.

ID formulir juga bukan rahasia. Bedanya adalah apa yang bisa dilakukan orang asing dengannya: mengirim formulir Anda, bukan mengirim email Anda.

EmailJS punya pengaturan yang mempersempit ini, seperti membatasi domain mana yang boleh memakai key Anda dan menambahkan captcha ke template. Cek mana yang termasuk dalam paket Anda dan nyalakan jika Anda tetap memakainya. Perbandingan di bawah ini soal desain, bukan vonis atas layanannya:

|   | EmailJS | Ikiform |
| --- | --- | --- |
| Yang dipegang browser | Service id, template id, dan public key | ID formulir |
| Siapa yang menentukan penerima | Template, yang bisa membaca variabel | Hanya pengaturan dashboard Anda |
| Penyaringan spam | Captcha per template, jika Anda menambahkannya | Honeypot, skor spam, dan rate limit di setiap formulir, ditambah pemeriksaan manusia opsional |
| Pesan Anda setelahnya | Di inbox Anda | Di dashboard, ekspor CSV, dan API, selain di inbox Anda |
| Email ke pengunjung | Ya, dengan template balasan otomatis | Tidak |
| Desain email Anda sendiri | Ya, template di editor mereka | Tidak, tata letak tetap dengan subjek `New submission: <form name>` |

Jika balasan otomatis atau email bermerek adalah tujuannya, EmailJS, atau server Anda sendiri dengan API email, lebih cocok. Jika Anda ingin pesan masuk ke inbox dengan aman dan tersimpan di suatu tempat, form backend lebih cocok.

## Pindahkan formulirnya

Formulir EmailJS yang umum di React, berdasarkan tutorial formulir kontak mereka:

`ContactForm.tsx, sebelum`:

```tsx
import emailjs from "@emailjs/browser";
import { type FormEvent, useRef, useState } from "react";

export function ContactForm() {
  const form = useRef<HTMLFormElement>(null);
  const [status, setStatus] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (!form.current) return;
    try {
      await emailjs.sendForm("service_abc123", "template_xyz789", form.current, {
        publicKey: "aBcDeFgHiJkLmNoPq",
      });
      setStatus("Message sent.");
    } catch {
      setStatus("Couldn't send your message.");
    }
  }

  return (
    <form ref={form} onSubmit={onSubmit}>
      <input name="user_name" required />
      <input name="user_email" type="email" required />
      <textarea name="message" required />
      <button>Send</button>
      <p role="status">{status}</p>
    </form>
  );
}
```

Formulir yang sama yang mengirim ke Ikiform dengan `submit()` dari `@ikiform/sdk`:

`ContactForm.tsx, sesudah`:

```tsx
import { submit } from "@ikiform/sdk";
import { type FormEvent, useState } from "react";

const FORM_ID = "frm_xxxxxxxxxxxxxxxx";

export function ContactForm() {
  const [status, setStatus] = useState<"idle" | "sending" | "sent">("idle");
  const [fields, setFields] = useState<Record<string, string[]>>({});
  const [error, setError] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setStatus("sending");
    setFields({});
    setError("");
    const result = await submit(FORM_ID, new FormData(event.currentTarget));
    if (result.ok) {
      setStatus("sent");
      return;
    }
    setFields(result.error.fields ?? {});
    setError(result.error.code === "network_error" ? "You're offline. Try again." : result.error.message);
    setStatus("idle");
  }

  if (status === "sent") return <p role="status">Message sent. We'll reply by email.</p>;

  return (
    <form onSubmit={onSubmit}>
      <label htmlFor="user_name">Name</label>
      <input id="user_name" name="user_name" required />
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />
      {fields.email ? <p role="alert">{fields.email[0]}</p> : null}
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" required />
      {fields.message ? <p role="alert">{fields.message[0]}</p> : null}
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {error && !Object.keys(fields).length ? <p role="alert">{error}</p> : null}
      <button disabled={status === "sending"}>{status === "sending" ? "Sending" : "Send"}</button>
    </form>
  );
}
```

- `@emailjs/browser` dan ketiga ID dihapus. Uninstall package-nya, dan hapus template atau service EmailJS setelah formulir baru berfungsi, agar salinan halaman yang ter-cache berhenti mengirim.
- `user_email` menjadi `email`. EmailJS tidak peduli pada nama, tetapi Ikiform memakai field bernama `email` sebagai reply-to. Nama lain boleh tetap, karena notifikasi mencantumkan field sesuai urutan datangnya.
- `submit()` tidak pernah melempar error. Fungsi ini mengembalikan envelope error, sehingga error field dari definisi formulir tampil di bawah input-nya.
- Honeypot-nya baru. EmailJS tidak punya; input tersembunyi ini menangkap bot paling sederhana tanpa setup.
- Tambahkan domain Anda di **Allowed domains**, agar kiriman dari situs lain ditandai sebagai spam.

> Memakai pemeriksaan manusia? Atur **Spam check** formulir ke **Ikiform**, lalu panggil `challenge(FORM_ID)` sebelum `submit` dan teruskan token-nya sebagai `turnstileToken`. [Panduan React](https://ikiform.com/id/docs/guides/react.md) menunjukkan caranya.

## Jika Anda memakai balasan otomatis

Ikiform tidak mengirim email ke orang yang mengisi formulir. Email notifikasi hanya dikirim ke Anda dan tim, jadi tidak ada autoresponder, email konfirmasi, atau double opt-in. Kirim semua itu dari alat email Anda, dipicu oleh [webhook](https://ikiform.com/id/docs/webhooks.md). Layanan pengiriman yang Anda kendalikan dari server, seperti Resend atau Postmark, bisa mengirimnya di setiap webhook `submission.created`, dengan alamat pengunjung dari payload tepercaya, bukan dari browser. [Panduan beberapa penerima](https://ikiform.com/id/guides/send-form-to-email/multiple-recipients.md#route) punya handler yang bisa Anda sesuaikan.

Tanyakan apakah Anda membutuhkannya. Pesan terima kasih di halaman formulir kontak sudah memberi tahu pengunjung bahwa kirimannya berhasil, dan balasan sungguhan dari Anda sejam kemudian lebih baik daripada template.

## Pastikan berfungsi

1. Di **Allowed domains** formulir, tambahkan domain situs Anda, ditambah `localhost` selama pengembangan. Biarkan kosong untuk menerima kiriman dari mana saja.
2. Cari `emailjs` dan service id lama Anda di situs hasil build. Tidak boleh ada yang cocok.
3. Isi formulir di halaman Anda lalu kirim. Anda mendarat di halaman terima kasih Anda, atau halaman hosted milik Ikiform.
4. Cek inbox setiap penerima notifikasi. Masing-masing mendapat email berisi field, dan **Reply** terkirim ke pengirim formulir jika formulir punya field `email`.
5. Kirim dengan honeypot terisi, `_gotcha=bot`. Kiriman tetap mendapat jawaban sukses, tetapi disimpan sebagai spam dan tidak memicu email atau webhook.

## Jika ada yang salah

| Masalah | Yang harus dilakukan |
| --- | --- |
| Reply masih terkirim ke Ikiform | Field-nya masih `user_email`. Ganti namanya menjadi `email`, atau salah satu dari `email`, `email_address`, `your_email`, `reply_to`, `contact_email`. |
| `422 validation_failed` | Kiriman tidak cocok dengan definisi field formulir. `error.fields` menyebutkan setiap field dan alasannya. Samakan `name` input dengan ID field. |
| `403 challenge_required` | Spam check formulir diatur ke **Ikiform** dan kiriman `fetch` tidak membawa token. Ambil token dengan `challenge(formId)` dari `@ikiform/sdk` sebelum setiap pengiriman, atau matikan pemeriksaannya. |
| `403 origin_not_allowed`, atau setiap kiriman masuk ke spam | Domain halaman tidak ada di **Allowed domains** formulir. Tambahkan, ditambah `localhost` untuk uji lokal. Subdomain dari domain yang terdaftar diizinkan. |

## Pertanyaan

### Apakah Ikiform lebih murah dari EmailJS?

Tergantung volume dan paket, dan keduanya berubah dari waktu ke waktu, jadi bandingkan halaman harga terbaru. Ikiform tidak punya paket gratis; tagihannya per kiriman, dengan Pro atau paket sekali bayar.

### Apakah aman mengekspos public key EmailJS?

EmailJS menyatakan key yang disalin hanya bisa mengirim template Anda, bukan email kustom. Namun pemanggil tetap mengisi variabel template, dan jika penerimanya berupa variabel, mereka juga yang memilihnya. Batasi key ke domain Anda dan tambahkan captcha jika Anda tetap memakainya.

### Apakah Ikiform butuh API key di browser?

Tidak. Formulir di browser mengirim ke URL formulir Anda tanpa key. Secret key hanya ada untuk mengirim dari server Anda sendiri, dan tempatnya di environment variable sisi server.

### Bisakah Ikiform mengirim email ke orang yang mengisi formulir?

Tidak. Ikiform mengirim email ke Anda dan tim, bukan ke pengirim formulir, jadi tidak ada autoresponder atau email konfirmasi. Kirim webhook ke alat email Anda dan kirim konfirmasinya dari sana.

### Bisakah saya mempertahankan nama field EmailJS saya?

Bisa, kecuali field email: beri nama email agar balasan notifikasi terkirim ke pengunjung. Ikiform menyimpan nama field apa pun yang Anda kirim.

## Panduan lainnya

- [Kirim formulir ke email Anda](https://ikiform.com/id/guides/send-form-to-email.md): Terima setiap kiriman formulir di inbox tanpa mail server: mengapa mailto: dan mail() PHP gagal, dan setup yang menyaring spam serta memungkinkan Anda membalas.
- [Next.js](https://ikiform.com/id/guides/send-form-to-email/nextjs.md): Dua cara mengirim email dari formulir Next.js dengan Server Action: kirim sendiri dengan Resend atau Nodemailer, atau teruskan ke form backend, dibandingkan secara jujur.
- [Balas dari inbox Anda](https://ikiform.com/id/guides/send-form-to-email/reply-to.md): Buat Reply di notifikasi formulir membalas ke orang yang mengisi formulir: nama field yang berfungsi, kapan memakai _replyto, dan mengapa balasan bisa nyasar.
- [Tanpa PHP](https://ikiform.com/id/guides/send-form-to-email/without-php.md): Ganti skrip contact.php yang memanggil mail() dengan formulir yang mengirim ke endpoint hosted, dengan nama field, halaman terima kasih, dan balasan tetap sama.
- [Ke Gmail](https://ikiform.com/id/guides/send-form-to-email/gmail.md): Terima kiriman formulir di Gmail tanpa link mailto:, app password, atau Apps Script, lalu beri label, jauhkan dari spam, dan balas dari Gmail.
- [Beberapa penerima](https://ikiform.com/id/guides/send-form-to-email/multiple-recipients.md): Kirim email dari satu formulir ke seluruh tim, atau arahkan setiap pesan ke orang yang tepat berdasarkan topik, tanpa membiarkan formulir sendiri memilih siapa yang menerima email.
