หลายปีมา Quincy Larson ส่งอีเมลนิวส์เลตเตอร์รายสัปดาห์ผ่านแพลตฟอร์ม Mail for Good ของ freeCodeCamp ซึ่งขับเคลื่อนโดย Amazon SES
ล่าสุดเขาย้ายกระบวนการนี้ไปใช้ SendGrid ในบทความนี้ จะแสดงวิธีการสร้างเครื่องมือเพื่อทำสิ่งนี้
วิธีตั้งค่าบัญชี SendGrid
ขั้นแรก สมัคร SendGrid และตั้งค่าบัญชี สำหรับบทช่วยสอนนี้ แพ็กเกจฟรีเพียงพอ
เมื่อขยายแอปพลิเคชัน อาจต้องเพิ่มโควต้าอีเมลผ่านแพลตฟอร์ม
วิธีตั้งค่า Dedicated IP Address บน SendGrid
ค่าเริ่มต้น SendGrid ใช้ IP แบบแชร์สำหรับส่งอีเมล อาจเหมาะสำหรับแอปพลิเคชันขนาดเล็ก แต่เมื่อเพิ่มอัตราการส่ง จะต้องตั้งค่า dedicated IP address
เป็นความคิดที่ดี เพราะ "sender reputation" (เมตริกที่ SendGrid ใช้ประเมินความน่าเชื่อถือกับผู้ให้บริการอีเมล) จะไม่ได้รับผลกระทบเชิงลบจากการกระทำของผู้ใช้คนอื่นที่แชร์ IP เดียวกัน
เพื่อตั้งค่า dedicated IP เอง เลือก "Settings" จากเมนูด้านข้าง แล้วเลือก "IP Addresses" หมายเหตุ: ตัวเลือกนี้ไม่พร้อมใช้งานในแพ็กเกจฟรี
ขึ้นอยู่กับแพ็กเกจที่จ่าย อาจมี dedicated IP อยู่แล้ว หากไม่มี หรือต้องการเพิ่ม ให้เลือกปุ่ม "Add an IP address" เพื่อกำหนดค่า IP ใหม่
วิธียืนยันผู้ส่งอีเมลใน SendGrid
หมายเหตุ: ข้ามส่วนนี้ได้หากใช้โดเมนส่วนตัวสำหรับอีเมล
เพื่อส่งอีเมลจากอีเมลส่วนตัว ต้องยืนยันว่าอีเมลนั้นเป็นของคุณ
ในเมนูซ้าย เลือก "Settings" แล้ว "Sender Authentication" เลือก "Verify a Single Sender" เพื่อเดินผ่านขั้นตอนเพิ่มอีเมลเดียว
วิธียืนยันโดเมนส่วนตัวใน SendGrid
หมายเหตุ: ข้ามส่วนนี้ได้หากไม่ใช้โดเมนส่วนตัวสำหรับอีเมล
เพื่อส่งอีเมลจากโดเมนเมลส่วนตัว ต้องยืนยันโดเมนกับ SendGrid ไปที่หน้าจอนี้: เลือกเมนู Settings อีกครั้ง แล้วเลือก "Sender Authentication"
จะเห็นหน้าจอที่มีตัวเลือก "Domain Authentication" เลือก "Authenticate Your Domain" และ SendGrid จะนำคุณผ่านกระบวนการกำหนดค่า DNS records (พร้อมคำแนะนำเฉพาะตาม DNS provider)
วิธีตั้งค่า Reverse DNS ใน SendGrid
หมายเหตุ: ข้ามส่วนนี้ได้หากไม่ใช้โดเมนส่วนตัวสำหรับอีเมล
Reverse DNS (Domain Name System) ใช้โดยผู้ให้บริการอีเมลเพื่อค้นหาเจ้าของ IP address การตั้งค่านี้จะช่วยให้ผู้ให้บริการอีเมลตรวจสอบว่า IP ที่ส่งอีเมลเชื่อมโยงกับโดเมนส่วนตัวของคุณ
ในหน้า Sender Authentication เดียวกัน จะมีส่วน "Reverse DNS" จะมีตัวเลือกกำหนดค่า reverse DNS สำหรับ dedicated IP แต่ละอันในบัญชี - เหมือน Domain Authentication แพลตฟอร์ม SendGrid จะนำคุณผ่านการตั้งค่า
วิธีตั้งค่าการรับรองความถูกต้องของอีเมลใน SendGrid
หมายเหตุ: ข้ามส่วนนี้ได้หากไม่ใช้โดเมนส่วนตัวสำหรับอีเมล
ผู้ให้บริการอีเมลหลัก (Gmail, Yahoo, Outlook) ใช้หลายวิธีรับรองผู้ส่ง: SPF, DKIM, และ DMARC
- SPF (Sender Policy Framework) ยืนยันว่า IP ที่ส่งเมลจากโดเมนของคุณได้รับอนุญาต
- DKIM (DomainKeys Identified Mail) ใช้สตริงคีย์สาธารณะรับรองว่า from address ถูกต้องและไม่ถูกปลอม
- DMARC (Domain-based Message Authentication, Reporting, and Conformance) เป็นชุดคำสั่งบอกผู้ให้บริการอีเมลว่าต้องตอบสนองอย่างไรเมื่ออีเมลล้มเหลวการตรวจสอบ SPF หรือ DKIM
ขั้นตอนการรับรองของ SendGrid จะนำคุณตั้งค่า SPF และ DKIM ระหว่างการยืนยันโดเมน แต่ DMARC ต้องกำหนดค่าเอง
ไปที่ DNS hosting provider และเข้าถึงการจัดการ DNS เพิ่มระเบียน TXT ใหม่ ชื่อ _dmarc.yourdomain.com (แทนที่ yourdomain.com ด้วยโดเมนของคุณ)
หมายเหตุ: บางผู้ให้บริการอย่าง GoDaddy จะเพิ่มโดเมนอัตโนมัติ - ในกรณีนี้ ชื่อควรเป็น _dmarc
ค่า value ของระเบียนควรมีโครงสร้างคล้าย:
"v=DMARC1; p=none; pct=100; rua=mailto:dmarc@yourdomain.com"
v=DMARCระบุเวอร์ชันกฎ DMARC (ปัจจุบันมีเพียง Version 1)p=noneระบุการกระทำเมื่ออีเมลล้มเหลว DKIM หรือ SPF ควรเริ่มที่noneเพื่อไม่ให้กระทบ deliverability หลังยืนยัน DKIM และ SPF ถูกต้องแล้ว สามารถอัปเดตเป็นquarantine(ส่งไปสแปม) หรือreject(ปฏิเสธ)pct=100ระบุเปอร์เซ็นต์อีเมลที่ล้มเหลวที่จะนำการกระทำไปใช้rua=mailto:dmarc@yourdomain.comอีเมลสำหรับรับรายงานสรุป แทนที่ด้วยอีเมลที่ต้องการรับรายงาน
วิธีสร้าง Dynamic Template ใน SendGrid
เครื่องมือที่จะสร้างวันนี้ใช้ฟีเจอร์ dynamic template ของ SendGrid เพื่อตั้งค่าหัวข้อและเนื้อหาอีเมล ไปที่ "Email API" ในเมนูด้านข้าง แล้วเลือก "Dynamic Templates"
จะเห็นหน้าจอพร้อมสร้าง dynamic template แรก เลือก "Create a Dynamic Template"
ตั้งชื่อเทมเพลต: "freeCodeCamp SendGrid Tutorial" SendGrid จะเพิ่มเทมเพลตในรายการ เลือกเทมเพลตเพื่อดู Template ID (จดไว้ จะใช้ในเครื่องมือภายหลัง) และคลิก "Add Version"
เลือก "Blank Template" แล้วเลือก "Code Editor" จะเห็นมุมมองเอดิเตอร์ SendGrid ใช้ HTML สร้างเนื้อหาอีเมล - แต่เครื่องมือของเราจะส่ง plain text
แทนที่เนื้อหาในเอดิเตอร์ด้วยโค้ด:
<p>This is a test email used with the freeCodeCamp SendGrid tutorial</p>
<p>Unsubscribe: {{{unsubscribeId}}}</p>
สังเกตว่าเราเพิ่ม {{{unsubscribeId}}} SendGrid ใช้ Handlebars แทนค่าแบบไดนามิก - เราจะใช้ประโยชน์จากฟีเจอร์นี้
เลือก settings จากมุมซ้ายบน - อาจตั้งชื่อเวอร์ชันเทมเพลตได้ แต่ฟิลด์ "Subject" คือสิ่งที่ต้องแก้ ตั้งค่าเป็น {{{subject}}} เพื่อโหลดค่าหัวข้อจากเครื่องมือ
เพื่อทดสอบ dynamic template เลือก "Test Data" จากเมนูบน ใส่ JSON:
{ "unsubscribeId": "1", "subject": "Testing emails!" }
ควรเห็น preview ด้านขวาแสดงค่าเหล่านี้ในเทมเพลต จำไว้กด Save!
วิธีสร้าง API Key ใน SendGrid
ขั้นสุดท้ายในการตั้งค่าบัญชี SendGrid คือ สร้าง API key สำหรับเครื่องมือ
คลิกลูกศรย้อนกลับมุมซ้ายบนกลับหน้าหลัก SendGrid เลือก "Settings" และ "API keys" เลือก "Create API Key" สร้างคีย์ใหม่ อาจให้ "Full Access" แต่สำหรับบทช่วยสอน ต้องการแค่ "Mail Send"
ตั้งชื่อคีย์ให้เข้าใจง่าย เพื่อจำได้ว่าทำไม ถ้าตั้งค่าสิทธิ์เสร็จ เลือก "Create and View" เพื่อสร้างคีย์ - บันทึกไว้ที่ปลอดภ เพราะจะไม่ดูได้อีก
วิธีสร้างเครื่องมือส่งอีเมล
ถึงเวลาเขียนโค้ดส่งอีเมลจริง สามารถดูโค้ดแอปพลิเคชันจริงได้ แต่สำหรับบทช่วยสอน จะสร้างเวอร์ชันลดรูปเพื่อโฟกัสที่ SendGrid API
ซอฟต์แวร์ที่จำเป็นสำหรับสคริปต์แคมเปญอีเมลแบบกำหนดเอง
ต้องมีเครื่องมือต่อไปนี้:
- Node.js - แนะนำ LTS version
- IDE เช่น VSCode หรือ Atom
อาจอยากมี git สำหรับควบคุมเวอร์ชัน
เครื่องมือจริงใช้ MongoDB Atlas cluster แต่ตัวอย่างบทช่วยสอนจะไม่ใช้ หากไม่คุ้นเคย MongoDB freeCodeCamp มีหลักสูตรสอนการตั้งค่าและใช้งาน
วิธีเริ่มต้นโปรเจกต์
สร้างโฟลเดอร์ทำงาน เปิดกับเอดิเตอร์และเทอร์มินัล
ตั้งค่าเป็น Node project ด้วย npm init ในเทอร์มินัล จะสร้าง package.json ไฟล์หลักของ Node app
ค่าเริ่มต้นใช้ได้ แต่อาจต้องแก้ส่วน scripts:
"scripts": {
"build": "tsc",
"send": "node ./prod/send.js"
},
npm init สร้างสคริปต์ test - สามารถลบได้build ใช้คอมไพล์ TypeScript เป็น JavaScript send ใช้รันแอปพลิเคชัน
ติดตั้งและตั้งค่า TypeScript ถ้ายังไม่คุ้นเคย TypeScript คือ superset ของ JavaScript พร้อม type definitions แข็งแกร่งกว่า และตรวจสอบข้อผิดพลาดขณะคอมไพล์
ติดตั้ง TypeScript: npm install --save-dev typescript (flag --save-dev บันทึกเป็น dev dependency - TypeScript ไม่จำเป็นตอนรันจริง จึงลบได้ใน production)
TypeScript ต้องการไฟล์ config ตัวเอง สร้าง tsconfig.json ใน root directory:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./prod",
"rootDir": "./src"
}
}
excludes เพิ่มเติม สำหรับโครงสร้างไฟล์เฉพาะบทช่วยสอน ไม่จำเป็นต้องมีไม่ลึกลงรายละเอียดการตั้งค่า หากต้องการข้อมูลเพิ่ม TypeScript มีเอกสารครบถ้วน
หากใช้ git สร้าง .gitignore ใน root directory ใส่:
/node_modules/
.env
/prod/
/node_modules/ไม่สนใจแพ็กเกจที่ติดตั้ง - best practice สำหรับ version control.envไม่สนใจไฟล์ environment variables - สำคัญมาก ไม่เคย commit secrets/prod/ไม่สนใจไฟล์ JavaScript ที่คอมไพล์แล้ว จะใช้โฟลเดอร์นี้สำหรับรายชื่ออีเมล จึงสำคัญที่จะหลีกเลี่ยงการ commit ข้อมูลส่วนตัวโดยบังเอิญ
สร้างไฟล์ .env ใน root project directory โหลด environment variables:
SENDGRID_API_KEY=
SENDGRID_FROM=
SENDGRID_TEMPLATE_ID=
MAIL_SUBJECT=
=!SENDGRID_API_KEYคือ API key ที่สร้างข้างต้นSENDGRID_FROMคืออีเมลของคุณ (ใช้สำหรับฟิลด์from)SENDGRID_TEMPLATE_IDคือสตริงidของ dynamic template ที่สร้างก่อนหน้าMAIL_SUBJECTจะเป็นหัวข้ออีเมล ตั้งเป็น "fCC Tutorial Email" ไปก่อน
สุดท้าย สร้างโฟลเดอร์ src ใน root และสร้าง send.ts ในโฟลเดอร์นั้น
วิธีติดตั้ง Dependencies
ติดตั้งแพ็กเกจ sendgrid Node.js: npm install @sendgrid/mail แพ็กเกจนี้เป็น wrapper สำหรับ SendGrid API จะช่วยให้เรียก API ส่งอีเมลง่ายขึ้น
ติดตั้ง dev dependencies: npm install --save-dev dotenv @types/node
dotenvโหลด environment variables จาก.envในเครื่อง@types/nodeให้ type definitions สำหรับ Node.js - TypeScript ใช้เข้าใจโครงสร้างเมธอดและฟังก์ชัน built-in
วิธีเขียน Logic
ทำงานใน /src/send.ts - ส่วนหลักของแอป เริ่มจาก import ค่าที่ต้องการ
โหลด dotenv และ parse environment variables:
import dotenv from "dotenv";
dotenv.config();
dotenv จำเป็นเฉพาะ local development - โฮสต์ออนไลน์อย่าง Heroku, Repl.it จัดการ env variables เองได้dotenv.config() อ่าน .env และโหลดค่าเข้า process.env ของ Node
Import โมดูลจาก SendGrid:
import sgMail, { MailDataRequired } from "@sendgrid/mail";
sgMail คือ API wrapper หลัก MailDataRequired คือ type definition ที่ต้องการ
Import ฟีเจอร์ Node built-in สำหรับจัดการไฟล์:
import path from "path";
import { createWriteStream, readFile } from "fs";
pathใช้หาไฟล์รายชื่ออีเมลด้วย relative pathsfsใช้ read/write ไฟล์เหล่านั้น
เริ่มสร้าง logic! แอปต้องการค่าสำคัญใน .env ต้อง validate ก่อน ถ้าขาด ให้ exit ทันทีเพื่อไม่ให้ error ตอนส่งอีเมล
// ตรวจสอบ API key ที่ถูกต้อง
const apiKey = process.env.SENDGRID_API_KEY;
if (!apiKey) {
console.error("Missing SendGrid Key");
process.exit(1);
}
// ตรวจสอบ from address
const fromAddress = process.env.SENDGRID_FROM;
if (!fromAddress) {
console.error("Missing sender email address!");
process.exit(1);
}
// ตรวจสอบ dynamic template ID
const sgTemplate = process.env.SENDGRID_TEMPLATE_ID;
if (!sgTemplate) {
console.error("Missing SendGrid Template ID");
process.exit(1);
}
// ตรวจสอบ mail subject ถ้าขาดไม่ต้อง exit ใช้ค่า fallback
const subjectValue = process.env.MAIL_SUBJECT || "Fallback value - check your env!";
|| บอกว่า ถ้า process.env.MAIL_SUBJECT เป็น undefined หรือ falsy ใช้สตริงแทนprocess.exit(1) บอก Node ให้จบ process ด้วย exit code 1 แสดงว่าแอป crash จากการตรวจสอบล้มเหลว
SendGrid ต้องการตั้งค่า API key เพิ่ม function call ตั้งค่าคีย์:
// ตั้งค่า SendGrid API key
sgMail.setApiKey(apiKey);
ก่อนไปต่อ รัน npm run build - จะสร้างโฟลเดอร์ prod พร้อม JavaScript ที่คอมไพล์แล้ว ควรเห็นโครงสร้างไฟล์:
ที่จุดนี้ หากใช้ git ให้แน่ใจมากว่า prod folder จะไม่ถูก commit
ในโฟลเดอร์ prod สร้าง validEmails.csv แอปจะอ่านรายชื่ออีเมลจากไฟล์นี้ เริ่มต้นไฟล์ด้วย (แทนที่ your@email.com ด้วยอีเมลจริง):
email,unsubscribeId
your@email.com,1
iama@fake.email,2
.csv ไม่มีเว้นวรรครอบ commaเขียนโค้ด parse เป็นรายชื่ออีเมล! ใน src/send.ts เพิ่ม:
// รวม file path สำหรับ valid email file
const filePath = path.join(__dirname + "/../validEmails.csv");
// เริ่มอ่านไฟล์!
readFile(filePath, "utf8", (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data)
});
function declarations ได้เช่นกันรัน npm run build และ npm run send ควรเห็นเนื้อหา validEmail.csv ในเทอร์มินัล
ตอนนี้ parse string เป็น array of objects เพื่อวนลูปสร้างข้อความอีเมล อัปเดต callback function:
// เริ่มอ่านไฟล์!
readFile(filePath, "utf8", (err, data) => {
if (err) {
console.error(err);
return;
}
// parse data เป็น object array
const emailList = data
.split("\n")
.slice(1)
.map((el) => {
const [email, unsubscribeId] = el.split(",");
return { email, unsubscribeId };
});
});
[email, unsubscribeId] ใช้ destructuring กำหนดค่าจาก split array.split("\n")แยก string ด้วย line breaks หมายเหตุ: บน Windows อาจต้องเปลี่ยน end-of-line setting ของvalidEmails.csvจากCRLFเป็นLF(Windows ใส่ linebreak เพิ่มที่จะกระทบข้อมูล).slice(1)ลบ element แรก (บรรทัดemail,unsubscribeId)mapแปลงแต่ละemail,unsubscribeIdstring เป็น object{email, unsubscribeId}
ผลลัพธ์คือ array of objects พร้อม properties email และ unsubscribeId - ทำงานง่ายกว่า string
ถึงเวลาส่งอีเมล เพิ่มโครงสร้าง iteration method (ยังอยู่ใน readFile callback) ใช้ .forEach:
// วนลูป emailList array
emailList.forEach((user) => {});
user คือ object {email, unsubscribeId} เรียกว่า user เพราะแทน userdata ของ freeCodeCamp ในแอปเต็มรูปแบบใน callback ของ .forEach สร้าง message object ที่ SendGrid API คาดหวัง:
// วนลูป emailList array
emailList.forEach((user) => {
// message object ที่ SendGrid ต้องการ
const message: MailDataRequired = {
to: user.email,
from: fromAddress,
subject: subjectValue,
text: "This goes away!",
templateId: sgTemplate,
dynamicTemplateData: {
subject: subjectValue,
unsubscribeId: user.unsubscribeId
}
}
});
ดู message object ใกล้ชิด MailDataRequired ที่ import มาใช้เป็น type definition TypeScript จะเตือนถ้าขาด required property เรามีครบแล้ว แต่แต่ละอย่างหมายถึงอะไร?
to:อีเมลปลายทาง - จะเป็นemailจากแต่ละบรรทัดvalidEmails.csvfrom:อีเมลผู้ส่ง - ตั้งใน.env(ควรเป็นอีเมลของคุณ)subject:ฟิลด์นี้ไม่จำเป็น แต่ให้ fallback หาก dynamic template ไม่ parse subject ได้text:ค่านี้ถูกเขียนทับโดย template แต่สำคัญที่จะใช้ SendGrid ส่งเป็นplaintextหรือhtml- ใช้textแทนhtmlเพื่อให้ template ส่งเป็นplaintextผู้ให้บริการอีเมล มัก flag HTML ว่า spam ดังนั้นช่วยเพิ่ม deliverabilitytemplateId:ID ของ dynamic template ที่ SendGrid ควรใช้dynamicTemplateData:ค่าที่สอดคล้องกับ Handlebars strings ที่ตั้งใน dynamic template
สร้าง message แล้ว ต่อไปส่งมัน เพิ่ม send call (ยังอยู่ใน .forEach callback):
// ส่ง message ที่สร้างแล้ว!
sgMail.send(message);
จะส่งข้อความไปทุกอีเมลใน validEmails.csv แต่โค้ดรันเงียบๆ ไม่รู้ว่าส่งสำเร็จหรือไม่ เพิ่ม error handling
.send() return Promise ใช้ .then().catch() จัดการ:
// ส่ง message ที่สร้างแล้ว!
sgMail.send(message)
.then(() => {
// log successful sends
console.info(`Message send success: ${user.email}`)
})
.catch((err) => {
// log failed sends
console.error(err);
console.error(`Message send failed: ${user.email}`)
});
รัน npm run build และ npm run send ควรเห็นอีเมลสวยงามใน inbox!
ตอนนี้มีแอปพลิเคชันส่งอีเมลที่ทำงานแล้ว ยินดีด้วย! ดูความคืบหน้าจนถึงที่นี่ได้
อ่านต่อเพื่อดูวิธาจัดการ bounced emails และ logic เพิ่มเติมสำหรับการส่งล้มเหลว
วิธาจัดการ Bounced Emails ใน SendGrid
สังเกตว่า iama@fake.email ไม่ใช่อีเมลจริง SendGrid สร้าง bounced reports รายวันสำหรับกิจกรรมวันก่อน
อีเมลที่ bounce ทุกอันทำลาย reputation ของ SendGrid และอาจทำให้ผู้ให้บริการอีเมล mark เมลของคุณเป็น spam ต้องเพิ่ม logic ป้องกันการส่งไปที่ bouncing addresses ที่รู้จัก
สร้าง bouncedEmails.csv ใน prod folder (ข้าง validEmails.csv) ไม่ต้องมี unsubscribeId เริ่มต้นด้วย:
email
iama@fake.email
กลับไป send.ts บรรทัด 38 ใต้ filePath เดิม เพิ่ม path สำหรับ bouncedEmails.csv:
// รวม file paths สำหรับ CSV files
const filePath = path.join(__dirname + "/validEmails.csv");
const bouncePath = path.join(__dirname + "/bouncedEmails.csv");
send.ts)อ่านไฟล์ bounce list เพิ่ม logic อ่านไฟล์ bounced (ก่อน readFile เดิม) เพิ่มทันทีใต้ file path declarations:
// อ่าน bounce list, parse เป็น array
readFile(bouncePath, "utf8", (err, data) => {
if (err) {
console.error(err);
process.exit(1);
}
bounceList = data.split("\n").slice(1);
readFile เป็น asynchronous - ต้อง wrap callback function รอบ ทั้งหมดของ send logic ที่มีอยู่ ย้าย closing }) ของ callback ไปท้ายไฟล์
อ่าน bouncedEmails.csv split โดย new line (จำไว้ว่า line endings ต้องเป็น LF) ลบบรรทัด email แล้วต่อ send logic เดิม
กลับไป send logic ใน .forEach เพิ่ม logic ข้าม blocked emails (ก่อนสร้าง message object):
// วนลูป emailList array
emailList.forEach((user) => {
// ตรวจสอบว่าอีเมลเคย bounce หรือไม่
if (bounceList.length && bounceList.includes(user.email)) {
console.info(`Message send skipped: ${user.email}`);
return;
}
bounceList.csv ว่าง เรียก includes จะ error ตรวจ .length ก่อนใช้ early return จบ iteration นั้นเมื่อ bounceList มีอีเมลนั้น ป้องกันการพยายามส่งไปที่ bounce แล้ว รัน npm run build และ npm run start ควรเห็น output:
ดูความคืบหน้าจนถึงที่นี่
วิธีจับ Failed Emails ใน SendGrid
ปัจจุบันแอปจะ log error ถ้าส่งล้มเหลว ใช้ได้สำหรับ use cases เล็ก แต่ scale ขึ้น จะยากขึ้นในการระบุ failures และพยายามส่งใหม่
แทนที่จะทำเช่นนั้น ให้แอปบันทึกอีเมลเหล่านั้นในไฟล์ใหม่
สร้าง failedEmails.csv ใน prod folder ไฟล์นี้ว่างได้ เขียนโค้ดเพิ่ม header row
กลับไป send.ts path declarations บรรทัด 38 เพิ่ม path สำหรับ failedEmails.csv:
// รวม file paths สำหรับ CSV files
const filePath = path.join(__dirname + "/validEmails.csv");
const bouncePath = path.join(__dirname + "/bouncedEmails.csv");
const failedPath = path.join(__dirname + "/failedEmails.csv");
ไม่เหมือน path อื่นๆ path นี้ใช้สำหรับ write operation เนื่องจากต้องเขียนต่อเนื่องขณะประมวลผลอีเมล ต้องสร้าง stream สร้าง stream และเพิ่ม header row ใต้ path declarations:
// สร้าง write stream สำหรับ failed emails
const failedStream = createWriteStream(failedPath);
// เพิ่ม header row
failedStream.write("email,unsubscribeId\n")
ปรับปรุง error handling ให้รวม stream ใหม่ เพิ่ม write operation ใน error handling ของ send call:
// ส่ง message ที่สร้างแล้ว!
sgMail
.send(message)
.then(() => {
// log successful sends
console.info(`Message send success: ${user.email}`);
})
.catch((err) => {
// log failed sends
console.error(err);
console.error(`Message send failed: ${user.email}`);
// เพิ่มอีเมลนั้นใน failedEmails.csv
failedStream.write(`${user.email},${user.unsubscribeId}\n`)
});
จะเขียน email และ unsubscribeId ลง failedEmails.csv ในรูปแบบที่ถูกต้อง - ทำให้ copy data ไป validEmails.csv เพื่อส่งลองใหม่ได้
ยินดีด้วย! สร้างเครื่องมือส่งอีเมล blast ที่สมบูรณ์และทำงานได้แล้ว ดู completed code ได้ หากต้องการยืนยัน แต่อ่านต่อสำหรับฟีเจอร์ "nice to have" เพิ่มเติม
ฟีเจอร์เพิ่มเติมสำหรับเครื่องมืออีเมล
เพราะเครื่องมือเป็น CLI-based (ใช้ใน command-line interface หรือเทอร์มินัล) ไม่มี user feedback มาก สามารถใช้ console functions เพิ่มเติมเพื่อให้ข้อมูลความคืบหน้า
เพิ่ม "checkpoints" ก่อน validation environment variables พิมพ์ข้อความว่า script เริ่มต้นและกำลังตรวจสอบ:
console.info('Script started. Validating environment variables...')
หลัง validation พิมพ์ข้อความสำเร็จ:
// ตั้งค่า SendGrid API key
sgMail.setApiKey(apiKey);
console.info('Variables confirmed!')
ใน function อ่าน bounced file เพิ่มข้อความเริ่มต้น ล้มเหลว สำเร็จ:
console.info('Reading bounced email list...')
// อ่าน bounce list, parse เป็น array
readFile(bouncePath, "utf8", (err, data) => {
if (err) {
console.error(err);
console.error('Failed to read bounced emails!')
process.exit(1);
}
bounceList = data.split("\n").slice(1);
console.info('Bounced emails read!')
เหมือนกันสำหรับ valid email list:
console.info('Reading send list...')
// เริ่มอ่านไฟล์!
readFile(filePath, "utf8", (err, data) => {
if (err) {
console.error(err);
console.error('Failed to read send list!')
return;
}
อยากมีข้อความ print เมื่อ operation เสร็จ แต่ถ้าเพิ่ม console.info หลัง .forEach loop จะ print ก่อน ที่อีเมลจะส่งเสร็จ!
เพราะ .send สร้าง network call return Promise และ Promise อาจยังไม่ resolve/reject ก่อนที่ iteration จบ
แทนที่จะทำเช่นนั้น สร้าง counter ติดตามจำนวนอีเมลที่ส่ง vs ทั้งหมด เพิ่มตัวแปรก่อน .forEach loop:
// สร้างตัวแปรนับ
const emailTotal = emailList.length;
let emailCount = 0;
นับ bounced emails ว่า processed แม้จะข้าม:
// วนลูป emailList array
emailList.forEach((user) => {
// ตรวจสอบ bounce
if (bounceList.includes(user.email)) {
console.info(`Message send skipped: ${user.email}`);
emailCount++;
if (emailCount === emailTotal) {
console.info(
`Sending complete! Sent ${emailTotal} emails. Have a nice day!`
);
return;
}
}
เพิ่ม logic เช็คว่าเป็นอีเมลสุดท้ายใน success และ error handlers:
// ส่ง message ที่สร้างแล้ว!
sgMail
.send(message)
.then(() => {
// log successful sends
console.info(`Message send success: ${user.email}`);
// จัดการ email counts
emailCount++;
if (emailCount === emailTotal) {
console.info(
`Sending complete! Sent ${emailTotal} emails. Have a nice day!`
);
}
})
.catch((err) => {
// log failed sends
console.error(err);
console.error(`Message send failed: ${user.email}`);
// เพิ่มใน failedEmails.csv
failedStream.write(`${user.email},${user.unsubscribeId}\n`);
// จัดการ email counts
emailCount++;
if (emailCount === emailTotal) {
console.info(
`Sending complete! Sent ${emailTotal} emails. Have a nice day!`
);
}
});
แอปสมบูรณ์แล้ว! รัน npm run build และ npm run send ควรเห็น output:
และควรได้รับอีเมลคล้ายนี้:
ดู final code ได้ หรือดู extended version ที่สร้างสำหรับ freeCodeCamp