# Email IMAP/SMTP

Email IMAP/SMTP API APIPedia: Baca, cari, dan kirim email dari banyak mailbox (IMAP/SMTP, Gmail, inbox webhook) lewat satu API. Panggil dengan satu token API di `https://api.apipedia.id/v1/mail`.

Hubungkan mailbox apa pun yang mendukung IMAP/SMTP di halaman Koneksi (server terdeteksi otomatis dari domain). Lalu daftar & cari email per folder, baca isi dan lampiran, tandai dibaca/berbintang, pindahkan ke Sampah, kirim dan balas email dengan lampiran. Banyak akun sekaligus; pilih dengan connection_id atau pakai akun default. Password disimpan terenkripsi AES-256-GCM.

- Base URL: `https://api.apipedia.id/v1/mail`
- Autentikasi: `Authorization: Bearer apk_…` (token dari console.apipedia.id)
- Kategori: Produktivitas
- Halaman: https://apipedia.id/docs/email-imap
- OpenAPI: https://console.apipedia.id/openapi/email-imap.yaml

Semua respons memakai amplop `{ "is_success", "message", "data" }` kecuali disebutkan lain. Kode error umum: lihat https://apipedia.id/docs#kode-status-error.

## Endpoint (13)

- `GET /accounts`: Daftar mailbox terhubung
- `POST /accounts`: Hubungkan mailbox IMAP/SMTP
- `POST /accounts/{id}/test`: Uji ulang login IMAP dan SMTP
- `GET /detect`: Tebak server IMAP/SMTP dari alamat email
- `GET /folders`: Daftar folder mailbox
- `GET /messages`: Daftar dan cari email
- `GET /messages/{uid}`: Baca satu email
- `PATCH /messages/{uid}`: Tandai dibaca/belum dibaca atau berbintang
- `DELETE /messages/{uid}`: Pindahkan email ke Sampah
- `GET /messages/{uid}/attachments/{index}`: Unduh lampiran email
- `POST /send`: Kirim atau balas email
- `POST /webhooks`: Buat inbox webhook (URL rahasia)
- `POST /webhooks/{id}/rotate`: Ganti URL inbox webhook

### Daftar mailbox terhubung

`GET https://api.apipedia.id/v1/mail/accounts`

Semua mailbox aktif milik akun Anda: mailbox IMAP/SMTP (`toolkit: "email"`) dan inbox webhook (`toolkit: "email-webhook"`). Urut default dulu, lalu yang paling lama terhubung. Password tidak pernah dikembalikan; `meta` hanya berisi data publik (alamat, host). Pakai `id` sebagai `connection_id` di endpoint lain. Daftar kosong berarti belum ada mailbox - hubungkan lewat POST /mail/accounts atau halaman Koneksi.

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/accounts" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx"
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "Daftar mailbox berhasil diambil",
  "data": {
    "items": [
      {
        "id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
        "toolkit": "email",
        "accountLabel": "budi@tokosejahtera.co.id",
        "isDefault": true,
        "connectedAt": "2026-11-10T02:14:09.512Z",
        "meta": {
          "email": "budi@tokosejahtera.co.id",
          "imap_host": "imap.gmail.com",
          "smtp_host": "smtp.gmail.com",
          "from_name": "Budi - Toko Sejahtera"
        },
        "lastUsedAt": "2026-11-12T09:02:44.118Z"
      },
      {
        "id": "8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57",
        "toolkit": "email-webhook",
        "accountLabel": "Pesanan toko online",
        "isDefault": false,
        "connectedAt": "2026-11-11T05:30:00.000Z",
        "lastUsedAt": null,
        "meta": {
          "kind": "webhook"
        }
      }
    ]
  }
}
```

Error khusus endpoint ini:

- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Hubungkan mailbox IMAP/SMTP

`POST https://api.apipedia.id/v1/mail/accounts`

Menghubungkan mailbox baru. Server MENGUJI login IMAP (status INBOX) dan SMTP (verify) lebih dulu; kredensial baru disimpan (terenkripsi AES-256-GCM) bila keduanya berhasil. Mailbox pertama otomatis menjadi default. Akun yang sama juga bisa dihubungkan dari halaman Koneksi console.

