본문 바로가기
개발/React

리액트 - Toast UI Editor로 메일 보내기 with nodemailer

by 피로물든딸기 2023. 12. 10.
반응형

리액트 전체 링크

Node JS 전체 링크

 

참고

- body-parser를 이용해 데이터 전송 받기

- Toast UI 에디터로 깃허브 마크다운 저장하기

- nodemailer로 구글, 네이버, 다음 카카오 메일 보내기

 

nodemailer의 html 설정으로 어느 정도 형식이 있는 메일을 보낼 수 있다.

리액트에서 Toast UI Editor를 이용하면 getHTML() 으로 html 태그를 쉽게 얻을 수 있다.

 

먼저 링크를 참고하여 express로 nodemailer 라우터를 구축하자.

 

./routes/post_man.js

메일이 전송되지 않은 경우 try catch를 이용해 false를 send하도록 하였다.

const express = require("express");
const router = express.Router();
const nodemailer = require("nodemailer");
const bodyParser = require("body-parser");

router.use(bodyParser.json({ limit: 100000000 })); // 100MB

const sendEmail = async (to, cc, bcc, subject, html) => {
  const transporter = nodemailer.createTransport({
    service: "gmail",
    //secure: true, // use TLS
    auth: { user: "GMAIL ID", pass: "GMAIL PASSWORD" },
  });

  const mailOptions = {
    // from: google 생략 가능
    to, // a@hanmail.net, b@hanmail.net : , 로 구분
    cc, // 참조
    bcc, // 비밀 참조
    subject, // 제목
    // text : ""
    html,
    // attachments:
  };

  try {
    await transporter.sendMail(mailOptions);
    console.log("이메일 전송 완료");
    return true;
  } catch (e) {
    console.error("이메일 전송 오류:", e);
    throw e;
  }
};

const sendWrapper = async (to, cc, bcc, subject, html, res) => {
  try {
    let ret = await sendEmail(to, cc, bcc, subject, html);
    console.log(ret);
    res.json({ result: true });
  } catch (error) {
    console.error("에러 발생:", error);
    res.json({ result: false });
  }
};

router.post("/", (req, res) => {
  // let from = req.body.from; // auth로 고정
  let to = req.body.to;
  let cc = req.body.cc;
  let bcc = req.body.bcc;
  let subject = req.body.subject;
  let html = req.body.html;

  console.log({ to, cc, bcc, subject, html });

  sendWrapper(to, cc, bcc, subject, html, res);
});

module.exports = router;

 

server.js

const express = require("express");
const app = express();

const cors = require("cors");
app.use(cors());

const post_man = require("./routes/post_man");

app.use("/post_man", post_man);

app.listen(3002, () => {
  console.log("Node.js Server is running on port 3002...");
});

 

리액트에서 axios를 이용해 응답을 받은 로그는 다음과 같다.


리액트 구현

 

Material UI와 Toast UI Editor 링크를 참고하여 아래와 같이 템플릿을 만들자.

참고로 보내는 사람은 형식만 갖출 뿐, Node JS에서 설정한 auth로 보내게 된다.

import React, { useRef, useState } from "react";

import axios from "axios";

import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";

// Toast UI Editor
import "@toast-ui/editor/dist/toastui-editor.css";
import "@toast-ui/editor/dist/toastui-editor-viewer.css"; // Viewer css
import { Editor } from "@toast-ui/react-editor";
//import Viewer from "@toast-ui/editor/dist/toastui-editor-viewer";

// Dark Theme 적용
// import '@toast-ui/editor/dist/toastui-editor.css';
// import '@toast-ui/editor/dist/theme/toastui-editor-dark.css';

// Color Syntax Plugin
import "tui-color-picker/dist/tui-color-picker.css";
import "@toast-ui/editor-plugin-color-syntax/dist/toastui-editor-plugin-color-syntax.css";
import colorSyntax from "@toast-ui/editor-plugin-color-syntax";

// Table Merged Cell Plugin
import "@toast-ui/editor-plugin-table-merged-cell/dist/toastui-editor-plugin-table-merged-cell.css";
import tableMergedCell from "@toast-ui/editor-plugin-table-merged-cell";

const colorSyntaxOptions = {
  preset: [
    "#333333", "#666666", "#FFFFFF", "#EE2323", "#F89009", "#009A87", "#006DD7", "#8A3DB6",
    "#781B33", "#5733B1", "#953B34", "#FFC1C8", "#FFC9AF", "#9FEEC3", "#99CEFA", "#C1BEF9",
  ],
};

const ToastMailer = () => {
  const editorRef = useRef(null);
  const [info, setInfo] = useState({
    to: "",
    cc: "",
    bcc: "",
    subject: "",
  });

  const sendEmail = async () => {
    let htmlContents = editorRef.current.getInstance().getHTML();

    const response = await axios.post(
      "http://192.168.55.120:3002/post_man",
      {
        to: info.to,
        cc: info.cc,
        bcc: info.bcc,
        subject: info.subject,
        html: htmlContents,
      },
      {
        header: { "content-type": "application/json" },
      }
    );

    console.log(response);
  };

  return (
    <div>
      <Box sx={{ m: 2 }}>
        <h1>Toast Mail</h1>
        <Button
          variant="outlined"
          color="secondary"
          sx={{ m: 1 }}
          onClick={sendEmail}
        >
          보내기
        </Button>
        <Box
          component="form"
          sx={{
            m: 2,
            display: "flex",
            flexDirection: "column",
            width: "98%",
          }}
          noValidate
          autoComplete="off"
        >
          <TextField
            sx={{ marginBottom: 3 }}
            color="primary"
            label="보내는사람"
          />
          <TextField
            sx={{ marginBottom: 3 }}
            color="primary"
            label="받는사람"
            value={info.to}
            onChange={(e) => setInfo({ ...info, to: e.target.value })}
          />
          <TextField
            sx={{ marginBottom: 3 }}
            color="primary"
            label="참조"
            value={info.cc}
            onChange={(e) => setInfo({ ...info, cc: e.target.value })}
          />
          <TextField
            sx={{ marginBottom: 3 }}
            color="warning"
            label="비밀참조"
            value={info.bcc}
            onChange={(e) => setInfo({ ...info, bcc: e.target.value })}
          />
          <TextField
            sx={{ marginBottom: 3 }}
            color="success"
            label="제목"
            value={info.subject}
            onChange={(e) => setInfo({ ...info, subject: e.target.value })}
          />
        </Box>

        <Editor
          ref={editorRef}
          // initialValue={initContents}
          height="1000px"
          placeholder="Please Enter Text."
          previewStyle="tab" // or vertical
          initialEditType="wysiwyg" // or markdown
          hideModeSwitch={true} // 하단 숨기기
          toolbarItems={[
            // 툴바 옵션 설정
            ["heading", "bold", "italic", "strike"],
            ["hr", "quote"],
            ["ul", "ol", "task", "indent", "outdent"],
            ["table", /* "image", */ "link"],
            ["code", "codeblock"],
          ]}
          //theme="dark"
          //useCommandShortcut={false} // 키보드 입력 컨트롤 방지 ex ctrl z 등
          usageStatistics={false} // 통계 수집 거부
          plugins={[[colorSyntax, colorSyntaxOptions], tableMergedCell]}
        />
      </Box>
    </div>
  );
};

export default ToastMailer;

 

결과는 다음과 같다.

 

이제 에디터에서 여러 편집 기능을 사용해서 메일을 보내보자.

 

전송받은 메일은 다음과 같다.

아쉽게도 block quote, check box, 테이블 등은 class가 존재하지 않기 때문에 반영되지 않는다.

반응형

댓글