# 키를 클라이언트에 두지 않는 EmailJS 대안

> 문의 폼을 EmailJS에서 옮기세요. 브라우저 측 이메일 키가 위험한 이유, 코드에서 바뀌는 점, 자동 응답처럼 포기하는 기능을 다룹니다.

2026-10-25 게시 · https://ikiform.com/ko/guides/send-form-to-email/emailjs-alternative

EmailJS는 브라우저에서 이메일을 보내므로 서비스 ID, 템플릿 ID, 퍼블릭 키가 페이지의 JavaScript에 담겨 누구나 복사할 수 있습니다. 폼 백엔드는 브라우저에 키가 필요 없습니다. 폼이 URL로 POST하면 백엔드가 스팸을 거르고 메시지를 저장한 뒤 이메일을 보냅니다. 방문자에게 보내는 EmailJS의 자동 응답은 잃게 되며, 이는 대신 여러분의 이메일 도구에서 보내면 됩니다.

## 이 경우가 다른 점

EmailJS가 인기 있는 데는 이유가 있습니다. 서버 없이 정적 사이트에서 실제 이메일을 보냅니다. 이를 위해 브라우저가 서비스 ID, 템플릿 ID, 퍼블릭 키라는 세 값으로 EmailJS를 직접 호출합니다. 이 값들은 번들에 들어 있어 페이지 소스나 네트워크 탭에서 읽을 수 있습니다.

EmailJS의 FAQ에 따르면 이 값을 복사한 사람은 임의의 이메일이 아니라 여러분의 템플릿과 콘텐츠만 보낼 수 있습니다. 템플릿의 고정 텍스트에 대해서는 맞는 말입니다. 하지만 템플릿 변수는 호출하는 쪽이 채우므로, 복사한 키로 이름, 메시지, 링크를 마음대로 넣은 템플릿을 할당량이 허락하는 만큼 보낼 수 있습니다. 그리고 방문자 주소로 보내는 자동 응답처럼 템플릿의 **To**가 변수라면 수신자까지 호출하는 쪽이 고릅니다.

다이어그램: 브라우저 이메일 키와 폼 엔드포인트로 각각 누가 보낼 수 있는가

1. 브라우저 이메일 SDK를 쓰면 보내는 데 필요한 모든 것이 페이지에 있습니다. 다른 곳의 스크립트가 ID와 키를 복사해 자기 텍스트로 여러분의 템플릿을 할당량까지 보낼 수 있습니다.
2. 템플릿의 수신자가 변수에서 온다면 복사한 키로 받을 사람까지 고를 수 있습니다.
3. 폼 엔드포인트를 쓰면 페이지에는 폼 ID만 있습니다. POST는 스팸 필터링과 레이트 리밋을 거치고, 이메일은 대시보드에서 설정한 수신자에게만 갑니다.

폼 ID도 비밀은 아닙니다. 차이는 모르는 사람이 그걸로 할 수 있는 일입니다. 여러분의 폼을 제출할 수는 있어도 여러분의 이메일을 보낼 수는 없습니다.

EmailJS에는 키를 쓸 수 있는 도메인을 제한하거나 템플릿에 캡차를 추가하는 등 이를 좁히는 설정이 있습니다. 계속 쓴다면 플랜에 어떤 설정이 포함되는지 확인하고 켜 두세요. 아래 비교는 설계에 관한 것이지, 서비스에 대한 평가가 아닙니다.

|   | EmailJS | Ikiform |
| --- | --- | --- |
| 브라우저가 가진 것 | 서비스 ID, 템플릿 ID, 퍼블릭 키 | 폼 ID |
| 수신자를 정하는 쪽 | 템플릿(변수를 읽을 수 있음) | 대시보드 설정만 |
| 스팸 필터링 | 추가했다면 템플릿별 캡차 | 모든 폼에 허니팟, 스팸 점수, 레이트 리밋, 그리고 선택적 사람 확인 |
| 이후 메시지 보관 | 받은편지함 | 받은편지함은 물론 대시보드, CSV 내보내기, API |
| 방문자에게 보내는 이메일 | 예, 자동 응답 템플릿으로 | 아니요 |
| 직접 디자인한 이메일 | 예, 해당 서비스 편집기의 템플릿 | 아니요, 제목이 `New submission: <form name>`인 고정 레이아웃 |