Host: isi `preset` (gmail, outlook, icloud, yahoo, zoho, fastmail), ATAU isi `imap_host` + `smtp_host` manual, ATAU kosongkan keduanya supaya server menebak dari domain (sama dengan GET /mail/detect). Port IMAP yang diizinkan 993 atau 143, SMTP 465, 587, 25, atau 2525. Keamanan `tls` atau `starttls` (`none` ditolak). Host yang mengarah ke jaringan privat/localhost ditolak (anti-SSRF). `username` bawaan = `email`. Gmail, Yahoo, iCloud, dan Fastmail butuh App Password, bukan password biasa.

Error: 400 email/password kosong, host tidak bisa ditebak, port/keamanan tidak diizinkan, host privat; 401 login IMAP/SMTP ditolak; 502 server email tidak bisa dihubungi (pesan diawali "IMAP berhasil, tetapi SMTP gagal." bila hanya SMTP yang gagal).

Body (`application/json`, wajib):

```json
{
  "email": "budi@tokosejahtera.co.id",
  "password": "abcd efgh ijkl mnop",
  "label": "Email toko",
  "from_name": "Budi - Toko Sejahtera",
  "preset": "gmail"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/mail/accounts" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"email":"budi@tokosejahtera.co.id","password":"abcd efgh ijkl mnop","label":"Email toko","from_name":"Budi - Toko Sejahtera","preset":"gmail"}'
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "Mailbox budi@tokosejahtera.co.id berhasil dihubungkan",
  "data": {
    "connection": {
      "id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
      "toolkit": "email",
      "accountLabel": "Email toko",
      "isDefault": true,
      "connectedAt": "2026-11-10T02:14:09.512Z",
      "meta": {
        "email": "budi@tokosejahtera.co.id",
        "imap_host": "imap.gmail.com",
        "smtp_host": "smtp.gmail.com",
        "from_name": "Budi - Toko Sejahtera"
      }
    }
  }
}
```

Error khusus endpoint ini:

- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Uji ulang login IMAP dan SMTP

`POST https://api.apipedia.id/v1/mail/accounts/{id}/test`

Menguji ulang login IMAP dan SMTP sebuah mailbox yang sudah terhubung memakai kredensial tersimpan - berguna setelah password atau App Password diganti. Tidak mengubah apa pun. Inbox webhook ditolak (400) karena tidak punya login.

Error: 400 id bukan uuid / akun webhook; 401 login ditolak; 404 akun tidak ditemukan; 502 server email tidak bisa dihubungi.

| Parameter | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id koneksi mailbox (uuid) dari GET /mail/accounts. |

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/mail/accounts/3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d/test" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx"
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "Login IMAP dan SMTP berhasil",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d"
  }
}
```

Error khusus endpoint ini:

- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Tebak server IMAP/SMTP dari alamat email

`GET https://api.apipedia.id/v1/mail/detect`

Menebak pengaturan IMAP/SMTP dari domain alamat email, tanpa login dan tanpa menyimpan apa pun. Urutannya: domain populer dikenali langsung (gmail.com, outlook.com/hotmail.com/live.com, icloud.com, yahoo.com/yahoo.co.id, zoho.com, fastmail.com); domain lain dicek lewat DNS MX (Google Workspace, Microsoft 365, iCloud, Yahoo, Zoho, Fastmail, Coremail, Tencent Exmail, Alibaba Mail, GoDaddy, Titan, Hostinger); bila MX ada tetapi tidak dikenali, ditebak `mail.<domain>` port 993/465 TLS dengan `guessed: true`. `settings` null berarti isi host secara manual di POST /mail/accounts. `presets` selalu berisi enam preset yang bisa dipakai lewat field `preset`.

Error: 400 parameter email kosong/tanpa @; 503 kunci enkripsi server belum diatur.

