Astro กับ Resend: ส่ง Email จาก Astro

#astro13 เม.ย. 2569

Astro กับ Resend: ส่ง Email จาก Astro

Resend เป็น email API สำหรับ developers ที่ออกแบบมาให้ใช้งานง่ายและมี deliverability สูง ด้วย React Email integration และ TypeScript support ทำให้การสร้างและส่ง email จาก Astro เป็นเรื่องง่ายและสวยงาม

ทำไมต้องใช้ Resend?

  • Developer-first: API ที่ออกแบบมาสำหรับ developers
  • React Email: สร้าง email templates ด้วย React
  • High Deliverability: อัตราการส่งสำเร็จสูง
  • Analytics: ติดตาม open rates, click rates
  • Free Tier: ส่งได้ 3,000 emails/เดือนฟรี

การติดตั้ง

npm install resend
npm install react @react-email/components  # สำหรับ email templates

ตั้งค่า environment variables:

# .env
RESEND_API_KEY=re_your_api_key
FROM_EMAIL=noreply@yourdomain.com

สร้าง Resend Client

// src/lib/resend.ts
import { Resend } from 'resend';

export const resend = new Resend(import.meta.env.RESEND_API_KEY);

export const FROM_EMAIL = import.meta.env.FROM_EMAIL ?? 'noreply@example.com';

สร้าง Email Templates ด้วย React Email

// src/emails/WelcomeEmail.tsx
import {
  Html,
  Head,
  Body,
  Container,
  Section,
  Text,
  Button,
  Heading,
  Hr,
  Img,
  Link,
  Preview,
} from '@react-email/components';
import * as React from 'react';

interface WelcomeEmailProps {
  userName: string;
  confirmUrl: string;
}

