Type something to search...

07. Contact.jsx — EmailJS로 이메일 전송

이번 단계에서 할 일

Contact 폼에서 입력한 내용을 실제 이메일로 전송한다. EmailJS — 백엔드 서버 없이 프론트엔드에서 바로 이메일을 보내주는 서비스이다.


피그마 디자인 분석

1
│ ─── contacts ──────────────────────────────── │
2
│ │
3
│ Name │
4
│ ┌────────────────────────────────────────┐ │
5
│ │ Value │ │ 높이: 40px
6
│ └────────────────────────────────────────┘ │
7
│ │
8
│ Email │
9
│ ┌────────────────────────────────────────┐ │
10
│ │ Value │ │
11
│ └────────────────────────────────────────┘ │
12
│ │
13
│ Message (textarea) │
14
│ │
15
│ ┌──────────┐ ┌──────────────────────────┐ │
16
│ │ Cancel │ │ Submit │ │
17
│ └──────────┘ └──────────────────────────┘ │

STEP 0 — EmailJS 가입 및 설정

EmailJS를 사용하려면 3가지 값이 필요하다.

설명어디서 찾나
Service ID연결한 이메일 서비스 식별자Email Services 메뉴
Template ID이메일 템플릿 식별자Email Templates 메뉴
Public Key공개 인증 키Account > API Keys
    1. EmailJS 가입https://www.emailjs.com 에 접속하여 무료 계정을 만든다

    2. 이메일 서비스 연결 — Email Services 메뉴에서 Gmail 등 사용할 이메일을 연결한다. 연결하면 Service ID가 생긴다 (예: service_abc123)

    3. 이메일 템플릿 만들기 — Email Templates 메뉴에서 새 템플릿을 만든다. 아래처럼 작성한다

    1
    Subject: 이력서 사이트에서 새 메시지가 도착했습니다
    2
    3
    Hello,
    4
    5
    You got a new message from {{user_name}} ({{user_email}}):
    6
    7
    {{message}}
    8
    9
    Best wishes,
    10
    EmailJS team

    정보

    {{user_name}}, {{user_email}}, {{message}} — 이중 중괄호 안의 이름은 HTML form의 name 속성과 일치해야 한다. 폼에서 name="user_name"으로 보내면 템플릿에서 {{user_name}}으로 받는다.

    1. Template ID 확인 — 템플릿을 저장하면 Template ID가 생긴다 (예: template_xyz789)

    2. Public Key 확인 — Account > API Keys 에서 Public Key를 복사한다


STEP 1 — 패키지 설치

Terminal window
1
npm install @emailjs/browser

@emailjs/browser — 브라우저 환경에서 EmailJS API를 사용할 수 있게 해주는 라이브러리이다


STEP 2 — Contact.jsx 작성

1
import { useState, useRef } from 'react'
2
import emailjs from '@emailjs/browser'
3
import SectionTitle from './SectionTitle'
4
5
const Contact = () => {
6
const formRef = useRef(null)
7
const [loading, setLoading] = useState(false)
8
9
const handleSubmit = (e) => {
10
e.preventDefault()
11
setLoading(true)
12
13
emailjs
14
.sendForm(
15
'YOUR_SERVICE_ID',
16
'YOUR_TEMPLATE_ID',
17
formRef.current,
18
{ publicKey: 'YOUR_PUBLIC_KEY' }
19
)
20
.then(() => {
21
alert('메시지가 전송되었다!')
22
formRef.current.reset()
23
})
24
.catch((error) => {
25
alert('전송 실패: ' + error.text)
26
})
27
.finally(() => {
28
setLoading(false)
29
})
30
}
31
32
const handleReset = () => {
33
formRef.current.reset()
34
}
35
36
return (
37
<section className="contact" id="contacts">
38
<SectionTitle
39
title="contacts"
40
subtitle="끈기와 열정으로 성장하는 오성의입니다."
41
/>
42
43
<form className="contact-form" ref={formRef} onSubmit={handleSubmit}>
44
<div className="form-field">
45
<label className="form-label">Name</label>
46
<input
47
className="form-input"
48
type="text"
49
name="user_name"
50
placeholder="이름을 입력하세요"
51
required
52
/>
53
</div>
54
55
<div className="form-field">
56
<label className="form-label">Email</label>
57
<input
58
className="form-input"
59
type="email"
60
name="user_email"
61
placeholder="이메일을 입력하세요"
62
required
63
/>
64
</div>
65
66
<div className="form-field">
67
<label className="form-label">Message</label>
68
<textarea
69
className="form-textarea"
70
name="message"
71
placeholder="메시지를 입력하세요"
72
required
73
/>
74
</div>
75
76
<div className="form-buttons">
77
<button
78
type="button"
79
className="form-btn form-btn--cancel"
80
onClick={handleReset}
81
>
82
Cancel
83
</button>
84
<button
85
type="submit"
86
className="form-btn form-btn--submit"
87
disabled={loading}
88
>
89
{loading ? 'Sending...' : 'Submit'}
90
</button>
91
</div>
92
</form>
93
</section>
94
)
95
}
96
97
export default Contact

