Metrics and logs
Metrics show how your email performs over time: how much you sent, how much arrived, and how many people opened, clicked, bounced or complained. Logs show every API call your keys made, with what was sent and what came back, so you can debug an integration without adding logging to your own code.
Metrics in the dashboard
Email → Metrics shows the volume per day (sent, delivered, opened, clicked) and the delivery, open, click, bounce and complaint rates. Pick the last 7, 14 or 30 days and one domain or all of them. Days follow Vietnam time.
- The bounce rate chart marks 4% and the complaint rate chart marks 0.08%. Above these lines, Gmail and Yahoo start to throttle your mail or send it to spam.
- Open and click rates are over delivered mail and count each person once.
- Opens are an estimate: Apple Mail Privacy Protection and other image proxies can make them look higher. See Open and click tracking.
Metrics API
GET /emails/metrics returns the same numbers, in the shape of Resend's metrics endpoint. A key with email:read (or email:send / email:write) is enough.
- cURL
- Node.js
- Python
- PHP
curl "https://api.hatang.dev/emails/metrics?start_date=2026-10-01&end_date=2026-10-14&timezone=Asia/Ho_Chi_Minh&dimensions=period,domain&metrics=sent,delivered,bounce_rate" \
-H "Authorization: Bearer $HATANG_API_KEY"
const { data } = await hatang.emails.metrics({
start_date: "2026-10-01",
end_date: "2026-10-14",
timezone: "Asia/Ho_Chi_Minh",
dimensions: ["period", "domain"],
metrics: ["sent", "delivered", "bounce_rate"],
});
res = requests.get(
"https://api.hatang.dev/emails/metrics",
headers={"Authorization": f"Bearer {os.environ['HATANG_API_KEY']}"},
params={"start_date": "2026-10-01", "end_date": "2026-10-14", "timezone": "Asia/Ho_Chi_Minh", "metrics": "sent,delivered,bounce_rate"},
)
$res = $client->get('/emails/metrics', [
'headers' => ['Authorization' => 'Bearer ' . getenv('HATANG_API_KEY')],
'query' => ['start_date' => '2026-10-01', 'end_date' => '2026-10-14', 'timezone' => 'Asia/Ho_Chi_Minh', 'metrics' => 'sent,delivered,bounce_rate'],
]);
| Parameter | Description |
|---|---|
start_date, end_date | A date (2026-10-01, read as midnight UTC) or an ISO date and time. A date-only end_date includes that whole day (until 23:59:59 UTC). The range is at most 31 days. Without them: the last 7 days. |
timezone | IANA time zone used to cut periods, for example Asia/Ho_Chi_Minh. Default UTC. |
granularity | hourly, daily (default), weekly or monthly. |
metrics | Comma-separated list of the metrics to return. Default: all of them. |
dimensions | period (default), or period,domain to split each period per domain. |
domain_id | Only these domains, comma-separated. |
{
"object": "metrics",
"start_date": "2026-10-01T00:00:00.000Z",
"end_date": "2026-10-14T23:59:59.999Z",
"timezone": "Asia/Ho_Chi_Minh",
"granularity": "daily",
"metrics": ["sent", "delivered", "bounce_rate"],
"dimensions": ["period"],
"totals": { "sent": 1840, "delivered": 1812, "bounce_rate": 0.0098 },
"data": [
{ "period": "2026-10-01T00:00:00", "sent": 120, "delivered": 119, "bounce_rate": 0.0083 },
{ "period": "2026-10-02T00:00:00", "sent": 134, "delivered": 131, "bounce_rate": 0.0149 }
]
}
totals covers the whole range; data has one row per period (and per domain, with domain_id and domain_name). Rates are fractions rounded to four decimals, so 0.0098 is 0.98%.
Counts
| Metric | Counts |
|---|---|
sent | Recipients Ha Tang sent to, without suppressed and canceled ones. |
delivered | Delivery events. |
delivery_delayed | Delay events. |
failed | Failure events. |
suppressed | Recipients skipped because they were suppressed. |
bounced | Bounce events, including those of suppressed recipients. |
opened, unique_opened | Opens, and recipients who opened at least once. |
clicked, unique_clicked | Clicks, and recipients who clicked at least once. |
complained | Spam complaints. |
unsubscribed | Contacts who unsubscribed from everything. Not split per domain, so it is left out when you split or filter by domain. |
received | Messages received on your domains. |
Rates
| Rate | Formula |
|---|---|
delivery_rate | delivered / sent |
bounce_rate | bounced / sent |
open_rate | unique_opened / delivered |
click_rate | unique_clicked / delivered |
complaint_rate | complained / delivered |
unsubscribe_rate | unsubscribed / delivered |
Sends are counted in the period the email was created; events in the period they happened.
Logs
Email → Logs records every API call made with your business's keys: endpoint, method, status, duration, API key, the user agent (which shows the SDK and its version) and the request and response bodies. Filter by status, method, API key or path. A failed call has a Copy for AI button that puts the request, the response and a link to the docs into one block you can paste into an AI assistant.
- Passwords, secrets, tokens and API keys in bodies are replaced with
[redacted]. Attachment content is replaced with its size. - Each body is kept up to 16 KB. Logs are kept for 30 days.
- Calls made by the dashboard itself are not in the log; only calls made with an API key are.
curl "https://api.hatang.dev/logs?status=error&limit=20" -H "Authorization: Bearer $HATANG_API_KEY"
curl "https://api.hatang.dev/logs/log_0199e6c17a2b7f009c1e3f6a2b1d9e10" -H "Authorization: Bearer $HATANG_API_KEY"
GET /logs parameter | Description |
|---|---|
status | 2xx, 4xx, 5xx or error (any 4xx or 5xx). |
method | GET, POST, PATCH… |
endpoint | Part of the path, for example /emails. |
api_key_id | Only calls made with this key. |
user_agent | Part of the user agent, for example hatang-sdk-node. |
since, until | Time range. |
limit, after, before | Pagination, as for other lists. |
A log entry looks like this; GET /logs/{id} adds request_body, response_body and resource_ids (the ids the response created or returned):
{ "object": "log", "id": "log_…", "created_at": "2026-10-14T09:12:03.000Z", "endpoint": "/emails", "method": "POST", "response_status": 422, "duration_ms": 38, "user_agent": "hatang-sdk-node/1.4.0 (node/22.11.0)", "sdk": "hatang-sdk-node 1.4.0", "api_key_id": "key_…" }
Usage
GET /usage shows what your business used against its limits: emails today against the daily quota (with the reset time), emails sent and received this month, and the number of domains, contacts, templates and automation runs this month.
{
"object": "usage",
"emails": {
"daily": { "used": 312, "limit": 10000, "resets_at": "2026-10-15T00:00:00.000Z" },
"monthly": { "sent": 8840, "received": 112, "limit": null, "resets_at": "2026-11-01T00:00:00.000Z" }
},
"domains": { "used": 2, "limit": null },
"contacts": { "used": 5120, "limit": null },
"templates": { "used": 14, "limit": null },
"automation_runs": { "monthly": 640, "limit": null }
}
Reading logs and usage needs a key with a write scope on any product (for example email:write), or full access.