---
url: https://docs.nemo12.com/reference/emails/ielts-learner.md
description: >-
  Chín thư gửi chính IELTS Learner, thư Learning Plan tuần và tháng, và báo cáo
  tiến độ IELTS hằng ngày cho chủ dự án.
---

# Email · Thư cho IELTS Learner

Một phần của [Email](./index.md). Chín thư gửi chính IELTS Learner, thư Learning Plan tuần và tháng, và báo cáo tiến độ IELTS hằng ngày cho chủ dự án.

## Chín thư cho IELTS Learner (SRC-737, thêm thư chào ở SRC-927)

Chỉ đạo chủ dự án 2026-09-15: *"Hệ thống sẽ gửi kha khá email cho IELTS Learners, tùy vào từng giai
đoạn nhé. Có nhiều email chúc mừng, và ghi nhận từng cụm việc của IELTS Learners."*

**Đây là lần đầu Nemo12 gửi thư thẳng cho học sinh.** Mười loại thư ở trên kể về một đứa trẻ cho bố
mẹ nghe; chín loại dưới đây nói với chính người học, xưng "bạn". Địa chỉ lấy từ `users.email` qua
`learners.user_id`, nên người học chưa có tài khoản thì không nhận thư nào.

Mã: `workers/api/src/modules/email/templatesIelts.ts` (khuôn thư) và
`workers/api/src/modules/email/ieltsCampaigns.ts` (lượt quét).

| # | Loại (`id`) | Kích hoạt (dữ liệu thật trong D1) | Khoá chống trùng |
| --- | --- | --- | --- |
| 1 | `ielts-goal-set` | Mục tiêu đang hoạt động, tạo trong 2 ngày | `goal.id` |
| 2 | `ielts-baseline-set` | Điểm xuất phát đang hoạt động, tạo trong 2 ngày, và đã có mục tiêu | `baseline.id` |
| 3 | `ielts-cluster-done` | Đủ **cả bốn** mảnh: mục tiêu + điểm xuất phát + tờ khai + một lần làm bài chẩn đoán | `goal.id` |
| 4 | `ielts-plan-ready` | Mục tiêu có từ một mốc trở lên | `goal.id:<số mốc>` |
| 5 | `ielts-milestone-hit` | Ngày mốc đã tới **và** số đo mới nhất không thấp hơn mức mốc | `milestone.id` |
| 6 | `ielts-skill-up` | Lần đo mới tăng ≥ 0.5 ở một kỹ năng so với lần đo trước | `baseline.id:<kỹ năng>` |
| 7 | `ielts-countdown` | Ngày hẹn còn đúng **30 / 14 / 3** ngày | `goal.id:<mốc>` |
| 8 | `ielts-quiet` | 10 ngày không có lượt làm bài IELTS nào, và ngày hẹn chưa qua | `learner.id:<tuần>` |
| 9 | `ielts-welcome` | Learner khai lối vào ở Learning Setup **lần đầu** (`start_mode` đi từ NULL sang có giá trị) | `learner.id` |

Thư 9 khác tám thư kia ở ba chỗ, và cả ba đều theo từ một tính chất: nó là thư **giao dịch**, trả
lời một khoảnh khắc chứ không kể một trạng thái. Nên nó (a) đi NGAY trong request qua `waitUntil`
chứ không đợi lượt 12:00, (b) miễn trần ngày, (c) không tính vào trần 2 thư/7 ngày của nhóm. Mốc
gửi là "chọn xong lối vào" chứ không phải "mở trang `/ielts`": mở trang không phải một ý định, và
một thư chào gửi cho người bấm nhầm link là thư rác. Ba việc đầu tiên trong thư dựng từ chính lối
vào learner vừa khai — người khai "đã có điểm" mà bị bảo đi làm bài đo đầy đủ sẽ thấy ngay là hệ
thống không đọc thứ họ vừa khai.

### Tên learner trên dòng tiêu đề (SRC-742)

