Computer >> Hướng Dẫn Máy Tính >  >> Phần Mềm >> Thư Điện Tử

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Quincy Larson đã gửi bản tin email hàng tuần qua platform Mail for Good của freeCodeCamp (dựa trên Amazon SES). Gần đây ông chuyển sang SendGrid. Bài này hướng dẫn xây dựng công cụ thực hiện việc đó.

Thiết lập tài khoản SendGrid

Đăng ký SendGrid, thiết lập tài khoản. Free tier đủ cho hướng dẫn này. Khi mở rộng, có thể tăng giới hạn email.

Thiết lập Dedicated IP trên SendGrid

Mặc định SendGrid dùng shared IP. Phù hợp quy mô nhỏ, nhưng khi tăng gửi, cần dedicated IP để "sender reputation" không bị ảnh hưởng bởi user khác chia sẻ IP.

Vào Settings > IP Addresses. Lưu ý: free tier không có tùy chọn này. Tùy plan trả phí, có thể đã có 1 dedicated IP. Nếu không, nhấn "Add an IP address" để cấu hình.

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Menu cài đặt địa chỉ IP

Xác thực Email Sender trong SendGrid

LƯU Ý: Bỏ qua nếu dùng custom domain.

Để gửi từ email cá nhân, cần xác thực địa chỉ thuộc về bạn. Vào Settings > Sender Authentication > "Verify a Single Sender".

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Tùy chọn Single Sender

Xác thực Custom Domain trong SendGrid

LƯU Ý: Bỏ qua nếu KHÔNG dùng custom domain.

Để gửi từ custom mail domain, cần xác thực domain với SendGrid. Vào Settings > Sender Authentication > "Domain Authentication" > "Authenticate Your Domain". SendGrid hướng dẫn cấu hình DNS records (hướng dẫn cụ thể theo DNS provider).

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Trang cài đặt Sender Authentication

Thiết lập Reverse DNS trong SendGrid

LƯU Ý: Bỏ qua nếu không dùng custom domain.

Reverse DNS dùng bởi email provider để lookup chủ sở hữu IP. Thiết lập này cho phép provider xác minh IP gửi email kết nối với custom domain của bạn.

Trong màn hình Sender Authentication, phần "Reverse DNS". Cấu hình cho từng dedicated IP. SendGrid hướng dẫn setup.

Thiết lập Email Authentication trong SendGrid

LƯU Ý: Bỏ qua nếu không dùng custom domain.

Các email provider lớn (Gmail, Yahoo, Outlook) dùng SPF, DKIM, DMARC để xác thực sender:

  • SPF (Sender Policy Framework): xác thực IP gửi mail từ domain được ủy quyền.
  • DKIM (DomainKeys Identified Mail): dùng public key xác thực địa chỉ from chính xác, không bị spoof.
  • DMARC (Domain-based Message Authentication, Reporting, and Conformance): hướng dẫn provider xử lý khi email fail SPF/DKIM.

Luồng xác thực SendGrid hướng dẫn setup SPF và DKIM trong domain authentication, nhưng DMARC cần cấu hình thủ công.

Vào DNS hosting provider, thêm bản ghi TXT tên _dmarc.yourdomain.com (thay yourdomain.com bằng domain của bạn). Một số provider như GoDaddy tự append domain - lúc đó tên là _dmarc.

Giá trị record:

"v=DMARC1; p=none; pct=100; rua=mailto:dmarc@yourdomain.com"
  • v=DMARC: phiên bản DMARC (chỉ v1).
  • p=none: hành động khi fail DKIM/SPF. Bắt đầu bằng none để không ảnh hưởng deliverability. Sau khi xác nhận DKIM/SPF đúng, cập nhật quarantine (chuyển spam) hoặc reject (từ chối).
  • pct=100: % email fail áp dụng hành động.
  • rua=mailto:dmarc@yourdomain.com: email nhận aggregate reports. Thay bằng email bạn muốn nhận báo cáo.

Tạo Dynamic Template trong SendGrid

Công cụ hôm nay dùng dynamic template để set subject và body text. Vào Email API > Dynamic Templates.

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Menu Dynamic Templates