1: import { useState, useRef } from 'react'useState는 상태 관리, useRef는 DOM 요소를 직접 참조할 때 사용한다

2: import emailjs from '@emailjs/browser' — EmailJS 라이브러리를 가져온다

3: import SectionTitle from './SectionTitle' — 이전에 만든 섹션 타이틀 컴포넌트이다

6: const formRef = useRef(null) — form 태그를 직접 참조하기 위한 ref이다. EmailJS의 sendForm이 form 요소를 직접 읽어야 하기 때문에 필요하다

7: const [loading, setLoading] = useState(false) — 전송 중인지 아닌지 상태를 저장한다. 전송 중에는 버튼을 비활성화한다

9: const handleSubmit = (e) => { — 폼을 제출할 때 실행되는 함수이다

10: e.preventDefault() — form 태그의 기본 동작(페이지 새로고침)을 막는다

11: setLoading(true) — 전송 시작을 표시한다

13~18: emailjs.sendForm(...) — EmailJS에 이메일 전송을 요청한다

  • 첫 번째 인자: Service ID
  • 두 번째 인자: Template ID
  • 세 번째 인자: form DOM 요소 (formRef.current)
  • 네 번째 인자: Public Key 객체

19~21: .then(() => { ... }) — 전송 성공 시 알림을 띄우고, formRef.current.reset()으로 폼을 초기화한다

22~24: .catch((error) => { ... }) — 전송 실패 시 에러 메시지를 보여준다

25~27: .finally(() => { ... }) — 성공/실패 상관없이 loading 상태를 false로 되돌린다

30~32: const handleReset = () => { ... } — Cancel 버튼 클릭 시 폼의 모든 값을 초기화한다

42: ref={formRef} — form 태그에 ref를 연결한다. 이제 formRef.current로 이 form 요소에 접근할 수 있다

47: name="user_name" — EmailJS 템플릿의 {{user_name}}과 매칭되는 이름이다

49: required — 빈 칸으로 제출할 수 없게 막는다

75: disabled={loading} — 전송 중에는 버튼을 클릭할 수 없게 막는다. 중복 전송을 방지한다

77: {loading ? 'Sending...' : 'Submit'} — 삼항 연산자로 전송 중이면 “Sending…”, 아니면 “Submit”을 표시한다

주의

'YOUR_SERVICE_ID', 'YOUR_TEMPLATE_ID', 'YOUR_PUBLIC_KEY'는 반드시 본인의 EmailJS 대시보드에서 복사한 실제 값으로 바꿔야 한다.


새로운 개념: useRef

이전 단계에서는 useState로 input 값을 관리했다 (제어 컴포넌트). 이번에는 useRef로 form 자체를 참조한다 (비제어 컴포넌트).

1
제어 컴포넌트 (useState):
2
React가 input 값을 직접 관리한다
3
value={state} + onChange={setState}
4
5
비제어 컴포넌트 (useRef):
6
HTML form이 값을 스스로 관리한다
7
React는 ref로 form에 접근만 한다

EmailJS의 sendForm은 form DOM 요소를 통째로 읽어서 데이터를 수집한다. 그래서 useRef로 form을 참조하는 방식이 더 자연스럽다.

1
const formRef = useRef(null)
2
3
<form ref={formRef}>
4
<input name="user_name" />
5
</form>
6
7
emailjs.sendForm('...', '...', formRef.current, { publicKey: '...' })

formRef.current — ref 안에 저장된 실제 DOM 요소이다. useRef.current 속성에 값을 담아둔다.


새로운 개념: Promise (.then / .catch / .finally)

emailjs.sendForm()Promise를 반환한다. Promise — 미래에 완료될 작업을 나타내는 객체이다. “나중에 결과를 알려줄게”라는 약속이다.

1
emailjs.sendForm(...) ← 이메일 전송 요청 (시간이 걸린다)
2
.then(() => { ... }) ← 성공하면 실행
3
.catch(() => { ... }) ← 실패하면 실행
4
.finally(() => { ... }) ← 성공/실패 상관없이 항상 실행

비유: 택배를 보내는 것과 같다.

1
택배 접수 (sendForm)
2
├─ 배달 완료 (.then) → "배달되었습니다" 알림
3
├─ 배달 실패 (.catch) → "배달 실패" 알림
4
└─ 어떤 경우든 (.finally) → 배달 추적 화면 닫기

STEP 3 — CSS 추가

1
/* src/index.css 에 추가 */
2
3
.contact {
4
padding: 80px 24px;
5
}
6
7
.contact-form {
8
width: 320px;
9
display: flex;
10
flex-direction: column;
11
gap: 20px;
12
}
13
14
.form-field {
15
display: flex;
16
flex-direction: column;
17
gap: 6px;
18
}
19
20
.form-label {
21
font-size: 16px;
22
font-weight: 500;
23
color: #1e1e1e;
24
}
25
26
.form-input {
27
height: 40px;
28
padding: 0 12px;
29
border: 1px solid #d0d0d0;
30
border-radius: 4px;
31
font-size: 16px;
32
color: #1e1e1e;
33
outline: none;
34
width: 100%;
35
}
36
37
.form-textarea {
38
height: 80px;
39
padding: 10px 12px;
40
border: 1px solid #d0d0d0;
41
border-radius: 4px;
42
font-size: 16px;
43
color: #1e1e1e;
44
resize: none;
45
outline: none;
46
width: 100%;
47
}
48
49
.form-input::placeholder,
50
.form-textarea::placeholder {
51
color: #b3b3b3;
52
}
53
54
.form-input:focus,
55
.form-textarea:focus {
56
border-color: #2c2c2c;
57
}
58
59
.form-buttons {
60
display: flex;
61
gap: 12px;
62
}
63
64
.form-btn {
65
height: 40px;
66
padding: 0 20px;
67
border: none;
68
border-radius: 4px;
69
font-size: 16px;
70
cursor: pointer;
71
transition: opacity 0.2s;
72
}
73
74
.form-btn--cancel {
75
background-color: #f0f0f0;
76
color: #303030;
77
}
78
79
.form-btn--submit {
80
flex: 1;
81
background-color: #2c2c2c;
82
color: #f5f5f5;
83
}
84
85
.form-btn--submit:disabled {
86
opacity: 0.5;
87
cursor: not-allowed;
88
}
89
90
.form-btn:hover:not(:disabled) {
91
opacity: 0.8;
92
}

89~92: .form-btn--submit:disableddisabled 속성이 있을 때의 스타일이다. 전송 중에는 반투명하게 보이고 커서가 금지 모양으로 바뀐다

94~96: .form-btn:hover:not(:disabled) — 비활성화 상태가 아닐 때만 hover 효과를 적용한다


브라우저 확인

    1. 폼에 이름, 이메일, 메시지를 입력한다
    2. Submit 버튼을 클릭한다
    3. 버튼이 “Sending…”으로 바뀌는 것을 확인한다
    4. “메시지가 전송되었다!” 알림이 뜨면 성공이다
    5. EmailJS에 연결한 이메일 받은편지함을 확인한다 — 실제 이메일이 와 있다

주의

전송이 실패하면 확인할 것:

  • Service ID, Template ID, Public Key가 정확한지 확인한다
  • EmailJS 대시보드에서 이메일 서비스가 Active 상태인지 확인한다
  • 템플릿의 변수 이름({{user_name}} 등)이 form의 name 속성과 일치하는지 확인한다
  • 무료 플랜은 월 200건까지 전송 가능하다


정리

개념설명
EmailJS백엔드 없이 프론트엔드에서 이메일을 보내는 서비스
useRefDOM 요소를 직접 참조하는 React Hook
ref={formRef}form 태그에 ref를 연결하는 방법
sendForm()form 요소를 읽어서 이메일을 전송하는 함수
Promise미래에 완료될 작업을 나타내는 객체
.then()성공 시 실행
.catch()실패 시 실행
.finally()성공/실패 상관없이 항상 실행
disabled버튼을 클릭 불가능하게 만드는 속성
required빈 칸 제출을 막는 HTML 속성