# Mailketing API v2

> **Base URL:** `https://api.mailketing.co.id/api/v2`
> **Content-Type:** `application/json`
> **Version:** 2.0.0

## Authentication

Semua endpoint memerlukan API token. Token bisa dikirim melalui 4 cara (priority order):

| Priority | Method | Contoh |
|----------|--------|--------|
| 1 | Header `Authorization: Bearer <token>` | `-H "Authorization: Bearer xxx"` |
| 2 | Header `X-Api-Token: <token>` | `-H "X-Api-Token: xxx"` |
| 3 | Query param `?api_token=<token>` | `?api_token=xxx` |
| 4 | JSON body `{"api_token": "xxx"}` | `-d '{"api_token":"xxx"}'` |

**Test token:** *(didapat dari Dashboard Mailketing — menu API Integration)*

## Response Format

Semua response mengikuti format standar:

| Field | Type | Always Present | Description |
|-------|------|----------------|-------------|
| `success` | boolean | Ya | `true` untuk sukses, `false` untuk error |
| `data` | object / null | Ya | Payload response (null jika error) |
| `message` | string | Ya | Human-readable message |
| `errors` | object | Tidak | Hanya ada saat validasi gagal. Key = field, Value = pesan error |

```json
{
  "success": true,
  "data": { ... },
  "message": "OK",
  "errors": {}
}
```

## Rate Limiting

Rate limiting **hanya diterapkan pada request yang gagal** (mis. token invalid, validasi input gagal). Request yang sukses **tidak** di-rate limit.

| Jenis Request | Max | Per |
|---------------|-----|-----|
| Request gagal (401/402/403/404/422/500) | 30 | menit per IP |

Response headers saat rate limit: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `Retry-After`

---

## Endpoints

### 1. Kirim Email

`POST /send`

Mengirim email transaksional via SMTP relay. Domain pengirim harus sudah diverifikasi di Mailketing.

**Minimal request:**

```bash
curl -X POST "https://api.mailketing.co.id/api/v2/send" \
  -H "Content-Type: application/json" \
  -H "X-Api-Token: YOUR_API_TOKEN" \
  -d '{
    "from_name": "John Doe",
    "from_email": "john@verified-domain.com",
    "subject": "Hello World",
    "recipient": "target@example.com",
    "content": "<html><body><h1>Hello</h1><p>Email content</p></body></html>"
  }'
```

**Full request (dengan attachment):**

```bash
curl -X POST "https://api.mailketing.co.id/api/v2/send" \
  -H "Content-Type: application/json" \
  -H "X-Api-Token: YOUR_API_TOKEN" \
  -d '{
    "from_name": "John Doe",
    "from_email": "john@verified-domain.com",
    "subject": "Hello World",
    "recipient": "target@example.com",
    "content": "<html><body><h1>Hello</h1><p>Email content</p></body></html>",
    "attach1": "https://example.com/file.pdf",
    "attach2": "https://example.com/image.png",
    "attach3": "https://example.com/doc.docx"
  }'
```

**Request Fields:**

| Field | Required | Type | Max | Description |
|-------|----------|------|-----|-------------|
| `from_name` | **Ya** | string | 255 | Nama pengirim |
| `from_email` | **Ya** | email | 255 | Email pengirim — domain harus terverifikasi di Mailketing |
| `subject` | **Ya** | string | 998 | Subject email |
| `recipient` | **Ya** | email | 255 | Email penerima |
| `content` | **Ya** | string | 300KB | Konten email. HTML (mengandung `<html>`) = unlimited, Plain text = max 300KB |
| `attach1` | Tidak | url | - | URL attachment 1 (di-download server lalu di-attach ke email) |
| `attach2` | Tidak | url | - | URL attachment 2 |
| `attach3` | Tidak | url | - | URL attachment 3 |

> **`message_id` tidak bisa diisi sendiri.** Mailketing selalu membuat `message_id` otomatis untuk setiap email (format `<api.{random}@sender-domain>`) dan mengembalikannya di response `data.message_id`. Jika field `message_id` tetap dikirim di request, nilainya diabaikan. Simpan `message_id` dari response untuk mencocokkan event webhook (open, bounce).

**Success Response (200):**

```json
{
  "success": true,
  "data": {
    "message_id": "<api.a1b2c3d4e5f67890123456@verified-domain.com>"
  },
  "message": "Email queued successfully"
}
```

**Error Responses:**

| HTTP | Message | Penyebab |
|------|---------|----------|
| 401 | Invalid API token | Token tidak ditemukan / salah |
| 402 | Insufficient credits | Kredit habis, silakan top-up |
| 403 | Unknown sender domain | Domain `from_email` belum diverifikasi di menu Add Domain |
| 422 | Validation failed | Input tidak valid — cek field `errors` untuk detail |
| 429 | Rate limit exceeded | Terlalu banyak request gagal (30 gagal/menit per IP) |

---

### 2. Cek Kredit

`GET /credits`

Mendapatkan sisa kredit akun.

```bash
curl "https://api.mailketing.co.id/api/v2/credits?api_token=YOUR_API_TOKEN"
```

```bash
# Alternatif via header
curl "https://api.mailketing.co.id/api/v2/credits" \
  -H "X-Api-Token: YOUR_API_TOKEN"
```

**Success Response (200):**

```json
{
  "success": true,
  "data": {
    "credits": 1500,
    "email": "user@example.com"
  },
  "message": "OK"
}
```

---

### 3. Daftar Sender

`GET /senders`

Mendapatkan semua email pengirim yang sudah diverifikasi untuk akun ini.

```bash
curl "https://api.mailketing.co.id/api/v2/senders?api_token=YOUR_API_TOKEN"
```

**Success Response (200):**