Tạo template mới: "freeCodeCamp SendGrid Tutorial". Ghi nhớ Template ID (cần cho công cụ sau). Nhấn "Add Version".

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Xem trước template mới

Chọn "Blank Template" > "Code Editor". Thay nội dung editor bằng:

<p>This is a test email used with the freeCodeCamp SendGrid tutorial</p> <p>Unsubscribe: {{{unsubscribeId}}}</p>

Công cụ gửi plaintext, HTML boilerplate không cần thiết.

Chú ý {{{unsubscribeId}}}. SendGrid dùng Handlebars thay thế động - chúng ta sẽ tận dụng khi xây công cụ.

Chọn Settings góc trên trái, set Subject = {{{subject}}} để load động subject từ công cụ.

Test template: chọn "Test Data", nhập JSON:

{ "unsubscribeId": "1", "subject": "Testing emails!" }

JSON yêu cầu key trong ngoặc kép!

Preview bên phải sẽ phản ánh giá trị. Nhớ nhấn Save!

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Editor và Preview hiển thị dynamic loading

Tạo API Key trong SendGrid

Bước cuối cấu hình: tạo API key cho công cụ. Quay về trang chính SendGrid > Settings > API keys > "Create API Key". Có thể cấp "Full Access" nhưng tutorial chỉ cần "Mail Send". Đặt tên mô tả, nhấn "Create and View" - lưu an toàn, không xem lại được.

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Màn hình tạo API với quyền Mail Send

Xây dựng Email Tool

Viết code gửi email. Xem code ứng dụng live, nhưng tutorial xây phiên bản đơn giản tập trung vào SendGrid API.

Phần mềm cần thiết

  • Node.js - khuyến nghị LTS
  • IDE như VSCode hoặc Atom

Tùy chọn: git cho version control. Ứng dụng live dùng MongoDB Atlas, tutorial không dùng. freeCodeCamp có curriculum MongoDB tuyệt vời.

Khởi tạo dự án

Tạo thư mục, mở với editor và terminal. Thiết lập Node project: npm init. Tạo package.json. Sửa section scripts:

"scripts": {
  "build": "tsc",
  "send": "node ./prod/send.js"
},

npm init tạo script test - có thể xóa.

build compile TypeScript sang JavaScript, send chạy ứng dụng.

Cài TypeScript: npm install --save-dev typescript. (--save-dev lưu dev dependency - TS không cần runtime).

Tạo tsconfig.json ở root:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./prod",
    "rootDir": "./src"
  }
}

Nếu xem repo tutorial, thấy thêm property excludes - riêng cho cấu trúc file tutorial, không cần.

Tạo .gitignore ở root (nếu dùng git):

/node_modules/
.env
/prod/
  • /node_modules/: bỏ qua packages (best practice).
  • .env: bỏ qua environment variables (quan trọng - không commit secrets).
  • /prod/: bỏ qua compiled JS. Cũng dùng cho email lists, tránh commit PII.

Tạo .env ở root:

SENDGRID_API_KEY=
SENDGRID_FROM=
SENDGRID_TEMPLATE_ID=
MAIL_SUBJECT=

Khi điền giá trị, bọc trong ngoặc kép. Không khoảng cách quanh =!

  • SENDGRID_API_KEY: API key vừa tạo.
  • SENDGRID_FROM: email của bạn (dùng cho field from).
  • SENDGRID_TEMPLATE_ID: id string của dynamic template.
  • MAIL_SUBJECT: subject line email. Tạm đặt "fCC Tutorial Email".

Tạo thư mục src và file send.ts bên trong.

Cài đặt dependencies

Cài package sendgrid: npm install @sendgrid/mail. Wrapper cho SendGrid API.

Cài dev dependencies: npm install --save-dev dotenv @types/node.

  • dotenv: load env variables từ .env local.
  • @types/node: type definitions cho Node.js - TS cần để hiểu built-in methods.

Viết logic

Làm việc trong /src/send.ts. Import required values.

Load dotenv và parse env variables:

import dotenv from "dotenv";
dotenv.config();

