# SMTP 連線

本頁說明 `SMTP.SendEmail()` 依 port 選擇連線方式、認證條件，以及實際送出的信件格式。

## 前置檢查

`SendEmail()`（`internal/service/smtp.go:28`）在連線前依序檢查，任一失敗即回傳錯誤：

| 檢查 | 錯誤訊息 |
|------|---------|
| `enabled` 為 `true` 且 `host` 非空 | `SMTP not Enabled or Host is not set` |
| `to` 至少一個收件者 | `no user email configured` |
| `host:port` 在 5 秒內可建立 TCP 連線 | `failed to connect TCP (...)` |

## 連線方式

```mermaid
graph TB
    Probe[TCP 探測 5 秒] --> Plain[smtp.Dial 明文連線]
    Plain -->|成功| Port587{port = 587?}
    Plain -->|失敗且 port = 465| Implicit[tls.Dial 隱式 TLS]
    Plain -->|失敗且其他 port| Fail[回傳錯誤]
    Port587 -->|是且伺服器支援| StartTLS[STARTTLS 升級]
    Port587 -->|否| Auth
    StartTLS --> Auth{username 與 password 皆非空?}
    Implicit --> Auth
    Auth -->|是| Login[PLAIN 認證]
    Auth -->|否| Send
    Login --> Send[MAIL FROM / RCPT TO / DATA]
```

| port | 實際行為 |
|------|---------|
| `465` | 先嘗試明文 SMTP；隱式 TLS 伺服器不會送出明文問候，明文嘗試要等伺服器關閉連線才失敗，之後才改以 `tls.Dial` 建立隱式 TLS 連線，因此寄信可能延遲 |
| `587` | 明文連線後，若伺服器宣告 `STARTTLS` 擴充則升級為 TLS；不支援時以明文繼續 |
| 其他（如 `25`） | 只用明文連線，不升級 TLS |

TLS 一律以 `host` 作為 `ServerName` 驗證伺服器憑證。

## 認證

`username` 與 `password` 都非空時才執行 `smtp.PlainAuth`；任一為空則略過認證，適用於內網免認證的轉信伺服器。Go 標準庫的 PLAIN 認證只允許在 TLS 連線或 `localhost` 上送出，因此在 `25` 等未加密 port 上設定帳密會認證失敗。

## 信件格式

| 標頭 | 值 |
|------|----|
| From | `config.from` |
| To | `config.to` 以逗號串接 |
| Cc | `config.cc`；空值時填 `config.from` |
| Subject | 告警或測試信主旨 |
| Content-Type | `text/html; charset=UTF-8` |

`RCPT TO` 只對 `to` 清單中的地址送出，Cc 只寫在標頭，見 [已知限制](/zh/known-limitations)。
