{
  "info": {
    "_postman_id": "c611e5f8-142f-4faf-b39d-746fdba12b65",
    "name": "InboxRhino API",
    "description": "InboxRhino public /v1 API. Free collection — import this JSON in Postman (File → Import). Set collection variable apiKey to your live key from https://app.inboxrhino.in. The collection never ships a real key.\n\nBase URLs: https://api.inboxrhino.in (REST), https://files.inboxrhino.in (attachments). Receiving domain test.inboxrhino.in is SMTP only.\n\nAuth: Authorization Bearer ir_live_<16-hex>_<48-hex>. Keys cannot manage billing, members, or other keys. Do not call /console/* or /setup.\n\nRecommended flow:\n1. Health Check\n2. Usage (optional)\n3. Create Inbox — stores inboxId and inboxAddress\n4. Send a real email to inboxAddress with subject equal to expectedSubject\n5. Wait for Matching Email (wait_seconds up to 180). HTTP 200 = match. HTTP 204 = timeout with empty body; do not parse JSON.\n6. Get Message / Download First Attachment as needed\n7. Delete Current Inbox last so quota is released\n\nAgent notes:\n- include=content requires limit=1\n- Empty non-waiting lists return 200 { data: [], next_cursor: null }\n- Invalid query values return 422 and are never clamped\n- Filters are AND: subject (substring), sender (exact email), sender_domain (exact domain), received_after (RFC 3339)\n- There is no org-wide message search and no send endpoint\n- Attachment bytes are not malware-scanned\n- Docs: https://inboxrhino.in/docs/api  OpenAPI: https://inboxrhino.in/openapi.yaml",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.inboxrhino.in",
      "type": "string"
    },
    {
      "key": "filesBaseUrl",
      "value": "https://files.inboxrhino.in",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "secret"
    },
    {
      "key": "inboxPrefix",
      "value": "",
      "type": "string"
    },
    {
      "key": "idempotencyKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "inboxId",
      "value": "",
      "type": "string"
    },
    {
      "key": "inboxAddress",
      "value": "",
      "type": "string"
    },
    {
      "key": "messageId",
      "value": "",
      "type": "string"
    },
    {
      "key": "attachmentId",
      "value": "",
      "type": "string"
    },
    {
      "key": "expectedSubject",
      "value": "InboxRhino Postman test",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "1. Service and account",
      "item": [
        {
          "name": "Health Check",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/health",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "health"
              ]
            },
            "description": "Confirms that the production Worker is responding. No API key is required."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('API is healthy', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.status).to.eql('ok');",
                  "  pm.expect(body.service).to.eql('inboxrhino-api');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "Usage",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/usage",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "usage"
              ]
            },
            "description": "Shows the current inbox and email quota. Response is a bare object (no data wrapper). plan is currently always free. Limits: 11 active inboxes, 33 emails per UTC month."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Usage returned', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body).to.have.property('inboxes');",
                  "  pm.expect(body).to.have.property('emails');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "2. Inbox lifecycle",
      "item": [
        {
          "name": "Create Inbox",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const suffix = Date.now().toString(36).slice(-8);",
                  "pm.collectionVariables.set('inboxPrefix', `postman-${suffix}`);",
                  "pm.collectionVariables.set('idempotencyKey', `postman-${Date.now()}`);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Inbox created', function () {",
                  "  pm.response.to.have.status(201);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.data.address).to.include('@test.inboxrhino.in');",
                  "  pm.collectionVariables.set('inboxId', body.data.id);",
                  "  pm.collectionVariables.set('inboxAddress', body.data.address);",
                  "  console.log('Send the test email to:', body.data.address);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotencyKey}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"prefix\": \"{{inboxPrefix}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/inboxes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inboxes"
              ]
            },
            "description": "Creates a real receive-only address and its exact Cloudflare Email Routing rule. The response tests save inboxId and inboxAddress for later requests."
          }
        },
        {
          "name": "List Inboxes",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Inboxes returned', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.data).to.be.an('array');",
                  "  if (body.data.length && !pm.collectionVariables.get('inboxId')) {",
                  "    pm.collectionVariables.set('inboxId', body.data[0].id);",
                  "    pm.collectionVariables.set('inboxAddress', body.data[0].address);",
                  "  }",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/inboxes?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inboxes"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Integer 1–100. Invalid values return 422, not a clamped page."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque next_cursor from the previous page."
                }
              ]
            },
            "description": "Lists active inboxes. If inboxId is empty, the first inbox is selected automatically."
          }
        },
        {
          "name": "Get Current Inbox",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/inboxes/{{inboxId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inboxes",
                "{{inboxId}}"
              ]
            },
            "description": "Returns the inbox selected by Create Inbox or List Inboxes."
          }
        },
        {
          "name": "Delete Current Inbox",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/inboxes/{{inboxId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inboxes",
                "{{inboxId}}"
              ]
            },
            "description": "DESTRUCTIVE: removes the exact Cloudflare route and deletes all retained messages for the selected inbox. Run this last."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Inbox deleted', function () {",
                  "  pm.response.to.have.status(204);",
                  "  pm.collectionVariables.unset('inboxId');",
                  "  pm.collectionVariables.unset('inboxAddress');",
                  "  pm.collectionVariables.unset('messageId');",
                  "  pm.collectionVariables.unset('attachmentId');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "3. Receive and inspect email",
      "description": "After Create Inbox, send a real email to {{inboxAddress}}. Give it the subject stored in expectedSubject, then run Wait for Matching Email.",
      "item": [
        {
          "name": "Wait for Matching Email",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/inboxes/{{inboxId}}/messages?wait_seconds=180&limit=1&include=content&subject={{expectedSubject}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inboxes",
                "{{inboxId}}",
                "messages"
              ],
              "query": [
                {
                  "key": "wait_seconds",
                  "value": "180",
                  "description": "Long-poll for up to 180 seconds. 0 returns immediately. HTTP 204 means timeout."
                },
                {
                  "key": "limit",
                  "value": "1",
                  "description": "Must be 1 when include=content."
                },
                {
                  "key": "include",
                  "value": "content"
                },
                {
                  "key": "subject",
                  "value": "{{expectedSubject}}",
                  "description": "Case-insensitive substring match."
                },
                {
                  "key": "sender",
                  "value": "noreply@example.com",
                  "disabled": true,
                  "description": "Exact sender email. Enable if you need to filter by From."
                },
                {
                  "key": "sender_domain",
                  "value": "example.com",
                  "disabled": true,
                  "description": "Exact sender domain. Do not include @."
                },
                {
                  "key": "received_after",
                  "value": "2026-09-03T00:00:00Z",
                  "disabled": true,
                  "description": "RFC 3339. Ignore older matching mail still in the inbox."
                }
              ]
            },
            "description": "Waits up to 180 seconds for an email whose subject contains expectedSubject. HTTP 200 returns matching messages. HTTP 204 means no match; body is empty so do not parse JSON. include=content requires limit=1. Optional filters (sender, sender_domain, received_after) are present but disabled."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Email received or wait timed out cleanly', function () {",
                  "  pm.expect([200, 204]).to.include(pm.response.code);",
                  "});",
                  "if (pm.response.code === 200) {",
                  "  const body = pm.response.json();",
                  "  pm.test('Matching message returned', function () {",
                  "    pm.expect(body.data).to.be.an('array').that.is.not.empty;",
                  "  });",
                  "  const message = body.data[0];",
                  "  pm.collectionVariables.set('messageId', message.id);",
                  "  if (message.attachments && message.attachments.length) {",
                  "    pm.collectionVariables.set('attachmentId', message.attachments[0].id);",
                  "  }",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "List Messages Immediately",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/inboxes/{{inboxId}}/messages?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "inboxes",
                "{{inboxId}}",
                "messages"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Integer 1–100. Invalid values return 422, not a clamped page."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque next_cursor from the previous page."
                }
              ]
            },
            "description": "Returns retained message summaries without waiting. HTTP 200 with data: [] means the inbox is empty. HTTP 204 is only used when wait_seconds > 0 times out."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Messages returned', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.data).to.be.an('array');",
                  "  if (body.data.length) pm.collectionVariables.set('messageId', body.data[0].id);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "Get Current Message",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/messages/{{messageId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "messages",
                "{{messageId}}"
              ]
            },
            "description": "Returns complete text, HTML, headers and attachment metadata for the selected message."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Complete message returned', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const message = pm.response.json().data;",
                  "  pm.expect(message.id).to.eql(pm.collectionVariables.get('messageId'));",
                  "  if (message.attachments && message.attachments.length) {",
                  "    pm.collectionVariables.set('attachmentId', message.attachments[0].id);",
                  "  }",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "Download First Attachment",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{filesBaseUrl}}/v1/attachments/{{attachmentId}}",
              "host": [
                "{{filesBaseUrl}}"
              ],
              "path": [
                "v1",
                "attachments",
                "{{attachmentId}}"
              ]
            },
            "description": "Downloads the attachment selected from Wait for Matching Email or Get Current Message. Host is files.inboxrhino.in. Same Bearer API key. Response is raw bytes with Content-Disposition attachment, not JSON. Content is not malware-scanned. Use Send and Download in Postman to save it."
          }
        },
        {
          "name": "Delete Current Message",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/messages/{{messageId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "messages",
                "{{messageId}}"
              ]
            },
            "description": "DESTRUCTIVE: deletes the selected message and its stored content/attachments."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Message deleted', function () {",
                  "  pm.response.to.have.status(204);",
                  "  pm.collectionVariables.unset('messageId');",
                  "  pm.collectionVariables.unset('attachmentId');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}