dotenv chỉ cần dev local - host online như Heroku, Repl.it xử lý env variables native.

dotenv.config() đọc .env load vào process.env.

Import modules từ SendGrid:

import sgMail, { MailDataRequired } from "@sendgrid/mail";

TS có thể auto import, nhưng import thủ công là practice tốt.

sgMail là API wrapper chính, MailDataRequired là type definition cần thiết.

Import built-in Node features xử lý file:

import path from "path";
import { createWriteStream, readFile } from "fs";

Cú pháp { } import specific modules.

  • path: locate email list files với relative paths
  • fs: read/write files

Xây dựng logic! Ứng dụng phụ thuộc essential values trong .env, validate chúng. Nếu thiếu, exit sớm tránh lỗi khi gửi.

// Check valid API key
const apiKey = process.env.SENDGRID_API_KEY;
if (!apiKey) {
  console.error("Missing SendGrid Key");
  process.exit(1);
}
// Check valid from address
const fromAddress = process.env.SENDGRID_FROM;
if (!fromAddress) {
  console.error("Missing sender email address!");
  process.exit(1);
}
// Check dynamic template ID
const sgTemplate = process.env.SENDGRID_TEMPLATE_ID;
if (!sgTemplate) {
  console.error("Missing SendGrid Template ID");
  process.exit(1);
}
// Check mail subject, fallback nếu thiếu
const subjectValue = process.env.MAIL_SUBJECT || "Fallback value - check your env!";

Cú pháp ||: nếu process.env.MAIL_SUBJECT undefined/falsy, dùng string fallback.

process.exit(1) terminate process với exit code 1 (crash do check fail).

Set SendGrid API key:

// Set SendGrid API key
sgMail.setApiKey(apiKey);

Chạy npm run build - tạo folder prod chứa compiled JS. Cấu trúc file:

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

File Tree tại thời điểm này

Nếu dùng git, rất chắc chắn folder prod không được commit.

Trong prod, tạo validEmails.csv. App đọc file này làm email list. Khởi tạo nội dung (thay your@email.com bằng email thật):

email,unsubscribeId
your@email.com,1
iama@fake.email,2

File .csv không có khoảng cách quanh dấu phẩy.

Viết code parse file. Trong src/send.ts thêm:

// Concatenate file path
const filePath = path.join(__dirname + "/../validEmails.csv");
// Read file
readFile(filePath, "utf8", (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data)
});

Dùng ES6 arrow function. Có thể dùng function declaration nếu thích.

Chạy npm run build và npm run send - sẽ thấy nội dung validEmail.csv trong terminal.

Parse string thành array objects để iterate và build email messages. Update callback:

readFile(filePath, "utf8", (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  // Parse data into object array
  const emailList = data
    .split("\n")
    .slice(1)
    .map((el) => {
      const [email, unsubscribeId] = el.split(",");
      return { email, unsubscribeId };
    });
});

[email, unsubscribeId] destructuring gán giá trị split array cho 2 biến.

  • .split("\n") tách string theo line breaks. LƯU Ý: Windows có thể cần đổi end-of-line setting từ CRLF sang LF (Windows chèn thêm linebreak characters ảnh hưởng data handling).
  • .slice(1) bỏ phần tử đầu (dòng email,unsubscribeId).
  • map chuyển mỗi string email,unsubscribeId thành object {email, unsubscribeId}.

Kết quả: array objects với properties email và unsubscribeId - dễ làm việc hơn string.

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Ví dụ output từ parsing function

Gửi email. Dưới parsing function (vẫn trong readFile callback) thêm iteration structure. Dùng .forEach để access từng giá trị:

// Iterate through emailList array
emailList.forEach((user) => {});

Tham số user là object {email, unsubscribeId}. Đặt tên user vì đại diện userdata freeCodeCamp trong full app.

Trong callback .forEach, xây message object SendGrid API cần:

emailList.forEach((user) => {
  // Message object SendGrid needs
  const message: MailDataRequired = {
    to: user.email,
    from: fromAddress,
    subject: subjectValue,
    text: "This goes away!",
    templateId: sgTemplate,
    dynamicTemplateData: {
      subject: subjectValue,
      unsubscribeId: user.unsubscribeId
    }
  }
});