자동 응답이나 브랜딩된 이메일이 핵심이라면 EmailJS나 이메일 API를 쓰는 자체 서버가 더 잘 맞습니다. 메시지를 안전하게 받은편지함으로 받고 어딘가에 보관하고 싶다면 폼 백엔드가 맞습니다.

## 폼 옮기기

EmailJS의 문의 폼 튜토리얼을 바탕으로 한 전형적인 React용 EmailJS 폼입니다.

`ContactForm.tsx, 변경 전`:

```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>
  );
}
```

같은 폼이 `@ikiform/sdk`의 `submit()`으로 Ikiform에 POST하는 모습입니다.

`ContactForm.tsx, 변경 후`:

```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`와 세 ID는 사라집니다. 패키지를 제거하고, 새 폼이 동작하면 EmailJS 템플릿이나 서비스를 삭제해서 캐시된 페이지 사본이 더 이상 보내지 못하게 하세요.
- `user_email`은 `email`이 됩니다. EmailJS는 이름을 신경 쓰지 않지만 Ikiform은 `email`이라는 필드를 회신 주소로 씁니다. 알림은 필드를 도착한 그대로 나열하므로 다른 이름은 그대로 둬도 됩니다.
- `submit()`은 절대 예외를 던지지 않습니다. 오류 객체를 반환하므로, 폼 정의에서 나온 필드 오류가 해당 입력 아래에 표시됩니다.
- 허니팟이 새로 추가되었습니다. EmailJS에는 없었고, 이 숨김 입력은 설정 없이 가장 단순한 봇을 잡아냅니다.
- 다른 사이트에서 온 POST가 스팸으로 표시되도록 **Allowed domains**에 도메인을 추가하세요.

