ผมเพิ่งสร้าง Landing Page ให้ลูกค้าที่อยากรับอีเมลผ่านเว็บไซต์โดยไม่เปิดเผยอีเมลของตน
ซื่อสัตย์ ผมไม่เคย Implement ฟังก์ชันนี้เองมาก่อน เคยใช้แค่ปุ่ม Contact Us
พร้อม Anchor Tag และ mailto ใน href แบบนี้:
<button> <a href="mailto:myemail@example.com">Contact Me</a> </button>
แต่วิธีนี้มีข้อไม่สะดวก 2 ประการ:
- บังคับทั้งสองฝ่าย (ผู้ส่งและผู้รับ) แชร์อีเมลกัน แม้ OK บางคน แต่ไม่เหมาะสำหรับคนห่วง Privacy
- ผู้เยี่ยมชมกดลิงก์บังคับเปิด Default Mail Program บนอุปกรณ์ อาจทำให้หงุดหงิด: ถ้าใช้ Public Computer? ไม่ได้ Login? หรือแค่ไม่อยากใช้ Mail Program? ใช่ เทคนิค Copy Email ไปส่งจาก Browser/ที่ Login อยู่ก็ได้ แต่นี่คือ Extra Steps ที่อาจทำให้ User ไม่อยากส่งข้อความ และธุรกิจเสียโอกาส
เพราะฉะนั้น เลือกใช้ Email Form ให้ User พิมพ์ข้อความ กด Submit ส่งอีเมลให้เจ้าของเว็บโดยไม่ต้องออกจากเว็บไซต์
Google หน่อย พบ Third-party Tools/Widgets ที่ Embed ได้ แต่ส่วนใหญ่ Branded และต้องจ่าย Subscription สำหรับ Customization เต็มรูปแบบ
และเว้นแต่จะใช้ CMS อย่าง WordPress ที่มี Plugin Built-in ทำได้ นี่คือ Recurring Cost ที่ไม่สะดวก
ผมเลือกเขียน Feature เอง เพื่อ Control เต็มที่
ในคู่มือนี้ ผมจะสร้างขั้นตอนที่ทำ จริงใช้ HTML และ AWS Services
The HTML Form
ทำให้เรียบง่าย HTML Form พื้นฐาน ไม่มี CSS แค่ทดสอบ Functionality
<h2>Contact Us</h2> <form> <label for="name">Name:</label> <input name="name" type="text"/><br/><br/> <label for="email">Email:</label> <input name="email" type="email"/><br/><br/> <label for="name">Message:</label> <textarea name="message"></textarea><br/><br/> <input type="submit"/> <div> <p id="result-text"></p> </div> </form>
ตอนนี้จัดการ Submit ด้วย JavaScript
const form = document.querySelector('form') form.addEventListener('submit', event => { // prevent the form submit from refreshing the page event.preventDefault() const { name, email, message } = event.target console.log('Name: ', name.value) console.log('email: ', email.value) console.log('Message: ', message.value) })
ตอนนี้มี Form รับ Input และ JS แค่ Log ผลลัพธ์ใน Console
ทิ้งไว้ก่อน ไปสร้าง Backend Services ที่รับ Form Data และส่งอีเมลพร้อมข้อมูลนั้น
The Backend Overview
ลงลึก AWS และ Services ที่จะใช้และวิธีใช้
เหมือนในหัวข้อ เราจะใช้ AWS Lambda และ Simple Email Service (SES) SES คือ Serverless Messaging Service ส่งอีเมลเมื่อถูก Invoke Lambda ให้เขียน Server-side Code รันตอบสนอง Events
เราจะใช้ API Gateway ให้ Invoke Lambda Functions ผ่าน HTTP
ในกรณีนี้ เมื่อ Form ถูก Submit Workflow จะเป็น:
- Browser (JavaScript) ส่ง POST Request พร้อม Form Data ใน Request Body ไป Endpoint URL จาก AWS API Gateway
- API Gateway Validate Request แล้ว Trigger Lambda Function ที่รับ Event Parameter API Gateway ใส่ Form Data ใน Body Property ของ Event Parameter
- Lambda Function ดึง Data จาก Event Body สร้าง Email Body และ Recipients ใช้ AWS SDK เรียก SES พร้อม Email Data
- SES ได้ sendMail Request แปลง Email Data เป็น Text Email จริง ส่งไป Recipient ผ่าน Mail Servers ของ AWS
ส่งอีเมลเสร็จ Browser ได้ Response Status 200 และ Success Message หากขั้นตอนไหนใน AWS Cloud Fail Response จะเป็น Status 500
ขั้นตอนที่ 1: ตั้งค่า SES
เราจะตั้งค่าแต่ละขั้นตอนย้อนกลับ เริ่มจาก SES ที่ง่ายที่สุด
ใน AWS Console ไป SES Service → คลิก Email Addresses ใน Side Menu → กด Verify a New Email Address
ใน Dialog ที่เปิด ใส่อีเมลที่ต้องการให้ SES ใช้เป็น Sender ตอนส่งอีเมล
จะส่งอีเมลไปที่อีเมลนั้น พร้อมลิงก์คลิก Verify นี่คือวิธี AWS รู้ว่าเจ้าของอีเมลยินยอมให้ใช้ Address นี้เป็น Sender
จนกว่าจะ Verify SES Dashboard จะแสดง Verification Status เป็น Pending
เมื่อเจ้าของอีเมลเปิดอีเมลจาก AWS และคลิก Verify Link Status ควรเปลี่ยนเป็น Verified (Refresh หน้าเพื่อดู)
แค่นี้ก็จบ SES แล้ว สามารถทดสอบได้โดยเลือก Verified Email ในรายการ กด Send a Test Email
ใส่ Recipient, Subject, Message ส่งได้
อีเมลที่ส่งจะถูก Sign โดย AWS Servers และ Verified Address จะเป็น Sender ควรเป็นแบบนี้:
ขั้นตอนที่ 2: ตั้งค่า Lambda
นี่คือส่วนสนุกที่สุด สร้าง Function ที่รับ Form Data และเรียก SES
ความสวยงามของ Lambda Functions: ไม่ต้องกังวลรัน Backend Code บน Server 24/7 และ Maintain Server มัน Serverless
แต่ไม่ใช่ว่าไม่มี Server AWS จัดการใต้ฝาฟ้า ให้โฟกัสเขียน Code เท่านั้น นอกจากนี้ Billing เฉพาะจำนวนครั้งที่ Function ถูกเรียก และเวลาที่ Execute ราคาถูกมาก!
สร้าง IAM Role และ Config
ก่อนเขียน Lambda Function ต้องสร้าง IAM Role Attach ให้ Function และให้ Permissions (Policies ใน AWS) เรียก SES
จาก AWS Console ไป IAM Service → คลิก Policies ใน Side Menu → กด Create Policy
ในหน้าสร้าง Policy ไป Tab JSON วาง Permissions นี้ แล้ว Next
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ses:SendEmail", "ses:SendRawEmail" ], "Resource": "*" } ] }
หน้าที่ 3 ตั้งชื่อ Policy กด Create Policy
ตอนนี้สร้าง IAM Role Attach ให้ Lambda และ Link กับ Policy ที่สร้างไป
จาก IAM Side Menu คลิก Roles → กด Create role
ในหน้าสร้าง Role ตรวจสอบ Type เป็น AWS Service
เลือก Lambda Case → กด Next: Permissions
หน้าต่อไป ค้นหา Policy ที่สร้างก่อนหน้าจากชื่อ เลือก → Next
หน้า Review ตั้งชื่อ Role ที่จำง่าย → กด Create role
ตอนนี้สร้าง Lambda Function ใหม่ ไป Lambda Service Dashboard กด Create Function
ในหน้าสร้าง Function ตั้งชื่อ เลือก Author from scratch
เลือก Runtime Node.js
ภายใต้ Change default execution role
เลือก Use an existing role
แล้วเลือก Role ที่สร้างจาก Dropdown Existing role
กด Create function
เขียนโค้ดและทดสอบ
ใน Editor เปิดไฟล์ index.js (ไฟล์ที่รันเมื่อ Lambda ถูกเรียก) แทนที่เนื้อหาด้วยโค้ดนี้:
const aws = require("aws-sdk"); const ses = new aws.SES({ region: "us-east-1" }); exports.handler = async function (event) { console.log('EVENT: ', event) const params = { Destination: { ToAddresses: ["your@email.com"], }, Message: { Body: { Text: { Data: `Hello from Lambda!` }, }, Subject: { Data: `Message from AWS Lambda` }, }, Source: "your@email.com", }; return ses.sendEmail(params).promise() };
สังเกตบรรทัดที่ 2 ใช้ AWS SDK สร้าง SES Instance เหตุที่เลือก us-east-1 คือ Region ที่ผม Verify Email อย่าลืมเปลี่ยน Email และใช้ AWS Region ที่ Verify Email ของคุณ
ทดสอบ Function: กด Deploy
แล้วกด Test
→ Configure test event
จะเปิด Dialog สร้าง Test Event ใหม่
ใน Test Event Body Editor ใส่ JSON นี้ (เลียนแบบ Request จาก Browser) แล้ว Create
{ "body": { "senderName": "Namo", "senderEmail": "namo@trains.com", "message": "I love trains!" } }
กด Test จะรัน Test ที่สร้าง ควรเปิด Tab ใหม่ใน Editor แสดง Logs จากการรัน Function ประมาณนี้:
สังเกต Event Object ที่ Log แสดงอยู่ภายใต้ Function Logs พร้อม Body Data จาก Test Event
Test นี้ควรส่งอีเมลไป Inbox ผมด้วย — เช็คดู
ถูกต้อง ตามคาด เกิดขึ้นทันทีหลังรัน Test
ตอนนี้ปรับโค้ด Function ให้ข้อความมีความหมายมากขึ้นจาก Test Data
const aws = require("aws-sdk"); const ses = new aws.SES({ region: "us-east-1" }); exports.handler = async function (event) { console.log('EVENT: ', event) // Extract properties from event body const { senderEmail, senderName, message } = JSON.parse(event.body) const params = { Destination: { ToAddresses: ["the.benhawy@gmail.com"], }, // Interpolate data in strings to send Message: { Body: { Text: { Data: `You just got a message from ${senderName} - ${senderEmail}: ${message}` }, }, Subject: { Data: `Message from ${senderName}` }, }, Source: "the.benhawy@gmail.com", }; return ses.sendEmail(params).promise(); };
สำคัญ: เมื่อ API Gateway เรียก Function จะส่ง String ไปใน Event Body เพราะฉะนั้นใช้ JSON.parse บน event.body เพื่อแปลงเป็น JSON และดึง Sender Email, Name, Message แล้วนำไป Interpolate ใน Email Body และ Subject
หากลอง Test จะ Error เพราะ Test ส่ง JSON Object ไป event.body แต่เรา JSON.parse บน JSON ทำให้ Error ใน JavaScript
น่าเสียดาย Test Editor ไม่อนุญาตส่ง String ไป event.body ต้องทดสอบที่อื่นต่อ
ขั้นตอนที่ 3: ตั้งค่า API Gateway
บริการ AWS ตัวสุดท้ายคือ API Gateway ให้ Browser ส่ง HTTP Request ไป Lambda Function ที่สร้าง
โดยไม่ต้องออกจากหน้า Lambda Function ขยาย Section Function overview
กด Add trigger
เลือก API Gateway จาก Dropdown, HTTP API as API Type, Open
as Security Mechanism, ติ๊ก CORS Checkbox → กด Add
จะ Redirect ไป Tab Configuration
ของ Function แสดง API Gateway Trigger ที่เพิ่งสร้าง จากนั้นจด API Endpoint นี่คือ URL ที่จะเรียกจาก Browser พร้อม Form Data
กลับไปที่ HTML
สุดท้าย ทดสอบ Form ว่าส่งอีเมลได้จริงไหม
ปรับ JavaScript ให้จัดการส่ง Request ตอน Submit Form
const form = document.querySelector("form"); form.addEventListener("submit", (event) => { // prevent the form submit from refreshing the page event.preventDefault(); const { name, email, message } = event.target; // Use your API endpoint URL you copied from the previous step const endpoint = "<https://5ntvcwwmec.execute-api.us-east-1.amazonaws.com/default/sendContactEmail>"; // We use JSON.stringify here so the data can be sent as a string via HTTP const body = JSON.stringify({ senderName: name.value, senderEmail: email.value, message: message.value }); const requestOptions = { method: "POST", body }; fetch(endpoint, requestOptions) .then((response) => { if (!response.ok) throw new Error("Error in fetch"); return response.json(); }) .then((response) => { document.getElementById("result-text").innerText = "Email sent successfully!"; }) .catch((error) => { document.getElementById("result-text").innerText = "An unkown error occured."; }); });
ช่วงสำคัญ: กรอก Form กด Submit หากเห็น Success Message แปลว่าอีเมลส่งสำเร็จ
เพราะผมเป็นเจ้าของอีเมลที่รับ ผมเช็ค Inbox ดู ได้อีเมลจากตัวเองพร้อมรายละเอียดที่กรอกใน Form!
หากติดตามมาถึงที่นี่ คุณมี Contact Us
Form ที่ทำงานจริง นำไปเสียบในเว็บไซต์ไหนก็ได้ และคุณจ่ายเงินเฉพาะตอนใช้งานจริง
ไม่รู้คุณแต่ผมคิดว่านี่ เก่งมากและเกือบ Magical! และเป็น Practical Way ใช้ Cloud Computing/Services ใน Workflow
แน่นอน Customize Flow ได้ ใช้ Framework Frontend อย่าง React/Vue หรือ Language อื่นสำหรับ Lambda อย่าง Python/Go
Before you go...
ขอบคุณที่อ่านจนจบ! ผมเขียนโพสต์เกี่ยว JavaScript, Cloud Development และประสบการณ์การศึกษา/งาน بصف Self-taught Developer ติดตามได้ที่ Twitter @adham_benhawy
Resources
- https://aws.amazon.com/premiumsupport/knowledge-center/lambda-send-email-ses/
- https://docs.aws.amazon.com/lambda/latest/dg/lambda-invocation.html
- https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html?icmpid=docs_lambda_console