Xem xét message object. MailDataRequired imported earlier dùng làm type definition - TS cảnh báo nếu thiếu required property. Chúng ta có tất cả. Ý nghĩa:

  • to: email nhận. Là email từ mỗi dòng validEmails.csv
  • from: email gửi. Set trong .env (email của bạn).
  • subject: Không bắt buộc, nhưng fallback nếu dynamic template không parse đúng subject.
  • text: Giá trị text bị ghi đè bởi template. Tuy nhiên vẫn quan trọng. SendGrid gửi plaintext hoặc html - dùng text property thay vì html đảm bảo template gửi dưới dạng plaintext. Email provider hơn hẳn flag HTML là spam, giúp tăng deliverability.
  • templateId: ID dynamic template SendGrid dùng.
  • dynamicTemplateData: Giá trị tương ứng Handlebars strings trong dynamic template.

Gửi message. Dưới message object (vẫn trong .forEach callback) thêm send call:

// Send the message we just constructed!
sgMail.send(message);

Chạy npm run build và npm run send - sẽ thấy email đẹp trong inbox!

Đến đây đã có functional email sending app. Chúc mừng! Xem tiến độ tại đây.

Đọc tiếp để xử lý bounced emails và logic send failures.

Xử lý Bounced Emails trong SendGrid

Bạn nhận ra iama@fake.email không phải email thật. SendGrid tạo bounced reports hàng ngày cho hoạt động ngày trước.

Mỗi bounced email làm hại reputation SendGrid, dẫn đến provider đánh dấu mail là spam. Cần logic ngăn gửi đến địa chỉ bounced đã biết.

Tạo bouncedEmails.csv trong prod (bên cạnh validEmails.csv). Không cần unsubscribeId, khởi tạo:

email
iama@fake.email

Thêm fake email cho demo

Quay lại send.ts. Dòng 38, dưới filePath khai báo, cấu hình path cho bouncedEmails.csv:

// Concatenate file paths for CSV files
const filePath = path.join(__dirname + "/validEmails.csv");
const bouncePath = path.join(__dirname + "/bouncedEmails.csv");

__dirname là thư mục hiện tại của file này (send.ts).

Đọc file bounced. Ngay dưới khai báo path (trước readFile hiện có), thêm logic đọc bounced files:

// Read bounce list, parse into array
readFile(bouncePath, "utf8", (err, data) => {
  if (err) {
    console.error(err);
    process.exit(1);
  }
  bounceList = data.split("\n").slice(1);

readFile asynchronous - cần wrap callback function quanh toàn bộ send logic hiện có. Đảm bảo closing }) của callback này chuyển đến cuối file.

Đọc bouncedEmails.csv, split trên new line (nhớ đảm bảo line endings là LF), bỏ dòng email. Tiếp tục send logic hiện có.

Quay lại send logic. Trong .forEach, thêm logic skip blocked emails (trước khi xây message object để tránh tạo biến thừa):