| Parameter | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `email` | query | string | ya | Alamat email yang akan dihubungkan. |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/detect?email=budi%40tokosejahtera.co.id" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx"
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "Pengaturan server ditemukan",
  "data": {
    "domain": "tokosejahtera.co.id",
    "mx": [
      "aspmx.l.google.com",
      "alt1.aspmx.l.google.com",
      "alt2.aspmx.l.google.com"
    ],
    "settings": {
      "label": "Gmail / Google Workspace",
      "imap_host": "imap.gmail.com",
      "imap_port": 993,
      "imap_security": "tls",
      "smtp_host": "smtp.gmail.com",
      "smtp_port": 465,
      "smtp_security": "tls",
      "note": "Butuh Verifikasi 2 Langkah + App Password (myaccount.google.com/apppasswords). Password biasa tidak akan bisa.",
      "preset": "gmail"
    },
    "presets": [
      {
        "key": "gmail",
        "label": "Gmail / Google Workspace",
        "imap_host": "imap.gmail.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.gmail.com",
        "smtp_port": 465,
        "smtp_security": "tls",
        "note": "Butuh Verifikasi 2 Langkah + App Password (myaccount.google.com/apppasswords). Password biasa tidak akan bisa."
      },
      {
        "key": "outlook",
        "label": "Outlook / Microsoft 365",
        "imap_host": "outlook.office365.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.office365.com",
        "smtp_port": 587,
        "smtp_security": "starttls",
        "note": "Basic auth harus aktif untuk mailbox ini; banyak tenant Microsoft 365 hanya mengizinkan OAuth."
      },
      {
        "key": "icloud",
        "label": "iCloud Mail",
        "imap_host": "imap.mail.me.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.mail.me.com",
        "smtp_port": 587,
        "smtp_security": "starttls",
        "note": "Pakai app-specific password."
      },
      {
        "key": "yahoo",
        "label": "Yahoo Mail",
        "imap_host": "imap.mail.yahoo.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.mail.yahoo.com",
        "smtp_port": 465,
        "smtp_security": "tls",
        "note": "Pakai app password."
      },
      {
        "key": "zoho",
        "label": "Zoho Mail",
        "imap_host": "imap.zoho.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.zoho.com",
        "smtp_port": 465,
        "smtp_security": "tls"
      },
      {
        "key": "fastmail",
        "label": "Fastmail",
        "imap_host": "imap.fastmail.com",
        "imap_port": 993,
        "imap_security": "tls",
        "smtp_host": "smtp.fastmail.com",
        "smtp_port": 465,
        "smtp_security": "tls",
        "note": "Pakai app password."
      }
    ]
  }
}
```

Error khusus endpoint ini:

- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Daftar folder mailbox

`GET https://api.apipedia.id/v1/mail/folders`

Daftar folder IMAP yang bisa dipilih (folder \Noselect disaring) beserta jumlah email dan yang belum dibaca. `special_use` menandai folder khusus (\Inbox, \Sent, \Trash, \Drafts, \Junk, \All, \Flagged) sehingga Anda tidak perlu menebak nama lokal seperti "[Gmail]/Sent Mail". Inbox webhook selalu mengembalikan satu folder `INBOX`.

Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.

Prasyarat: mailbox harus terhubung lebih dulu - di halaman Koneksi console (console.apipedia.id/connections, kartu "Email IMAP/SMTP" atau "Inbox webhook") atau lewat POST /mail/accounts dan POST /mail/webhooks. Password disimpan terenkripsi AES-256-GCM dan tidak pernah dikembalikan; setiap permintaan langsung bicara ke server IMAP/SMTP Anda (isi email tidak di-cache di APIPedia).

| Parameter | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/folders?connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx"
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "Daftar folder berhasil diambil",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "items": [
      {
        "path": "INBOX",
        "name": "INBOX",
        "special_use": "\\Inbox",
        "messages": 1824,
        "unseen": 12
      },
      {
        "path": "[Gmail]/Sent Mail",
        "name": "Sent Mail",
        "special_use": "\\Sent",
        "messages": 640,
        "unseen": 0
      },
      {
        "path": "[Gmail]/Drafts",
        "name": "Drafts",
        "special_use": "\\Drafts",
        "messages": 3,
        "unseen": 0
      },
      {
        "path": "[Gmail]/Trash",
        "name": "Trash",
        "special_use": "\\Trash",
        "messages": 57,
        "unseen": 2
      },
      {
        "path": "Pesanan",
        "name": "Pesanan",
        "special_use": null,
        "messages": 311,
        "unseen": 4
      }
    ]
  }
}
```

Error khusus endpoint ini:

- **409**: Ada beberapa akun Email tanpa default dan connection_id tidak diisi. data.accounts berisi pilihan id. (`Anda punya 2 akun Email tanpa default. Sebutkan connection_id, atau tandai satu akun sebagai default di halaman Koneksi.`)
- **412**: Belum ada akun Email yang terhubung. Hubungkan dulu di halaman Koneksi console atau lewat POST /accounts. (`Belum ada akun Email yang terhubung. Hubungkan dulu di console.apipedia.id/connections.`)
- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Daftar dan cari email

`GET https://api.apipedia.id/v1/mail/messages`