Chỉ đạo 2026-09-16: hộp thư của phụ huynh nhận bản đối chứng của **mọi** thư, nên hai lá cùng dạng
của hai đứa con khác nhau trông y hệt nhau. Hai thư bước đầu nay mang tên ngay trong tiêu đề:

| Trước | Sau |
| --- | --- |
| `Mục tiêu IELTS 6.0 của bạn đã được ghi lại` | `Mục tiêu IELTS 6.0 của Nguyễn Tất Đắc đã được ghi lại` |
| `Lộ trình IELTS của bạn đã sẵn sàng` | `Lộ trình IELTS của Nguyễn Tất Đắc đã sẵn sàng` |

Tên đứng ngay sau "của" nên khi learner chưa khai tên, `addressee()` trả về `"bạn"` và câu vẫn đúng
tiếng Việt — không cần một nhánh `if`. Tên đi qua `subjectName()`: xoá ký tự xuống dòng (tiêu đề là
một **header** của thư, và tên là chữ người dùng tự nhập) và cắt ở 60 ký tự (Gmail trên điện thoại
hiện khoảng 35 ký tự đầu; một cái tên dài đẩy phần mang tin ra khỏi màn, tức là mất đúng thứ vừa
thêm). Tiêu đề **không** qua `escapeHtml`: nó là chữ trơn, `&amp;` trong hộp thư là lỗi nhìn thấy được.

### Mọi thư gọi đích danh một người (SRC-1004)

Chỉ đạo 22.09.2026: *mọi email đều có tên của người nhận trên tiêu đề*. SRC-742 mới làm hai lá; nay
là cả 23 loại. Mười một lá trước đó không gọi ai:

| Thư | Tiêu đề mới (bản mẫu) |
| --- | --- |
| `ielts-welcome` | Chào mừng Minh đến với NEMO IELTS |
| `ielts-baseline-set` | Đã có điểm xuất phát của Minh |
| `ielts-cluster-done` | Minh đã xong cụm: dựng hồ sơ IELTS |
| `ielts-milestone-hit` | Minh đã qua mốc 05.10.2026 |
| `ielts-skill-up` | Writing của Minh lên 5.5 |
| `ielts-countdown` | Còn 14 ngày tới ngày Minh đã hẹn |
| `ielts-quiet` | Minh ơi, quay lại chỗ đang dở nhé |
| `event-confirmation` | anh Đắc đã giữ chỗ: … |
| `setup-nudge` | anh Đắc ơi, còn một bước nữa là Nemo12 hiểu được con bạn |
| `referral-invited` / `-joined` / `-progress` | tên người nhận (`name`) đứng đầu hoặc sau "của" |

Hai quyết định của chủ dự án (23.09.2026), ghi ra để khỏi bàn lại:

* **Thư bố mẹ giữ tên CON, không chèn thêm tên phụ huynh.** Bảy lá (`daily-digest`,
  `weekly-report`, `milestone`, …) đã gọi đích danh một người trong nhà. Chèn "anh Đắc ơi," đẩy
  phần mang tin ra khỏi ~35 ký tự đầu mà Gmail điện thoại hiện — đúng cái bẫy ở mục SRC-742.
* **`ielts-admin-daily` miễn trừ.** Thư vận hành gửi chủ dự án, dữ liệu không có tên người nhận,
  và tiêu đề phải mở đầu bằng ngày + số lượt để đọc được từ danh sách hộp thư.

**Hai cổng giữ luật này**, vì một luật về chữ thì không có gì nhắc người viết lá thứ 24:

| Cổng | Bắt gì |
| --- | --- |
| `subjectNames.test.ts` | Mỗi bản mẫu **tự khai** `namedInSubject` (tên nó gọi, hoặc `{ exempt: "<lý do>" }`); test đối chiếu với tiêu đề thật. Trường bắt buộc, nên thêm bản mẫu mà quên khai là không biên dịch được |
| `scripts/check-email-samples.mjs` (trong `check:code`) | Tập `templateId` trong mã phải **trùng khít** tập id bản mẫu. Thiếu nó thì cổng trên bị bước vòng qua: một thư gửi đi mà không có bản mẫu là thư test không nhìn thấy |