emailList.forEach((user) => {
  // Check if email has bounced
  if (bounceList.length && bounceList.includes(user.email)) {
    console.info(`Message send skipped: ${user.email}`);
    return;
  }

Nếu bounceList.csv rỗng, gọi includes sẽ throw error. Check .length trước.

Dùng early return kết thúc iteration khi bounceList chứa email đó. Ngăn gửi đến địa chỉ đã bounced trước. Chạy npm run build và npm run start - thấy output:

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Ví dụ console output cho email bị skip và email thành công

Xem tiến độ tại đây.

Capture Failed Emails trong SendGrid

Hiện tại app log error nếu email fail gửi. Dùng cho small use cases, nhưng scale lên sẽ khó identify failures và retry.

Thay vào đó, app lưu email fail vào file mới.

Tạo failedEmails.csv trong prod. Có thể rỗng. Viết code thêm header row.

Quay lại send.ts, path declarations dòng 38. Thêm path cho failedEmails.csv:

// Concatenate file paths for CSV files
const filePath = path.join(__dirname + "/validEmails.csv");
const bouncePath = path.join(__dirname + "/bouncedEmails.csv");
const failedPath = path.join(__dirname + "/failedEmails.csv");

Khác với paths khác, path này dùng cho write operation. Vì muốn write continuously khi email được xử lý, cần tạo stream. Dưới khai báo path, tạo stream và thêm header row:

// Create write stream for failed emails
const failedStream = createWriteStream(failedPath);
// Add header row
failedStream.write("email,unsubscribeId\n")

Cải thiện error handling để incorporate stream mới. Thêm write operation vào error handling trong send call:

// Send the message we just constructed!
sgMail
  .send(message)
  .then(() => {
    // Log successful send requests
    console.info(`Message send success: ${user.email}`);
  })
  .catch((err) => {
    // Log errored send requests
    console.error(err);
    console.error(`Message send failed: ${user.email}`);
    // Add email to failedEmails.csv
    failedStream.write(`${user.email},${user.unsubscribeId}\n`)
  });

Giữ console statements để thấy feedback khi script chạy.

Điều này ghi email và unsubscribeId vào failedEmails.csv đúng format - cho phép copy data vào validEmails.csv để retry.

Chúc mừng! Đã xây thành công công cụ gửi email blasts hoàn chỉnh. Xem completed code nếu muốn confirm. Đọc tiếp cho optional features.

Optional Features cho Email Tool

Tool là CLI-based (dùng command-line/terminal), ít user feedback. Có thể dùng thêm console functions để cung cấp info tiến trình script.

Thêm "checkpoints". Trước env validation, print message script started và checking variables:

console.info('Script started. Validating environment variables...')

console.info chỉ ra đây là thông điệp informational

Sau validation, print success message:

// Set SendGrid API key
sgMail.setApiKey(apiKey);
console.info('Variables confirmed!')

Trong function đọc bounced file, thêm messages start, failure, success:

console.info('Reading bounced email list...')
// Read through the bounce list, parse into 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!')

Tương tự cho valid email list:

console.info('Reading send list...')
// This is where we start reading the file!
readFile(filePath, "utf8", (err, data) => {
  if (err) {
    console.error(err);
    console.error('Failed to read send list!')
    return;
  }

Rất hay có message print khi operation hoàn tất. Tuy nhiên, thêm console.info sau .forEach loop sẽ print trước khi emails gửi xong!

Vì .send tạo network call trả về Promise, Promise có thể chưa resolve/reject trước khi iteration kết thúc.

Thay vào đó, build counter track số email sent vs total. Trước .forEach loop, thêm variables:

// Create variables for counting
const emailTotal = emailList.length;
let emailCount = 0;

Gán .length vào variable tránh đọc mỗi iteration.

Đếm bounced emails là processed, dù skip:

emailList.forEach((user) => {
  // Check if email bounced
  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;
    }
  }

Cuối cùng, logic check nếu email gửi là email cuối. Logic này vào success và error handlers của send call:

// Send the message we just constructed!
sgMail
  .send(message)
  .then(() => {
    // Log successful send requests
    console.info(`Message send success: ${user.email}`);
    // Handle email counts
    emailCount++;
    if (emailCount === emailTotal) {
      console.info(
        `Sending complete! Sent ${emailTotal} emails. Have a nice day!`
      );
    }
  })
  .catch((err) => {
    // Log errored send requests
    console.error(err);
    console.error(`Message send failed: ${user.email}`);
    // Add email to failedEmails.csv
    failedStream.write(`${user.email},${user.unsubscribeId}\n`);
    // Handle email counts
    emailCount++;
    if (emailCount === emailTotal) {
      console.info(
        `Sending complete! Sent ${emailTotal} emails. Have a nice day!`
      );
    }
  });

App hoàn chỉnh! Chạy npm run build và npm run send - thấy output terminal:

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Ví dụ console output cho ứng dụng hoàn thành.

Và nhận được vài email giống như:

Gửi bản tin email với SendGrid API: Hướng dẫn từng bước hoàn chỉnh

Ảnh mẫu kết quả test email

Xem final code tại đây, hoặc extended version cho freeCodeCamp.