Daftar email di satu folder, terbaru dulu, per halaman. Tanpa filter, halaman dihitung dari nomor urut (cepat untuk mailbox besar) dan `total` = jumlah email di folder. Dengan filter (`query`, `unread_only`, `flagged_only`, `since`), server menjalankan IMAP SEARCH: `query` dicocokkan ke subjek, pengirim, penerima, ATAU isi; `total` = jumlah yang cocok. Isi email tidak ikut - baca dengan GET /mail/messages/{uid}. Membaca daftar TIDAK menandai email sebagai dibaca.

Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.

Error lain: 400 since bukan tanggal ISO 8601; 401 login IMAP ditolak; 502 server email tidak bisa dihubungi.

| Parameter | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `folder` | query | string | tidak | Path folder IMAP (dari GET /mail/folders), mis. `INBOX`, `[Gmail]/Sent Mail`, `Archive`. Inbox webhook hanya punya `INBOX`. Default `INBOX`. |
| `limit` | query | integer | tidak | Email per halaman, 1-100. Default `25`. |
| `page` | query | integer | tidak | Nomor halaman, mulai 1. Lanjutkan selama `has_more` true. Default `1`. |
| `query` | query | string | tidak | Kata kunci (maks 200 karakter) di subjek, pengirim, penerima, atau isi. Alias: `q`. |
| `unread_only` | query | boolean | tidak | `true` atau `1`: hanya email yang belum dibaca. Default `false`. |
| `flagged_only` | query | boolean | tidak | `true` atau `1`: hanya email berbintang. Default `false`. |
| `since` | query | string | tidak | Hanya email sejak tanggal ini (ISO 8601), mis. `2026-11-01`. |
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/messages?folder=INBOX&limit=25&query=pesanan&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx"
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "Daftar email berhasil diambil",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "folder": "INBOX",
    "total": 37,
    "page": 1,
    "limit": 25,
    "has_more": true,
    "items": [
      {
        "uid": 48213,
        "subject": "Konfirmasi pesanan #INV-2026-1187",
        "from": {
          "name": "Rina Kartika",
          "address": "rina.kartika@contoh.co.id"
        },
        "to": [
          {
            "name": "Budi - Toko Sejahtera",
            "address": "budi@tokosejahtera.co.id"
          }
        ],
        "date": "2026-11-12T08:41:27.000Z",
        "message_id": "<CAF3x9qL0p2@mail.contoh.co.id>",
        "seen": false,
        "flagged": false,
        "answered": false,
        "size": 18342,
        "has_attachments": true
      },
      {
        "uid": 48207,
        "subject": "Re: Penawaran harga grosir November",
        "from": {
          "name": "Dedi Pratama",
          "address": "dedi@supplierjaya.id"
        },
        "to": [
          {
            "name": null,
            "address": "budi@tokosejahtera.co.id"
          }
        ],
        "date": "2026-11-11T14:05:10.000Z",
        "message_id": "<20261111140510.4f2a@supplierjaya.id>",
        "seen": true,
        "flagged": true,
        "answered": true,
        "size": 7421,
        "has_attachments": false
      }
    ]
  }
}
```

Error khusus endpoint ini:

- **409**: Ada beberapa akun Email tanpa default dan connection_id tidak diisi. data.accounts berisi pilihan id. (`Anda punya 2 akun Email tanpa default. Sebutkan connection_id, atau tandai satu akun sebagai default di halaman Koneksi.`)
- **412**: Belum ada akun Email yang terhubung. Hubungkan dulu di halaman Koneksi console atau lewat POST /accounts. (`Belum ada akun Email yang terhubung. Hubungkan dulu di console.apipedia.id/connections.`)
- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Baca satu email

`GET https://api.apipedia.id/v1/mail/messages/{uid}`

Mengambil satu email lengkap: header, isi `text` dan `html` (hasil parse MIME), penerima cc/reply-to, header threading (`in_reply_to`, `references`), dan daftar lampiran (`index` dipakai untuk mengunduh lewat /mail/messages/{uid}/attachments/{index}). Bawaannya email ditandai sudah dibaca (flag \Seen); kirim `mark_read=false` untuk membaca tanpa menandai.

Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.

Error lain: 400 uid tidak valid; 404 uid tidak ada di folder itu.