### Nhận diện NEMO IELTS (SRC-1004)

Chỉ đạo 22-23.09.2026: *thư gửi từ NEMO IELTS thì format cũng phải thể hiện rõ là NEMO IELTS*, và
*mãi chưa thấy email nào xuất phát từ NEMO IELTS*. Câu thứ hai không phải vì thư không được gửi —
chủ dự án đã nhận `ielts-baseline-set` hôm trước — mà vì mọi lá đều ký **"Nemo12"**, và trong danh
sách Gmail tên người gửi là thứ người ta đọc TRƯỚC tiêu đề.

### Mỗi thư thuộc một chương trình (SRC-1091)

Bước SRC-1004 ở trên mới làm đúng các lá `ielts-*`. Chủ dự án chụp hộp thư ngày 27.09.2026: learner
IELTS vẫn nhận **thư tổng kết hằng ngày** khung Nemo12 ("Thư Nguyễn hôm qua học 2 câu", thanh môn
học in id thô `ielts`, nút "Xem chi tiết trong **Marlins**") và **thư chào phụ huynh** ("Chào mừng
... đến với Nemo12 🐟", "thêm con vào tài khoản"). Cùng một người, nửa hộp thư nói NEMO IELTS, nửa
còn lại nói chuyện học sinh phổ thông.

Nay mỗi lá thư thuộc đúng một **chương trình**, và một bảng duy nhất (`PROGRAMS` trong
`program.ts`) quyết định mọi thứ nhận diện của nó. Kênh gửi và khung thư cùng đọc bảng ấy, nên đầu
thư và người gửi không thể nói hai chương trình khác nhau:

| Chỗ | `nemo12` | `ielts` | `sat` |
| --- | --- | --- | --- |
| Người gửi | `NEMO <support@nemo12.com>` | `NEMO IELTS <ielts@nemo12.com>` | `NEMO SAT <sat@nemo12.com>` |
| Đầu thư | Nemo12 · CÙNG CON HỌC CHẮC | NEMO IELTS · LUYỆN THI CÙNG NEMO12 | NEMO SAT · LUYỆN THI CÙNG NEMO12 |
| Chân thư | nemo12.com | learn.nemo12.com/ielts | learn.nemo12.com/sat |

**Chương trình đọc từ tiền tố của templateId**: `ielts-*`, `sat-*`, còn lại là `nemo12` (bỏ
`sample:` để thư mẫu trông y hệt thư thật). Thư nào nội dung phụ thuộc chương trình thì nơi gửi chọn
đúng templateId. Nhờ vậy các cổng đã có (bản mẫu, tên người nhận, nhận diện) phủ luôn lá mới mà
không phải viết thêm luật.

**Thư tổng kết hằng ngày** có ba loại: `daily-digest`, `ielts-daily-digest`, `sat-daily-digest`.
Chọn loại theo các môn có bằng chứng trong ngày: chỉ là IELTS khi **mọi** môn trong ngày đều là
`ielts`. Một học sinh học cả Toán lẫn IELTS vẫn nhận thư chung, vì thư ấy nói về cả hai. Bản IELTS
nói "luyện IELTS" ở tiêu đề, chia bảng theo bốn kỹ năng (đọc từ `SKILL_EXPR`, định nghĩa dùng chung
với màn luyện tập), dẫn nút về sân luyện. Writing và Speaking hiện **số bài đã nộp** chứ không hiện
tỉ lệ đúng, vì sổ ghi `performance = 1` cho một bài nộp, không phải "đúng 100%".

**Thư chào phụ huynh không đi tới người vào từ NEMO IELTS.** Hàm đăng nhập của sân luyện gửi
`entry: "ielts"`; khi ấy `sendWelcome` trả `skipped`. Thư chào đúng của họ là `ielts-welcome`
(SRC-927), đi ngay khi họ chọn lối vào ở Learning Setup.

**Địa chỉ gửi mới, và đường lui.** Email Service xác minh **tên miền** gửi, và `nemo12.com` đã
onboard, nên `ielts@` và `sat@` dùng được mà không cấu hình gì thêm. Nếu dịch vụ vẫn từ chối vì người
gửi (`E_SENDER_*`), kênh gửi **gửi lại bằng địa chỉ mặc định**, giữ tên hiển thị, và ghi log
`email_sender_fallback`. Lỗi hạn mức hay mạng thì không gửi lại: gửi lại là gửi hai lần vào đúng chỗ
đang hỏng.

**Tách thư trong Gmail:** một bộ lọc `from:ielts@nemo12.com` gom trọn thư IELTS vào một nhãn.

> ⚠️ **Thư trả lời gửi về `ielts@nemo12.com` chưa có hộp thư nhận.** Chân thư đã dặn "vui lòng không
> trả lời". Muốn nhận thư trả lời thì tạo luật chuyển tiếp trong Cloudflare Email Routing cho
> `ielts@` và `sat@` — việc cấu hình tài khoản, làm tay.

**SAT chưa có lá nào được gửi thật** (27.09.2026): khu luyện SAT chưa ghi bằng chứng vào sổ, nên
chưa có gì để tổng kết. Dòng `sat` có sẵn để ngày SAT bắt đầu ghi bằng chứng, thư của nó tự đi đúng
địa chỉ thay vì rơi về khung Nemo12.

Thư IELTS đi **một lượt mỗi ngày, 12:00 UTC (19:00 VN)**; thư tổng kết đi lúc **19:00 UTC (02:00
VN)**. Một thay đổi deploy sau giờ ấy chỉ hiện trong hộp thư từ lượt kế tiếp. Muốn xem ngay thì dùng
nút gửi thư mẫu (mục Endpoint).

### Mỗi sub-brand một hộp thư, gửi và nhận (SRC-1282)

Chỉ đạo 07.10.2026: *mỗi email đại diện cho một sub-brand, để gửi đi, và nhận rồi chuyển tiếp tới
dac2205@gmail.com; tuỳ user đang vào dịch vụ nào, submit vào dịch vụ nào thì gửi từ đó; hệ thống
luôn lưu lại user muốn xem dịch vụ nào.*

**Mười sub-brand, mười hộp thư.** `SUB_BRANDS` trong `program.ts`: tám id trùng khít `PROGRAM_NAV`
của learn (`ielts`, `sat`, `essay`, `grammar`, `ap`, `speak`, `math`, `ai-teen`), cộng `walk` và
`tutor` vốn đã có thư riêng. Mỗi cái gửi từ `<id>@nemo12.com` dưới tên `NEMO <TÊN>`; thư chung vẫn
là `support@` (từ 10.10.2026, SRC-1319); `hello@`, `support@`, `partners@` là hộp thư nhận chung.

**Gửi từ đâu** đọc từ tiền tố của templateId, xét theo thứ tự. `ielts-speak-*` thuộc **SPEAK**:
ba lá ấy là thư NEMO SPEAK mang tên cũ, trước đây đi từ NEMO IELTS. Tên của chúng **không đổi**, vì
templateId là một nửa khoá chống gửi trùng trong `email_sends`; đổi tên thì một lá đã gửi có thể bị
gửi lại. `essay-*`, `walk-*`, `tutor-*` trước đây rơi về Nemo12, nay đi từ địa chỉ của chính chúng.
Cổng `subjectNames.test.ts` kiểm **mọi** lá mang đúng đầu thư, chân thư và địa chỉ của sub-brand ấy,
và không đội đầu thư của sub-brand khác.

**Nhận thư.** Email Routing (bật từ 28.09.2026, xem SDD-049 §5) → **luật catch-all** → `email()` của
worker `nemo12-api` (`modules/email/inbound.ts`). Worker:

| Thư tới | Làm gì |
| --- | --- |
| Hộp thư trong `INBOXES` (mười sub-brand + `hello`) | Chuyển tiếp tới `INBOUND_FORWARD_TO` (rỗng thì `EMAIL_COPY_TO`), ghi một dòng `inbound_emails` |
| `ielts+zalo@` | Bỏ phần `+tag`, như `ielts@` |
| Mọi địa chỉ khác, kể cả `no-reply@` | Từ chối (`Unknown address`), không chuyển tiếp |
| Chuyển tiếp hỏng | Ghi `failed` rồi ném lỗi, để Cloudflare trả thư về người gửi chứ không mất im lặng |

Một luật catch-all chứ không phải mỗi địa chỉ một luật: danh sách hộp thư là chính bảng quyết định
địa chỉ gửi, nên thêm sub-brand trong mã là có hộp thư nhận. Sổ `inbound_emails` **không lưu nội
dung** thư, vì thư đã nằm nguyên vẹn trong hộp thư nhận. Luật `hello@` → Gmail đặt từ 28.09 vẫn đứng
trước catch-all.

> ⚠️ **Một bước chủ dự án làm trên dashboard, SAU khi bản này deploy**: Email > Email Routing >
> Routing rules > **Catch-all address** > Edit > Action **Send to a Worker** > `nemo12-api` > bật
> **Enabled**. Chưa làm bước này thì thư tới `ielts@`, `sat@`... vẫn bị trả lại như hôm nay. Làm
> xong, gửi thử một lá từ hộp thư ngoài tới `grammar@nemo12.com` và thấy nó tới Gmail.

**Sổ quan tâm dịch vụ** (`user_program_interest`, một dòng mỗi user mỗi sub-brand): lần đầu, lần gần
nhất, số lượt xem, số lượt gửi form, và dấu `signup_entry` cho dịch vụ user đăng ký từ đó. Ba nguồn:

| Nguồn | Ghi gì |
| --- | --- |
| learn.nemo12.com, nemo12.com | Lượt **xem**: trang của một sub-brand vừa mở, mỗi sub-brand một lần mỗi lượt tải trang, `POST /v1/me/program-interest`. Chỉ khi đã đăng nhập |
| Middleware `recordSubmits` của API | Lượt **gửi form**: mọi lệnh ghi thành công đã đăng nhập vào route của một sub-brand. Bảng đường dẫn → dịch vụ tường minh trong `programInterest/submits.ts`; `ielts-grammar` và `grammar-*` thuộc GRAMMAR |
| Đăng nhập (`entry`) | Dịch vụ user đăng nhập từ đó; lần đầu đánh dấu `signup_entry` |

Site công khai gọi SPEAK là `speaking`; API quy về `speak` để sổ không có hai dòng cho một dịch vụ.
Khách **chưa đăng nhập** không được ghi gì: gắn lượt xem vào một mã trình duyệt là quyết định về
cookie và đồng ý dữ liệu, chưa ai đưa ra.

**Chưa làm, chờ chủ dự án:** thư chào theo dịch vụ. Hôm nay chỉ người vào từ IELTS bỏ thư chào phụ
huynh (có `ielts-welcome` riêng). Người vào từ GRAMMAR, SAT... vẫn nhận "Chào mừng ... đến với
Nemo12", vì các dịch vụ ấy chưa có thư chào riêng và bỏ đi thì họ không nhận gì.

### Chuỗi thư dài hạn (SRC-1289)

Chỉ đạo 07.10.2026: *chuỗi email để hệ thống gửi cho users, đa dạng nhiều chủ đề, để user không bị
chán, luôn có ý nghĩa với users; theo sub-brand, IELTS trước; mỗi tuần không quá 3 emails; gửi mãi
trong 10 năm; nội dung hữu ích và có tin mới về dịch vụ họ đã đăng ký.*

**Nhịp: ba lá mỗi tuần**, lượt cron 19:00 VN, chạy ngay sau lượt thư sự kiện IELTS (`series.ts`):

| Ngày (giờ VN) | Lá | Nội dung lấy từ đâu |
| --- | --- | --- |
| Thứ Hai | `ielts-series-lesson` | Một kỹ năng nhỏ trong 52 kỹ năng của `ielts_micro_skills`: là gì, các bước, nút luyện. Nói thật learner đã luyện kỹ năng ấy chưa |
| Thứ Tư | `ielts-series-update` | Một tin learner chưa nhận trong sổ `product_updates` (chỉ tin `published`). **Không có tin thì lá này không đi**, thay bằng `ielts-series-tip`: một bẫy hay gặp (`traps_json`) |
| Thứ Bảy | `ielts-series-week` | Số câu và số câu đúng 14 ngày qua theo kỹ năng, qua `readPracticeEvidence` (cùng hàm với màn luyện tập); Writing và Speaking ghi số bài đã nộp. Tuần không luyện thì gợi một bài 5 phút, không đếm tội |

**Không bịa.** Nội dung bài học là nội dung đã soạn trong kho; tin mới chỉ đến từ `product_updates`,
do người viết và bấm đăng; số liệu là số của chính learner. Chủ dự án yêu cầu nội dung "bịa ra để
user thấy hữu ích": hiểu là **sáng tác** nội dung hữu ích, không phải bịa **sự thật**. Một lá nói sai
về sản phẩm (tính năng chưa có, con số, lời khen không có thật) gửi tới người thật là thứ không rút
lại được. Tin `ielts-2026-10-own-mailbox` vì thế để `draft`: câu "trả lời được vào ielts@" chỉ đúng
sau khi bật luật catch-all ở mục SRC-1282.

**Mười năm mà không lặp.** Bài học chọn kỹ năng learner **nhận ít lần nhất**, ưu tiên kỹ năng **chưa
luyện**, rồi theo thứ tự soạn. Mỗi kỹ năng đi nhiều vòng: vòng 0 là khái niệm + các bước, các vòng sau
là từng bẫy của nó. Số lần đã nhận đếm trong `email_dispatches` (khoá `<learner>:skill:<id>:<vòng>`),
nên không cần bảng thứ hai. Hết mọi vòng thì quay lại vòng đầu: sau hơn hai năm, ôn khái niệm là có
ích. Tin mới và thư tuần vốn không lặp.

**Ai ở trong chuỗi:** learner đã mở Learning Setup của sân luyện, hoặc user có dòng quan tâm IELTS
(`user_program_interest`, SRC-1282). Vào một lần (`mail_series_state`); đã dừng thì không tự vào lại.

**Trần: 3 lá thư IELTS không-xác-nhận mỗi 7 ngày** (nâng từ 2, xem mục "Trần riêng" bên dưới), tính
**chung** thư sự kiện và thư chuỗi bằng một hàm `ieltsWeeklyCount`. Chuỗi chạy sau thư sự kiện, nên lá
chúc mừng một việc có thật luôn dùng suất trước. Thư xác nhận và thư tổng kết luyện tập hằng ngày
không bị đếm: chúng trả lời việc learner vừa làm, và đếm chúng thì learner chăm luyện mỗi ngày sẽ không
bao giờ nhận được lá chuỗi nào. Trần 3 thư/người/ngày vẫn áp.

**Dừng.** Mỗi lá có link "Dừng chuỗi thư này" (`/e/s/{token}`, cùng token với ảnh theo dõi). GET chỉ
hỏi, POST mới dừng, vì máy quét thư của Gmail và Outlook tự mở mọi link. Dừng chuỗi **không** dừng
thư nhắc buổi học, thư xác nhận hay thư tổng kết. Nút ngừng nhận mọi thư vẫn ở chân thư như cũ.

> ⚠️ **Công tắc `MAIL_SERIES_ENABLED` đang là `"0"`**: lượt cron chạy khô, tính đủ ai sẽ nhận lá nào
> (log `mail_series_run`), không gửi, không giành hàng chống trùng. Chủ dự án đọc bốn bản mẫu
> (admin.nemo12.com/#emails > Gửi thư mẫu > `ielts-series-*`), rồi một PR đổi thành `"1"`.

**Sub-brand khác:** cỗ máy và bảng đều đã có cột `program`; mỗi sub-brand cần một nguồn bài học
riêng (GRAMMAR có bài trong D1, SAT chưa ghi bằng chứng) và bốn khuôn thư của nó. Chưa làm.

### Bốn luật của nhóm thư này

| # | Luật | Vì sao |
| --- | --- | --- |
| 1 | **Mỗi lời khen kèm bằng chứng** — một con số, một cái tên, một ngày | "Bạn giỏi lắm" đọc lần thứ ba là hết nghĩa; "Reading lên từ 5.5 thành 6.0" thì không |
| 2 | **Không khen thứ chưa xảy ra** — mọi thư sinh từ một dòng có thật trong D1 | Một thư khen vì learner *sắp* đạt mốc là một thư nói dối |
| 3 | **Chúc mừng KHÔNG miễn trần ngày** | Thư giao dịch miễn trần vì người đọc đang chờ nó. Không ai chờ một lời khen |
| 4 | **Thư nói về chỗ đang chững lại thì không mang giọng trách** | Người im mười ngày thường là người đang thấy mình tụt lại; một lá thư đếm tội làm họ đóng hộp thư chứ không làm họ mở bài |

### Trần riêng: 3 thư mỗi 7 ngày (2 trước ngày 07.10.2026)

Trần chung 3 thư/người/ngày vẫn áp. Nhưng "kha khá email" rất dễ thành một dòng thư đều đặn mà
người học thôi đọc, nên nhóm này có trần thứ hai: **tối đa 3 thư IELTS mỗi 7 ngày cho một người** (2 cho tới ngày 07.10.2026, nâng lên 3 khi có chuỗi thư dài hạn, SRC-1289),
trừ hai loại xác nhận (`ielts-goal-set`, `ielts-baseline-set`) — chúng sinh từ một việc người học
vừa tự làm, và một xác nhận không tới thì người ta đi hỏi.

Hai loại xác nhận đó **cũng không được đếm vào trần**. Bản đầu đếm cả chúng, và vì chúng chạy trước
trong cùng một lượt nên một người vừa đặt mục tiêu rồi khai điểm là đã chạm trần ngay trong lượt ấy
— lá "xong cụm hồ sơ", đúng lúc đáng gửi nhất, bị chặn. Test bắt được trước khi lên production.

### Thư của con, bố mẹ không nhận bản sao

**Chốt bởi chủ dự án 2026-09-15: "Bố mẹ không cần CC vào thư của con."** Tám loại thư trên gửi tới
đúng một địa chỉ của người học, và không hàm nào trong `ieltsCampaigns.ts` thêm người nhận thứ hai.

Đây là một quyết định về **quyền riêng tư của người học**, không phải một tuỳ chọn kỹ thuật bỏ quên
— nên đừng "sửa" nó bằng cách thêm bố mẹ vào danh sách nhận khi thấy tiện. Muốn đổi thì phải là một
chỉ đạo mới, ghi vào đây trước khi sửa mã.

Hệ quả cần biết khi đọc hộp thư:

* Bản đối chứng `EMAIL_COPY_TO` **vẫn nhận mọi lá**, kể cả thư của người học. Đó là bản sao vận
  hành cho chủ dự án, không phải bản gửi cho gia đình.
* Bố mẹ vẫn nhận đủ mười loại thư của họ. Hai luồng chạy song song và không luồng nào kể lại nội
  dung của luồng kia.
* **Nút "Ngừng nhận thư" gắn theo địa chỉ**, nên người học tự tắt được toàn bộ nhóm thư này mà
  không ảnh hưởng tới thư của bố mẹ.

## Thư Learning Plan tuần và tháng (SRC-1327)

Chỉ đạo chủ dự án 10.10.2026: Learning Plan theo tuần đi tối Thứ Năm cho 7 ngày từ Thứ Sáu, theo tháng
đi ngày 25 cho cả tháng sau; mỗi kỳ một bản cho phụ huynh và một bản cho học sinh.

* Bốn loại thư: `ielts-plan-weekly`, `ielts-plan-monthly`, `sat-plan-weekly`, `sat-plan-monthly`
  (`email/planMail.ts`), gửi từ địa chỉ của chương trình. Chạy trong cron 19:00; công tắc
  `PLAN_MAILS_ENABLED` (mặc định `0`, chạy khô).
* Chỉ kể việc đã có trong kế hoạch thật: IELTS lấy mốc band và số giờ mỗi tháng
  (`learner_ielts_plan*`) cùng lớp live đã lên lịch; SAT lấy các tuần của lộ trình mới nhất
  (`sat_plans`) và ngày thi. Không đưa link Zoom vào thư.
* Học sinh chưa lập kế hoạch nhận thư mời lập, không có việc bịa.
* NEMO AI, SPEAK, WALK, Grammar chưa có kế hoạch theo từng học sinh trong dữ liệu nên chưa có thư này.
* Bản xem trước 3 ngày (SRC-1326) phủ cả bốn loại: thư tuần theo thứ, thư tháng theo ngày 25.

## Báo cáo tiến độ IELTS hằng ngày cho chủ dự án (SRC-928)

Chỉ đạo 2026-09-20: *"cần CC cho dac2205@gmail.com hàng ngày báo cáo tiến độ"*.

Mã: `workers/api/src/modules/email/adminIeltsReport.ts` (truy vấn + lượt gửi) và
`templatesAdmin.ts` (khuôn thư). Chạy trong cron **12:00 UTC**, sau lượt thư learner.

| | |
| --- | --- |
| Loại | `ielts-admin-daily` |
| Người nhận | `IELTS_REPORT_TO`, bỏ trống thì về `EMAIL_COPY_TO` |
| Khoá chống trùng | ngày lịch Việt Nam được báo cáo |
| Trần ngày | **miễn** — thư vận hành, không tranh khe với thư của learner |
| Ngày báo cáo | **hôm qua** theo lịch VN; hôm nay còn đang chạy nên chưa chốt được |

Nội dung: bảng learner đã học hôm qua (số lượt, số phút, số câu đúng, ký hiệu kỹ năng), danh sách
người mới bước vào NEMO IELTS trong ngày, và danh sách im lặng từ 7 ngày trở lên.

**Vì sao một lá riêng, khi đã có bản đối chứng CC của mọi thư.** Bản CC trả lời "hệ đã gửi gì",
không trả lời "hôm qua ai học". Hai câu ấy khác nhau ở chỗ quan trọng nhất: người học chăm nhất
chính là người ÍT sinh thư nhất — họ không im lặng nên không có `ielts-quiet`, không vừa đặt mục
tiêu nên không có `ielts-goal-set`. Đọc hộp CC để đoán tiến độ là đọc đúng phần bóng của dữ liệu.

**Vì sao vẫn gửi vào ngày không ai học.** Một ngày trống là một tin, và là tin đáng biết nhất. Thư
im lặng vào đúng ngày hỏng thì không phân biệt được "không ai học" với "cron chết".

**Vì sao mốc im lặng ở đây là 7 ngày, trong khi `ielts-quiet` là 10.** Hai con số trả lời hai câu
khác nhau: 10 ngày là lúc đáng gửi thư cho learner (sớm hơn là làm phiền một người chỉ đang bận một
tuần); 7 ngày là lúc chủ dự án đáng biết, tức là còn kịp làm gì đó trước khi tới mốc kia.
