การดูแลระบบ › อีเมล
เอกสาร/การดูแลระบบ/อีเมล

อีเมล

หน้านี้สอนอะไร

Easy CMS ส่งอีเมลอย่างไร (สำหรับลิงก์ตั้งรหัสผ่าน ฟอร์ม และ hook ของคุณเอง) วิธีเชื่อม SMTP และจะเกิดอะไรขึ้นเมื่อส่งไม่สำเร็จ

ควรอ่านก่อน: การตั้งค่า

Easy CMS ไม่ส่งอีเมลจนกว่าคุณจะใส่ email adapter ใน config จากนั้น CMS จะใช้ส่งลิงก์ลืมรหัสผ่านและคำเชิญ plugin อย่าง form builder ใช้ส่งการแจ้งเตือน และโค้ดของคุณก็ใช้ได้ผ่าน cms.sendEmail()

SMTP

@easy-cms/email-smtp ส่งผ่าน SMTP server ใดก็ได้: Gmail หรือ Google Workspace, Amazon SES, Resend, Mailgun, Postmark หรือ server ของคุณเอง

npm
npm install @easy-cms/email-smtp
pnpm
pnpm add @easy-cms/email-smtp
yarn
yarn add @easy-cms/email-smtp
bun
bun add @easy-cms/email-smtp
import { smtp } from '@easy-cms/email-smtp'
 
export default defineConfig({
  // …
  email: smtp({ from: 'My Site <no-reply@example.com>' }),
})
.env
SMTP_HOST=smtp.resend.com
SMTP_PORT=465
SMTP_USER=resend
SMTP_PASSWORD=re_…
ตัวเลือกค่าเริ่มต้น
hostSMTP_HOSTSMTP server
portSMTP_PORT หรือ 587
securetrue เมื่อใช้ port 465ใช้ TLS ตั้งแต่ต้น port อื่นจะอัปเกรดด้วย STARTTLS
user, passwordSMTP_USER, SMTP_PASSWORD
fromSMTP_FROMผู้ส่งเมื่อข้อความไม่ได้ระบุ
transport—ตัวเลือกอื่นของ nodemailer (pooling, DKIM…)

ค่าเหล่านี้ถูกอ่านตอนส่งอีเมลฉบับแรก config จึงโหลดได้แม้ยังไม่มีค่า เช่น ตอน build

ไม่ให้เข้ากล่องสแปม

ส่งจากที่อยู่อีเมลบนโดเมนของคุณเอง และตั้ง SPF, DKIM และ DMARC กับผู้ให้บริการ อีเมลจาก @gmail.com ที่ส่งผ่าน server อื่นมักเข้ากล่องสแปม

ตรวจการตั้งค่า

ตั้งค่า → อีเมล: ค่า SMTP ที่ใช้อยู่และการส่งอีเมลทดสอบตั้งค่า → อีเมล: ค่า SMTP ที่ใช้อยู่และการส่งอีเมลทดสอบ

admin เปิด ตั้งค่า → อีเมล ในหน้า admin ได้ หน้านี้แสดง adapter และค่าที่ใช้อยู่ พร้อมที่มาของแต่ละค่า (SMTP_HOST, smtp({ from })…) รหัสผ่านแสดงแค่ว่าตั้งไว้หรือยัง ถ้าจะเปลี่ยนค่า ให้แก้ environment variable หรือ config แล้ว restart

  • ตรวจการเชื่อมต่อ เชื่อมต่อและ login เข้า SMTP server โดยไม่ส่งอีเมล
  • ส่งอีเมลทดสอบ ส่งทันทีหนึ่งฉบับ ไม่ผ่านคิว ถึงตัวคุณเองหรือที่อยู่อื่น (ไม่เกิน 5 ครั้งใน 10 นาที)

ทั้งสองแสดงคำตอบจาก server พร้อมคำแนะนำสำหรับความผิดพลาดที่พบบ่อย: user หรือรหัสผ่านผิด (Gmail ต้องใช้ app password), port กับ secure ไม่ตรงกัน (465 คือ TLS ตั้งแต่เริ่ม 587 คือ STARTTLS) ติดต่อ server ไม่ได้ หรือ server ไม่ยอมรับผู้ส่ง