| Parameter | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `uid` | path | integer | ya | UID email di folder itu (angka bulat positif, dari GET /mail/messages). Untuk inbox webhook, uid = id email webhook. |
| `folder` | query | string | tidak | Path folder IMAP (dari GET /mail/folders), mis. `INBOX`, `[Gmail]/Sent Mail`, `Archive`. Inbox webhook hanya punya `INBOX`. Default `INBOX`. |
| `mark_read` | query | boolean | tidak | `false` supaya email TIDAK ditandai dibaca. Default `true`. |
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/messages/48213?folder=INBOX&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx"
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "Email berhasil diambil",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "folder": "INBOX",
    "message": {
      "uid": 48213,
      "subject": "Konfirmasi pesanan #INV-2026-1187",
      "from": {
        "name": "Rina Kartika",
        "address": "rina.kartika@contoh.co.id"
      },
      "to": [
        {
          "name": "Budi - Toko Sejahtera",
          "address": "budi@tokosejahtera.co.id"
        }
      ],
      "date": "2026-11-12T08:41:27.000Z",
      "message_id": "<CAF3x9qL0p2@mail.contoh.co.id>",
      "seen": true,
      "flagged": false,
      "answered": false,
      "size": 18342,
      "has_attachments": true,
      "cc": [
        {
          "name": "Admin Gudang",
          "address": "gudang@tokosejahtera.co.id"
        }
      ],
      "reply_to": [],
      "in_reply_to": null,
      "references": null,
      "text": "Halo Pak Budi,\n\nPesanan #INV-2026-1187 sudah kami terima dan akan dikirim besok pagi. Faktur terlampir.\n\nSalam,\nRina",
      "html": "<p>Halo Pak Budi,</p><p>Pesanan <b>#INV-2026-1187</b> sudah kami terima dan akan dikirim besok pagi. Faktur terlampir.</p><p>Salam,<br>Rina</p>",
      "attachments": [
        {
          "index": 0,
          "filename": "faktur-INV-2026-1187.pdf",
          "content_type": "application/pdf",
          "size": 15210,
          "inline": false
        }
      ]
    }
  }
}
```

Error khusus endpoint ini:

- **409**: Ada beberapa akun Email tanpa default dan connection_id tidak diisi. data.accounts berisi pilihan id. (`Anda punya 2 akun Email tanpa default. Sebutkan connection_id, atau tandai satu akun sebagai default di halaman Koneksi.`)
- **412**: Belum ada akun Email yang terhubung. Hubungkan dulu di halaman Koneksi console atau lewat POST /accounts. (`Belum ada akun Email yang terhubung. Hubungkan dulu di console.apipedia.id/connections.`)
- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Tandai dibaca/belum dibaca atau berbintang

`PATCH https://api.apipedia.id/v1/mail/messages/{uid}`

Mengubah flag IMAP sebuah email: `seen` (\Seen, dibaca) dan/atau `flagged` (\Flagged, berbintang). Isi minimal salah satu, bertipe boolean. `folder` dan `connection_id` boleh di query atau body. Jawaban menggemakan nilai yang dikirim (field yang tidak dikirim tidak muncul).

Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.

Error lain: 400 uid tidak valid atau tidak ada seen/flagged boolean; 404 uid tidak ditemukan (inbox webhook).

| Parameter | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `uid` | path | integer | ya | UID email di folder itu (angka bulat positif, dari GET /mail/messages). Untuk inbox webhook, uid = id email webhook. |
| `folder` | query | string | tidak | Path folder IMAP (dari GET /mail/folders), mis. `INBOX`, `[Gmail]/Sent Mail`, `Archive`. Inbox webhook hanya punya `INBOX`. Default `INBOX`. |
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. |

Body (`application/json`, wajib):

```json
{
  "seen": true,
  "flagged": true
}
```

Contoh request:

```bash
curl -X PATCH "https://api.apipedia.id/v1/mail/messages/48213?folder=INBOX&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"seen":true,"flagged":true}'
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "Status email diperbarui",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "folder": "INBOX",
    "uid": 48213,
    "seen": true,
    "flagged": true
  }
}
```

Error khusus endpoint ini:

- **409**: Ada beberapa akun Email tanpa default dan connection_id tidak diisi. data.accounts berisi pilihan id. (`Anda punya 2 akun Email tanpa default. Sebutkan connection_id, atau tandai satu akun sebagai default di halaman Koneksi.`)
- **412**: Belum ada akun Email yang terhubung. Hubungkan dulu di halaman Koneksi console atau lewat POST /accounts. (`Belum ada akun Email yang terhubung. Hubungkan dulu di console.apipedia.id/connections.`)
- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Pindahkan email ke Sampah

`DELETE https://api.apipedia.id/v1/mail/messages/{uid}`

Memindahkan email ke folder Sampah mailbox (folder ber-special-use \Trash) dan mengembalikan namanya di `moved_to`. Email dihapus permanen (`moved_to: null`) bila `permanent=true`, bila server tidak punya folder Sampah, atau bila email sudah berada di folder Sampah. Email inbox webhook selalu dihapus permanen.

Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.

Error lain: 400 uid tidak valid; 404 uid tidak ditemukan (inbox webhook).

| Parameter | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `uid` | path | integer | ya | UID email di folder itu (angka bulat positif, dari GET /mail/messages). Untuk inbox webhook, uid = id email webhook. |
| `folder` | query | string | tidak | Path folder IMAP (dari GET /mail/folders), mis. `INBOX`, `[Gmail]/Sent Mail`, `Archive`. Inbox webhook hanya punya `INBOX`. Default `INBOX`. |
| `permanent` | query | boolean | tidak | `true` untuk menghapus permanen tanpa lewat Sampah. Tidak bisa dibatalkan. Default `false`. |
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. |

Contoh request:

```bash
curl -X DELETE "https://api.apipedia.id/v1/mail/messages/48213?folder=INBOX&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx"
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "Email dipindahkan ke [Gmail]/Trash",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "uid": 48213,
    "moved_to": "[Gmail]/Trash"
  }
}
```

Error khusus endpoint ini:

- **409**: Ada beberapa akun Email tanpa default dan connection_id tidak diisi. data.accounts berisi pilihan id. (`Anda punya 2 akun Email tanpa default. Sebutkan connection_id, atau tandai satu akun sebagai default di halaman Koneksi.`)
- **412**: Belum ada akun Email yang terhubung. Hubungkan dulu di halaman Koneksi console atau lewat POST /accounts. (`Belum ada akun Email yang terhubung. Hubungkan dulu di console.apipedia.id/connections.`)
- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Unduh lampiran email

`GET https://api.apipedia.id/v1/mail/messages/{uid}/attachments/{index}`

Mengunduh satu lampiran sebagai berkas mentah dengan `Content-Type` asli lampiran dan `Content-Disposition: attachment; filename=...`. `index` diambil dari `message.attachments[].index` di GET /mail/messages/{uid}. Jawaban sukses BUKAN JSON; error tetap amplop JSON. Inbox webhook tidak menyimpan lampiran (selalu 404).

Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.

Error lain: 400 uid tidak valid; 404 email atau lampiran tidak ditemukan.

| Parameter | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `uid` | path | integer | ya | UID email di folder itu (angka bulat positif, dari GET /mail/messages). Untuk inbox webhook, uid = id email webhook. |
| `index` | path | integer | ya | Urutan lampiran, mulai 0. |
| `folder` | query | string | tidak | Path folder IMAP (dari GET /mail/folders), mis. `INBOX`, `[Gmail]/Sent Mail`, `Archive`. Inbox webhook hanya punya `INBOX`. Default `INBOX`. |
| `connection_id` | query | string | tidak | Id koneksi mailbox (uuid, dari GET /accounts produk ini atau GET /connections). Kosongkan untuk memakai akun default, atau satu-satunya akun. |

Contoh request:

```bash
curl -X GET "https://api.apipedia.id/v1/mail/messages/48213/attachments/0?folder=INBOX&connection_id=3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx"
```

Error khusus endpoint ini:

- **409**: Ada beberapa akun Email tanpa default dan connection_id tidak diisi. data.accounts berisi pilihan id. (`Anda punya 2 akun Email tanpa default. Sebutkan connection_id, atau tandai satu akun sebagai default di halaman Koneksi.`)
- **412**: Belum ada akun Email yang terhubung. Hubungkan dulu di halaman Koneksi console atau lewat POST /accounts. (`Belum ada akun Email yang terhubung. Hubungkan dulu di console.apipedia.id/connections.`)
- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Kirim atau balas email

`POST https://api.apipedia.id/v1/mail/send`