> 사람 확인을 쓰나요? 폼의 **Spam check**를 **Ikiform**으로 설정하고, `submit` 전에 `challenge(FORM_ID)`를 호출해 토큰을 `turnstileToken`으로 넘기세요. [React 가이드](https://ikiform.com/ko/docs/guides/react.md)에서 보여 줍니다.

## 자동 응답을 쓰고 있었다면

Ikiform은 폼을 작성한 사람에게 이메일을 보내지 않습니다. 알림 이메일은 여러분과 팀에게만 가므로 자동 응답, 확인 이메일, 더블 옵트인은 없습니다. 이런 메일은 [웹훅](https://ikiform.com/ko/docs/webhooks.md)으로 트리거해 이메일 도구에서 보내세요. Resend나 Postmark처럼 서버에서 제어하는 발송 서비스가 `submission.created` 웹훅마다 보낼 수 있으며, 방문자의 주소는 브라우저가 아니라 신뢰할 수 있는 페이로드에서 가져옵니다. 응용할 수 있는 핸들러는 [여러 수신자 가이드](https://ikiform.com/ko/guides/send-form-to-email/multiple-recipients.md#route)에 있습니다.

정말 필요한지 생각해 보세요. 문의 폼은 페이지의 감사 메시지로 제출이 됐다는 걸 방문자에게 알릴 수 있고, 한 시간 뒤 여러분이 직접 보내는 답장이 템플릿보다 낫습니다.

## 동작 확인하기

1. 폼의 **Allowed domains**에 사이트 도메인을 추가하고, 개발하는 동안에는 `localhost`도 추가하세요. 비워 두면 어디서 온 POST든 받습니다.
2. 빌드된 사이트에서 `emailjs`와 예전 서비스 ID를 검색하세요. 아무것도 나오지 않아야 합니다.
3. 페이지에서 폼을 작성해 제출하세요. 여러분의 감사 페이지나 Ikiform의 호스팅 감사 페이지로 이동합니다.
4. 각 알림 수신자의 받은편지함을 확인하세요. 모두 필드가 담긴 이메일을 받고, 폼에 `email` 필드가 있으면 **Reply**는 제출자에게 갑니다.
5. 허니팟을 채워서 `_gotcha=bot`으로 POST하세요. 성공 응답은 받지만 제출은 스팸으로 저장되고 이메일도 웹훅도 보내지 않습니다.

## 문제가 생기면

| 문제 | 해결 방법 |
| --- | --- |
| 답장이 여전히 Ikiform으로 감 | 필드가 아직 `user_email`입니다. `email`로 바꾸거나 `email`, `email_address`, `your_email`, `reply_to`, `contact_email` 중 하나로 바꾸세요. |
| `422 validation_failed` | POST가 폼의 필드 정의와 맞지 않습니다. `error.fields`에 각 필드와 이유가 있습니다. 입력의 `name`을 필드 ID와 맞추세요. |
| `403 challenge_required` | 폼의 스팸 검사가 **Ikiform**으로 설정되어 있는데 `fetch` POST에 토큰이 없었습니다. 제출할 때마다 `@ikiform/sdk`의 `challenge(formId)`로 토큰을 받거나 검사를 끄세요. |
| `403 origin_not_allowed`가 나거나 모든 POST가 스팸으로 감 | 페이지의 도메인이 폼의 **Allowed domains**에 없습니다. 도메인을 추가하고, 로컬 테스트용으로 `localhost`도 추가하세요. 등록한 도메인의 하위 도메인도 허용됩니다. |

## 질문

### Ikiform이 EmailJS보다 저렴한가요?

양과 플랜에 따라 다르고 둘 다 시간이 지나면 바뀌므로 현재 요금 페이지를 비교하세요. Ikiform에는 무료 플랜이 없으며, Pro 또는 일회성 팩으로 제출 건수에 따라 청구합니다.

### EmailJS 퍼블릭 키를 노출해도 안전한가요?

EmailJS는 복사된 키로는 임의의 이메일이 아니라 여러분의 템플릿만 보낼 수 있다고 합니다. 하지만 템플릿 변수는 여전히 호출하는 쪽이 채우고, 수신자가 변수라면 그것도 그들이 고릅니다. 계속 쓴다면 키를 여러분의 도메인으로 제한하고 캡차를 추가하세요.

### Ikiform은 브라우저에 API 키가 필요한가요?

아니요. 브라우저 폼은 키 없이 폼 URL로 POST합니다. 시크릿 키는 여러분의 서버에서 POST할 때만 쓰며, 서버 측 환경 변수에 두어야 합니다.

### Ikiform이 폼을 제출한 사람에게 이메일을 보낼 수 있나요?

아니요. Ikiform은 제출자가 아니라 여러분과 팀에게 이메일을 보내므로 자동 응답이나 확인 이메일은 없습니다. 웹훅을 이메일 도구로 보내고 거기서 확인 메일을 보내세요.

### EmailJS 필드 이름을 그대로 쓸 수 있나요?

네, 이메일 필드만 빼고요. 알림 답장이 방문자에게 가도록 이름을 email로 지정하세요. Ikiform은 POST한 필드 이름을 무엇이든 저장합니다.

## 가이드 더 보기

- [폼을 이메일로 보내기](https://ikiform.com/ko/guides/send-form-to-email.md): 메일 서버 없이 모든 폼 제출을 받은편지함으로 받으세요. mailto:와 PHP mail()이 실패하는 이유와, 스팸을 거르고 바로 답장할 수 있는 설정을 다룹니다.
- [Next.js](https://ikiform.com/ko/guides/send-form-to-email/nextjs.md): Server Action으로 Next.js 폼을 이메일로 보내는 두 가지 방법: Resend나 Nodemailer로 직접 보내기와 폼 백엔드로 전달하기를 솔직하게 비교합니다.
- [받은편지함에서 답장하기](https://ikiform.com/ko/guides/send-form-to-email/reply-to.md): 폼 알림에서 답장을 누르면 폼을 작성한 사람에게 가도록 하세요. 어떤 필드 이름이 동작하는지, _replyto는 언제 쓰는지, 답장이 엉뚱한 곳으로 가는 이유를 다룹니다.
- [PHP 없이](https://ikiform.com/ko/guides/send-form-to-email/without-php.md): mail()을 호출하는 contact.php 스크립트를 호스팅 엔드포인트로 POST하는 폼으로 바꾸세요. 필드 이름, 감사 페이지, 답장은 그대로 유지됩니다.
- [Gmail로](https://ikiform.com/ko/guides/send-form-to-email/gmail.md): mailto: 링크, 앱 비밀번호, Apps Script 없이 Gmail에서 폼 제출을 받고, 라벨을 붙이고, 스팸함을 피하고, Gmail에서 답장하세요.
- [여러 수신자](https://ikiform.com/ko/guides/send-form-to-email/multiple-recipients.md): 폼이 직접 수신자를 고르게 하지 않고도, 폼 하나를 팀 전체에 이메일로 보내거나 주제별로 메시지를 적절한 사람에게 보내세요.