export function WelcomeEmail({ userName, confirmUrl }: WelcomeEmailProps) {
  return (
    <Html lang="th">
      <Head />
      <Preview>ยินดีต้อนรับสู่ DoSystem, {userName}!</Preview>
      <Body style={bodyStyle}>
        <Container style={containerStyle}>
          <Img
            src="https://yourdomain.com/logo.png"
            alt="Logo"
            width={120}
            height={40}
          />
          <Heading style={headingStyle}>
            ยินดีต้อนรับ, {userName}!
          </Heading>
          <Text style={textStyle}>
            ขอบคุณที่สมัครสมาชิกกับเรา กรุณายืนยันอีเมลของคุณเพื่อเริ่มใช้งาน
          </Text>
          <Section style={buttonSectionStyle}>
            <Button href={confirmUrl} style={buttonStyle}>
              ยืนยันอีเมล
            </Button>
          </Section>
          <Hr style={hrStyle} />
          <Text style={footerStyle}>
            หากคุณไม่ได้สมัครสมาชิก กรุณาเพิกเฉยต่ออีเมลนี้
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

const bodyStyle = {
  backgroundColor: '#f6f9fc',
  fontFamily: 'Sarabun, -apple-system, sans-serif',
};

const containerStyle = {
  backgroundColor: '#ffffff',
  margin: '0 auto',
  padding: '20px 0 48px',
  marginBottom: '64px',
  maxWidth: '600px',
  borderRadius: '8px',
};

const headingStyle = {
  fontSize: '24px',
  fontWeight: 'bold',
  color: '#1a1a1a',
  padding: '0 48px',
};

const textStyle = {
  fontSize: '16px',
  lineHeight: '26px',
  color: '#404040',
  padding: '0 48px',
};

const buttonSectionStyle = {
  padding: '0 48px',
  marginTop: '24px',
};

const buttonStyle = {
  backgroundColor: '#5469d4',
  borderRadius: '4px',
  color: '#fff',
  fontSize: '16px',
  fontWeight: 'bold',
  textDecoration: 'none',
  textAlign: 'center' as const,
  display: 'block',
  padding: '12px 20px',
};

const hrStyle = {
  borderColor: '#e6ebf1',
  margin: '20px 0',
};

const footerStyle = {
  fontSize: '12px',
  color: '#8898aa',
  padding: '0 48px',
};

API Route สำหรับส่ง Email

// src/pages/api/send-email.ts
import type { APIRoute } from 'astro';
import { resend, FROM_EMAIL } from '../../lib/resend';
import { WelcomeEmail } from '../../emails/WelcomeEmail';
import { render } from '@react-email/render';
import * as React from 'react';

export const POST: APIRoute = async ({ request }) => {
  const { type, to, data } = await request.json();

  try {
    let result;

    switch (type) {
      case 'welcome': {
        const html = render(
          React.createElement(WelcomeEmail, {
            userName: data.userName,
            confirmUrl: data.confirmUrl,
          })
        );

        result = await resend.emails.send({
          from: FROM_EMAIL,
          to,
          subject: `ยินดีต้อนรับ, ${data.userName}!`,
          html,
        });
        break;
      }

      case 'contact': {
        result = await resend.emails.send({
          from: FROM_EMAIL,
          to: import.meta.env.ADMIN_EMAIL,
          replyTo: to,
          subject: `ข้อความจาก ${data.name}: ${data.subject}`,
          text: data.message,
        });
        break;
      }

      default:
        return new Response(JSON.stringify({ error: 'Unknown email type' }), {
          status: 400,
        });
    }

    return new Response(JSON.stringify({ id: result.data?.id }), {
      headers: { 'Content-Type': 'application/json' },
    });
  } catch (error) {
    console.error('Email error:', error);
    return new Response(JSON.stringify({ error: 'Failed to send email' }), {
      status: 500,
    });
  }
};

Contact Form

---
// src/pages/contact.astro
---

<html lang="th">
  <body>
    <h1>ติดต่อเรา</h1>
    <form id="contact-form">
      <input type="text" name="name" placeholder="ชื่อ" required />
      <input type="email" name="email" placeholder="อีเมล" required />
      <input type="text" name="subject" placeholder="หัวข้อ" required />
      <textarea name="message" placeholder="ข้อความ" rows="5" required></textarea>
      <button type="submit">ส่งข้อความ</button>
    </form>

    <script>
      document.getElementById('contact-form')?.addEventListener('submit', async (e) => {
        e.preventDefault();
        const form = e.target as HTMLFormElement;
        const formData = new FormData(form);

        const response = await fetch('/api/send-email', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            type: 'contact',
            to: formData.get('email'),
            data: {
              name: formData.get('name'),
              subject: formData.get('subject'),
              message: formData.get('message'),
            },
          }),
        });

        if (response.ok) {
          alert('ส่งข้อความสำเร็จ!');
          form.reset();
        } else {
          alert('เกิดข้อผิดพลาด กรุณาลองใหม่');
        }
      });
    </script>
  </body>
</html>

Batch Emails

// src/lib/emailBatch.ts
import { resend, FROM_EMAIL } from './resend';

export async function sendNewsletterBatch(
  subscribers: { email: string; name: string }[],
  subject: string,
  html: string
) {
  // Resend รองรับ batch ได้สูงสุด 100 emails ต่อ request
  const batchSize = 100;
  const results = [];

  for (let i = 0; i < subscribers.length; i += batchSize) {
    const batch = subscribers.slice(i, i + batchSize);

    const emails = batch.map((sub) => ({
      from: FROM_EMAIL,
      to: sub.email,
      subject,
      html: html.replace('{{name}}', sub.name),
    }));

    const result = await resend.batch.send(emails);
    results.push(result);

    // Rate limiting
    if (i + batchSize < subscribers.length) {
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
  }

  return results;
}

สรุป

Resend กับ Astro ทำให้การส่ง email เป็นเรื่องง่ายและมีประสิทธิภาพ ด้วย React Email templates ที่สวยงาม, TypeScript support, และ high deliverability ทำให้ระบบ email ของ application มีความน่าเชื่อถือสูง