Mengirim email lewat SMTP mailbox yang dipilih, dari alamat mailbox itu (nama pengirim = `from_name` saat dihubungkan). Salinan disimpan ke folder Terkirim (\Sent) lewat IMAP - Gmail melakukannya sendiri; `saved_to_sent` false berarti salinan gagal disimpan, email TETAP terkirim.

Field:
- `to` (wajib kecuali membalas), `cc`, `bcc`: string dipisah koma atau array, maks 50 alamat per field.
- `subject` (maks 998 karakter): wajib kecuali membalas - saat membalas bawaannya "Re: <subjek asli>".
- `text` dan/atau `html`: salah satu wajib, total maks 2 MB.
- `attachments`: maks 10, tiap item `{ filename, content_base64, content_type? }`. Seluruh body JSON maks 12 MB.
- `reply_to_uid` + `folder` (bawaan INBOX): membalas email itu - header In-Reply-To/References diisi, `to` bawaan = Reply-To/pengirim asli, dan email asli ditandai \Answered.

Endpoint ini punya efek samping nyata (email terkirim atas nama pemilik mailbox). Inbox webhook tidak bisa mengirim (400).

Akun dipilih lewat `connection_id` (query, atau body untuk POST/PATCH). Tanpa itu dipakai akun default, atau satu-satunya akun Email. Error pemilihan akun: 400 connection_id bukan uuid; 404 connection_id tidak ditemukan/sudah diputus; 409 beberapa akun tanpa default (`data.accounts` berisi pilihan); 412 belum ada akun terhubung.

Error lain: 400 field tidak valid; 404 email yang dibalas tidak ditemukan; 413 isi lebih dari 2 MB; 401/502 server SMTP menolak atau tidak bisa dihubungi.

Body (`application/json`, wajib):

```json
{
  "to": [
    "rina.kartika@contoh.co.id"
  ],
  "cc": "gudang@tokosejahtera.co.id",
  "subject": "Faktur pesanan #INV-2026-1187",
  "text": "Halo Bu Rina,\n\nTerlampir faktur pesanan #INV-2026-1187. Terima kasih.\n\nSalam,\nBudi",
  "html": "<p>Halo Bu Rina,</p><p>Terlampir faktur pesanan <b>#INV-2026-1187</b>. Terima kasih.</p><p>Salam,<br>Budi</p>",
  "attachments": [
    {
      "filename": "faktur.txt",
      "content_base64": "RmFrdHVyICNJTlYtMjAyNi0xMTg3",
      "content_type": "text/plain"
    }
  ],
  "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/mail/send" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"to":["rina.kartika@contoh.co.id"],"cc":"gudang@tokosejahtera.co.id","subject":"Faktur pesanan #INV-2026-1187","text":"Halo Bu Rina,\n\nTerlampir faktur pesanan #INV-2026-1187. Terima kasih.\n\nSalam,\nBudi","html":"<p>Halo Bu Rina,</p><p>Terlampir faktur pesanan <b>#INV-2026-1187</b>. Terima kasih.</p><p>Salam,<br>Budi</p>","attachments":[{"filename":"faktur.txt","content_base64":"RmFrdHVyICNJTlYtMjAyNi0xMTg3","content_type":"text/plain"}],"connection_id":"3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d"}'
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "Email berhasil dikirim",
  "data": {
    "connection_id": "3f9c2a71-8b4e-4d2a-9c61-5e7f0a1b2c3d",
    "message_id": "<7c1e5f0a-3b2d-4e8f-9a61-0d2c4b6e8f10@tokosejahtera.co.id>",
    "accepted": [
      "rina.kartika@contoh.co.id",
      "gudang@tokosejahtera.co.id"
    ],
    "rejected": [],
    "saved_to_sent": true
  }
}
```

Error khusus endpoint ini:

- **409**: Ada beberapa akun Email tanpa default dan connection_id tidak diisi. data.accounts berisi pilihan id. (`Anda punya 2 akun Email tanpa default. Sebutkan connection_id, atau tandai satu akun sebagai default di halaman Koneksi.`)
- **412**: Belum ada akun Email yang terhubung. Hubungkan dulu di halaman Koneksi console atau lewat POST /accounts. (`Belum ada akun Email yang terhubung. Hubungkan dulu di console.apipedia.id/connections.`)
- **413**: Isi text + html lebih dari 2 MB, atau body JSON lebih dari 12 MB (`Isi email terlalu besar (maks 2 MB).`)
- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Buat inbox webhook (URL rahasia)