ตอนพัฒนา

consoleEmail() พิมพ์อีเมลแต่ละฉบับลง log ของ server แทนการส่งจริง:

import { consoleEmail, defineConfig } from '@easy-cms/core'
import { smtp } from '@easy-cms/email-smtp'
 
export default defineConfig({
  email: process.env.SMTP_HOST ? smtp() : consoleEmail(),
})

ส่งจากโค้ดของคุณ

await cms.sendEmail({
  to: 'editor@example.com',
  subject: `มีความคิดเห็นใหม่ใน ${post.title}`,
  text: comment.body,
  html: `<p>${escapeHtml(comment.body)}</p>`, // escapeHtml จาก @easy-cms/richtext
})

to, cc และ bcc รับที่อยู่เดียวหรือเป็นรายการ ส่วน from และ replyTo ไม่บังคับ อะไรที่มาจากผู้เข้าชมต้อง escape ก่อนใส่ใน html

เข้าคิวและส่งซ้ำ

sendEmail จบเมื่ออีเมลถูกบันทึกลงฐานข้อมูลแล้ว (collection ภายใน email-deliveries) แล้วส่งเบื้องหลัง ถ้าส่งไม่สำเร็จ (SMTP ล่ม หรือเกินโควตา) จะลองใหม่หลัง 1 นาที, 5 นาที, 30 นาที, 2, 6 และ 12 ชั่วโมง แล้วจึงบันทึกว่าล้มเหลว การลองใหม่ทำงานพร้อมงานที่ตั้งเวลาไว้: ทุกนาทีบน server ที่รันต่อเนื่อง หรือจาก cron ของคุณ (GET <api>/jobs/run) admin ดูอีเมลที่ส่งไม่สำเร็จได้ที่ ตั้งค่า → การส่ง กดส่งซ้ำหรือลบได้ และจะถูกลบอัตโนมัติเมื่อเกิน 30 วัน

บน serverless ให้เรียก await cms.flushEmails() ก่อนฟังก์ชันจะจบ หรือปล่อยให้ cron ส่งฉบับที่ยังค้างอยู่

ตารางนี้ถูกสร้างเฉพาะเมื่อตั้ง email ให้สร้าง migration ด้วย (easy-cms migrate:create email)

เขียน adapter เอง

adapter คือ object ที่มี send() เช่น ใช้ HTTP API แทน SMTP:

import type { EmailAdapter } from '@easy-cms/core'
 
const resend: EmailAdapter = {
  // แสดงให้ admin เห็นบนแดชบอร์ด (กล่องระบบ)
  name: 'resend',
  from: 'My Site <no-reply@example.com>',
  async send(message) {
    const response = await fetch('https://api.resend.com/emails', {
      method: 'POST',
      headers: {
        authorization: `Bearer ${process.env.RESEND_API_KEY}`,
        'content-type': 'application/json',
      },
      body: JSON.stringify({
        from: message.from,
        to: message.to,
        cc: message.cc,
        bcc: message.bcc,
        reply_to: message.replyTo,
        subject: message.subject,
        html: message.html,
        text: message.text,
      }),
    })
    // throw เพื่อให้อีเมลถูกส่งซ้ำภายหลัง
    if (!response.ok) throw new Error(`Resend answered ${response.status}`)
  },
  // ไม่บังคับ สำหรับ ตั้งค่า → อีเมล: สิ่งที่จะแสดง (ห้ามมีความลับ) และการตรวจการเชื่อมต่อ
  describe: () => [{ key: 'from', value: 'My Site <no-reply@example.com>' }],
  async verify() {
    const response = await fetch('https://api.resend.com/domains', {
      headers: { authorization: `Bearer ${process.env.RESEND_API_KEY}` },
    })
    if (!response.ok) throw new Error(`Resend answered ${response.status}`)
  },
}

ขั้นต่อไป

  • ฟอร์ม: แจ้งเตือนเมื่อมีคนส่งฟอร์ม
  • การ deploy: ตั้งค่าตัวแปร SMTP บน host