```json
{
  "success": true,
  "data": {
    "senders": [
      { "email": "noreply@domain-a.com" },
      { "email": "support@domain-b.com" }
    ]
  },
  "message": "OK"
}
```

---

### 4. Daftar List

`GET /lists`

Mendapatkan semua list subscriber milik akun (tidak termasuk list internal "ALL CONTACT").

```bash
curl "https://api.mailketing.co.id/api/v2/lists?api_token=YOUR_API_TOKEN"
```

**Success Response (200):**

```json
{
  "success": true,
  "data": {
    "lists": [
      { "list_id": 1, "list_name": "Newsletter" },
      { "list_id": 2, "list_name": "Pelanggan VIP" },
      { "list_id": 3, "list_name": "Leads Facebook" }
    ]
  },
  "message": "OK"
}
```

> **Gunakan `list_id` dari response ini untuk endpoint POST /subscribers**

---

### 5. Tambah Subscriber

`POST /subscribers`

Menambahkan subscriber ke list. Jika email sudah ada di list tersebut, return status `already_exists` (tetap HTTP 200, bukan error).

**Request minimal:**

```bash
curl -X POST "https://api.mailketing.co.id/api/v2/subscribers" \
  -H "Content-Type: application/json" \
  -H "X-Api-Token: YOUR_API_TOKEN" \
  -d '{
    "list_id": 1,
    "email": "subscriber@example.com"
  }'
```

**Request lengkap (semua field opsional):**

```bash
curl -X POST "https://api.mailketing.co.id/api/v2/subscribers" \
  -H "Content-Type: application/json" \
  -H "X-Api-Token: YOUR_API_TOKEN" \
  -d '{
    "list_id": 1,
    "email": "subscriber@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "mobile": "08123456789",
    "birth_date": "1990-01-15",
    "city": "Jakarta",
    "country": "Indonesia",
    "company": "Acme Inc",
    "post_code": "12345",
    "state": "DKI Jakarta",
    "title": "Mr",
    "phone": "021123456",
    "fax": "021123457"
  }'
```

**Request Fields:**

| Field | Required | Type | Max | Description |
|-------|----------|------|-----|-------------|
| `list_id` | **Ya** | integer | - | ID list tujuan (dari GET /lists) |
| `email` | **Ya** | email | 255 | Email subscriber |
| `first_name` | Tidak | string | 255 | Nama depan — hanya alphanumeric & spasi |
| `last_name` | Tidak | string | 255 | Nama belakang — hanya alphanumeric & spasi |
| `mobile` | Tidak | string | 30 | Nomor HP — auto-normalize ke format `628...` |
| `birth_date` | Tidak | date | - | Format `YYYY-MM-DD` |
| `city` | Tidak | string | 100 | Kota |
| `country` | Tidak | string | 100 | Negara |
| `company` | Tidak | string | 255 | Perusahaan |
| `post_code` | Tidak | string | 20 | Kode pos |
| `state` | Tidak | string | 100 | Provinsi / state |
| `title` | Tidak | string | 100 | Title (Mr / Mrs / etc) |
| `phone` | Tidak | string | 30 | Nomor telepon |
| `fax` | Tidak | string | 30 | Nomor fax |

> **Custom fields:** Jika list memiliki custom fields, kirimkan dengan key = `field_name` dan value = string.
> Contoh: `"alamat_lengkap": "Jl. Sudirman No. 1"`

**Mobile Normalization:**

Nomor yang dikirim akan dinormalisasi otomatis:
- `08123456789` → `628123456789`
- `+62 812-3456-789` → `628123456789`
- `8123456789` → `628123456789`

**Success Response — Subscriber Baru (200):**

```json
{
  "success": true,
  "data": {
    "email_list_id": 123,
    "list_id": 1,
    "status": "added"
  },
  "message": "Subscriber added to list 1"
}
```

**Success Response — Sudah Ada (200):**

```json
{
  "success": true,
  "data": {
    "email_list_id": null,
    "list_id": 1,
    "status": "already_exists"
  },
  "message": "Subscriber already in list 1"
}
```

**Error Responses:**

| HTTP | Message | Penyebab |
|------|---------|----------|
| 401 | Invalid API token | Token tidak ditemukan |
| 404 | List not found | `list_id` tidak ditemukan atau bukan milik akun ini |
| 422 | Validation failed | Input tidak valid — cek field `errors` |
| 429 | Rate limit exceeded | Terlalu banyak request gagal (30 gagal/menit per IP) |

---

## HTTP Status Codes

| Code | Name | Description |
|------|------|-------------|
| 200 | OK | Request berhasil |
| 204 | No Content | OPTIONS preflight success |
| 400 | Bad Request | Request malformed |
| 401 | Unauthorized | API token missing atau invalid |
| 402 | Payment Required | Kredit habis |
| 403 | Forbidden | Sender domain tidak terverifikasi |
| 404 | Not Found | Resource tidak ditemukan |
| 422 | Unprocessable Entity | Validasi input gagal |
| 429 | Too Many Requests | Terlalu banyak request gagal — hanya request gagal yang di-rate limit (30/menit per IP), request sukses tidak |
| 500 | Internal Server Error | Server error |

---

## Changelog

| Version | Tanggal | Perubahan |
|---------|---------|-----------|
| 2.0.2 | 2026-10-23 | `message_id` tidak bisa diisi sendiri (`/send` dan `/corporate/send`) — selalu dibuat otomatis oleh Mailketing dan dikembalikan di response |
| 2.0.1 | 2026-08-10 | Rate limiting diubah: hanya request gagal yang di-rate limit (30/menit per IP); request sukses tidak |
| 2.0.0 | 2026-08-10 | Initial release: send, credits, senders, lists, subscribers |