`POST https://api.apipedia.id/v1/mail/webhooks`

Membuat kotak masuk berjenis webhook (toolkit `email-webhook`) dan mengembalikan `webhook_url` rahasia SEKALI saja - simpan sekarang; yang disimpan APIPedia hanya hash SHA-256-nya. Sistem lain (toko online, form, monitoring) cukup POST email JSON ke URL itu; email muncul di halaman Email console dan di GET /mail/messages seperti mailbox lain (pilih dengan `connection_id` inbox ini). Inbox webhook hanya MENERIMA: kirim/balas dari akun ini ditolak.

Mengirim ke `webhook_url` (publik, TANPA token, `Content-Type: application/json`, body maks 600 KB, maks 60 email/menit per inbox):
- `from` (wajib, alamat email), `from_name` (≤200 karakter)
- `to` (wajib, array 1-50 alamat), `cc` (array ≤50)
- `subject` (wajib, satu baris, ≤998 karakter)
- `text` (≤200 KB) dan/atau `html` (≤400 KB): salah satu wajib
- `date` (ISO 8601, ≤24 jam ke depan; bawaan sekarang), `message_id` (tanpa spasi, ≤300; dipakai untuk dedup)

Field lain ditolak. Jawaban: 201 `{ uid, duplicate: false }` ("Email diterima"), 200 `{ uid: null, duplicate: true }` bila message_id sudah ada, 400 bukan JSON valid, 404 URL salah/inbox diputus, 413 lebih dari 600 KB, 415 Content-Type bukan JSON, 422 validasi (`data.errors` berisi daftar kesalahan), 429 lebih dari 60 email/menit.

Contoh: `curl -X POST "<webhook_url>" -H "Content-Type: application/json" -d '{"from":"noreply@tokoonline.id","to":["budi@tokosejahtera.co.id"],"subject":"Pesanan baru #1187","text":"Ada pesanan baru."}'`

Body (`application/json`):

```json
{
  "label": "Pesanan toko online"
}
```

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/mail/webhooks" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"label":"Pesanan toko online"}'
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "Inbox webhook dibuat. Simpan URL-nya sekarang: tidak akan ditampilkan lagi.",
  "data": {
    "connection": {
      "id": "8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57",
      "toolkit": "email-webhook",
      "accountLabel": "Pesanan toko online",
      "isDefault": true,
      "connectedAt": "2026-11-10T02:14:09.512Z",
      "meta": {
        "kind": "webhook"
      }
    },
    "webhook_url": "https://api.apipedia.id/v1/mail-hook/q8ZxV3nK1pT7rWm2cYb5hLd0sFj4gHe9"
  }
}
```

Error khusus endpoint ini:

- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)

### Ganti URL inbox webhook

`POST https://api.apipedia.id/v1/mail/webhooks/{id}/rotate`

Membuat URL rahasia baru untuk inbox webhook; URL lama LANGSUNG tidak berlaku (404). Email yang sudah diterima tidak terpengaruh. URL baru hanya tampil di jawaban ini.

Error: 400 id bukan uuid; 404 inbox webhook tidak ditemukan (id milik mailbox IMAP juga 404).

| Parameter | Lokasi | Tipe | Wajib | Keterangan |
|---|---|---|---|---|
| `id` | path | string | ya | Id koneksi inbox webhook (uuid) dari GET /mail/accounts. |

Contoh request:

```bash
curl -X POST "https://api.apipedia.id/v1/mail/webhooks/8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57/rotate" \
  -H "Authorization: Bearer apk_xxxxxxxxxxxxxxxx"
```

Contoh respons (200):

```json
{
  "is_success": true,
  "message": "URL webhook diganti. URL lama tidak berlaku lagi.",
  "data": {
    "connection_id": "8d41e0b6-27c3-4f5a-b9e2-6a0c1d3e4f57",
    "webhook_url": "https://api.apipedia.id/v1/mail-hook/Lm4Rt8Wq2Zx6Cv0Bn3Kj7Hg1Fd5Sa9Pe"
  }
}
```

Error khusus endpoint ini:

- **503**: Kunci enkripsi kredensial (CONNECTION_SECRET_KEY) belum diatur di server (`Fitur ini belum diaktifkan di server: CONNECTION_SECRET_KEY belum diatur. Hubungi admin APIPedia.`)
