{
  "info": {
    "name": "ProspectMailer",
    "description": "Admin provisioning, tenant SMTP and email/report APIs. Set values in the collection Variables tab. No environment is needed. Before request scripts use these collection values. Send, rotation and deletion are explicit actions.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Admin",
      "description": "Operator key required. Create tenants, manage SMTP, regenerate keys and delete tenants. Never hand this key to a tenant.",
      "item": [
        {
          "name": "Create a tenant and issue its first key",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{baseUrl}}/api/admin/tenants",
            "description": "## Overview\nCreate tenant metadata and a unique API key atomically. SMTP can be configured afterward.\n\n## Authentication\n**Admin key** in the `ApiKey` header. Tenant keys return 403.\n\n## Request fields\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `name` | Required | 1–200 characters, no control characters. |\n| `contactEmail` | Optional | Bare email address; omit/null to clear. |\n| `isActive` | Optional | Defaults to true; false blocks the tenant key. |\n\n### JSON example\n```json\n{\n  \"name\": \"Example automation\",\n  \"contactEmail\": \"owner@example.com\",\n  \"isActive\": true\n}\n```\n\n## Response\n**HTTP 201** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `tenant` | object | Tenant metadata as described below. |\n| `apiKey` | string | Full new key; returned once only. |\n| `note` | string | Save and securely hand off the key. |\n| `tenant.id` | UUID | Tenant identifier. |\n| `tenant.name` | string | Tenant display name. |\n| `tenant.contactEmail` | string/null | Optional contact address. |\n| `tenant.isActive` | boolean | Whether the key is enabled. |\n| `tenant.createdAt` | timestamp | Creation time (UTC). |\n| `tenant.smtpConfigured` | boolean | SMTP settings have been saved; does not test delivery. |\n| `tenant.keyIdentifier` | string | Masked identifier, not a usable key. |\n| `tenant.keyCreatedAt` | timestamp | Current key creation/rotation time. |\n\n```json\n{\n  \"tenant\": {\n    \"id\": \"11111111-1111-4111-8111-111111111111\",\n    \"name\": \"Example automation\",\n    \"contactEmail\": \"owner@example.com\",\n    \"isActive\": true,\n    \"createdAt\": \"2026-10-07T12:00:00Z\",\n    \"smtpConfigured\": false,\n    \"keyIdentifier\": \"pmt_12345678…\",\n    \"keyCreatedAt\": \"2026-10-07T12:00:00Z\"\n  },\n  \"apiKey\": \"pmt_<identifier>.<secret>\",\n  \"note\": \"Save this key now and hand it to the tenant securely. It cannot be retrieved later.\"\n}\n```\n\n## Important\nThe full key is returned once. Save it securely and hand it to the tenant manually. An inactive tenant receives a key that cannot authenticate until reactivated.\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{adminApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Example automation\",\n  \"contactEmail\": \"owner@example.com\",\n  \"isActive\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 201\", function () { pm.response.to.have.status(201); });",
                  "if (pm.response.code === 201) {",
                  "  const data = pm.response.json();",
                  "  pm.test(\"New key and tenant returned\", () => { pm.expect(data.apiKey).to.be.a(\"string\"); pm.expect(data.tenant.id).to.be.a(\"string\"); });",
                  "  pm.collectionVariables.set(\"tenantId\", data.tenant.id);",
                  "  pm.collectionVariables.set(\"tenantApiKey\", data.apiKey);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "List tenants with pagination",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/admin/tenants?page=1&pageSize=50",
            "description": "## Overview\nList tenant metadata, masked key identifiers and SMTP setup state. No secrets are included.\n\n## Authentication\n**Admin key** in the `ApiKey` header. Tenant keys return 403.\n\n## Path and query parameters\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `page` | Optional query integer | Default 1; range 1–1000000. |\n| `pageSize` | Optional query integer | Default 50; range 1–100. |\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `items` | array | Tenant metadata objects. |\n| `page` | integer | Current page. |\n| `pageSize` | integer | Current page size. |\n| `total` | integer | Total tenants. |\n| `items[].id` | UUID | Tenant identifier. |\n| `items[].name` | string | Tenant display name. |\n| `items[].contactEmail` | string/null | Optional contact address. |\n| `items[].isActive` | boolean | Whether the key is enabled. |\n| `items[].createdAt` | timestamp | Creation time (UTC). |\n| `items[].smtpConfigured` | boolean | SMTP settings have been saved; does not test delivery. |\n| `items[].keyIdentifier` | string | Masked identifier, not a usable key. |\n| `items[].keyCreatedAt` | timestamp | Current key creation/rotation time. |\n\n```json\n{\n  \"items\": [\n    {\n      \"id\": \"11111111-1111-4111-8111-111111111111\",\n      \"name\": \"Example automation\",\n      \"contactEmail\": \"owner@example.com\",\n      \"isActive\": true,\n      \"createdAt\": \"2026-10-07T12:00:00Z\",\n      \"smtpConfigured\": false,\n      \"keyIdentifier\": \"pmt_12345678…\",\n      \"keyCreatedAt\": \"2026-10-07T12:00:00Z\"\n    }\n  ],\n  \"page\": 1,\n  \"pageSize\": 50,\n  \"total\": 1\n}\n```\n\n## Important\nRequests are limited to 120 per minute per tenant (admin requests have a separate quota).\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{adminApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) { pm.test(\"JSON response\", () => { pm.expect(pm.response.json()).to.be.an(\"object\"); }); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Read a tenant",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/admin/tenants/{{tenantId}}",
            "description": "## Overview\nFetch metadata and setup state for one tenant.\n\n## Authentication\n**Admin key** in the `ApiKey` header. Tenant keys return 403.\n\n## Path and query parameters\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `tenantId` | Required path UUID | Target tenant created by the admin. |\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `id` | UUID | Tenant identifier. |\n| `name` | string | Tenant display name. |\n| `contactEmail` | string/null | Optional contact address. |\n| `isActive` | boolean | Whether the key is enabled. |\n| `createdAt` | timestamp | Creation time (UTC). |\n| `smtpConfigured` | boolean | SMTP settings have been saved; does not test delivery. |\n| `keyIdentifier` | string | Masked identifier, not a usable key. |\n| `keyCreatedAt` | timestamp | Current key creation/rotation time. |\n\n```json\n{\n  \"id\": \"11111111-1111-4111-8111-111111111111\",\n  \"name\": \"Example automation\",\n  \"contactEmail\": \"owner@example.com\",\n  \"isActive\": true,\n  \"createdAt\": \"2026-10-07T12:00:00Z\",\n  \"smtpConfigured\": false,\n  \"keyIdentifier\": \"pmt_12345678…\",\n  \"keyCreatedAt\": \"2026-10-07T12:00:00Z\"\n}\n```\n\n## Important\nRequests are limited to 120 per minute per tenant (admin requests have a separate quota).\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. 404 unknown tenant. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{adminApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) { pm.test(\"JSON response\", () => { pm.expect(pm.response.json()).to.be.an(\"object\"); }); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Update tenant metadata or active status",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{baseUrl}}/api/admin/tenants/{{tenantId}}",
            "description": "## Overview\nReplace name, contact email and active status. SMTP settings and key are preserved.\n\n## Authentication\n**Admin key** in the `ApiKey` header. Tenant keys return 403.\n\n## Path and query parameters\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `tenantId` | Required path UUID | Target tenant created by the admin. |\n\n## Request fields\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `name` | Required | 1–200 characters, no control characters. |\n| `contactEmail` | Optional | Bare email address; omit/null to clear. |\n| `isActive` | Optional | Defaults to true; false blocks the tenant key. |\n\n### JSON example\n```json\n{\n  \"name\": \"Example automation\",\n  \"contactEmail\": \"owner@example.com\",\n  \"isActive\": true\n}\n```\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `id` | UUID | Tenant identifier. |\n| `name` | string | Tenant display name. |\n| `contactEmail` | string/null | Optional contact address. |\n| `isActive` | boolean | Whether the key is enabled. |\n| `createdAt` | timestamp | Creation time (UTC). |\n| `smtpConfigured` | boolean | SMTP settings have been saved; does not test delivery. |\n| `keyIdentifier` | string | Masked identifier, not a usable key. |\n| `keyCreatedAt` | timestamp | Current key creation/rotation time. |\n\n```json\n{\n  \"id\": \"11111111-1111-4111-8111-111111111111\",\n  \"name\": \"Example automation\",\n  \"contactEmail\": \"owner@example.com\",\n  \"isActive\": true,\n  \"createdAt\": \"2026-10-07T12:00:00Z\",\n  \"smtpConfigured\": false,\n  \"keyIdentifier\": \"pmt_12345678…\",\n  \"keyCreatedAt\": \"2026-10-07T12:00:00Z\"\n}\n```\n\n## Important\nOmitting isActive defaults to true. Set false to immediately disable the tenant key without deleting stored configuration. Omit/null contactEmail to clear it.\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. 404 unknown tenant. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{adminApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Example automation\",\n  \"contactEmail\": \"owner@example.com\",\n  \"isActive\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) { pm.test(\"JSON response\", () => { pm.expect(pm.response.json()).to.be.an(\"object\"); }); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Regenerate a tenant API key [REVOKES OLD KEY]",
          "request": {
            "method": "POST",
            "header": [],
            "url": "{{baseUrl}}/api/admin/tenants/{{tenantId}}/api-key/regenerate",
            "description": "## Overview\nAtomically replace the tenant key and return its full new value.\n\n## Authentication\n**Admin key** in the `ApiKey` header. Tenant keys return 403.\n\n## Path and query parameters\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `tenantId` | Required path UUID | Target tenant created by the admin. |\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `tenant` | object | Tenant metadata as described below. |\n| `apiKey` | string | Full new key; returned once only. |\n| `note` | string | Save and securely hand off the key. |\n| `tenant.id` | UUID | Tenant identifier. |\n| `tenant.name` | string | Tenant display name. |\n| `tenant.contactEmail` | string/null | Optional contact address. |\n| `tenant.isActive` | boolean | Whether the key is enabled. |\n| `tenant.createdAt` | timestamp | Creation time (UTC). |\n| `tenant.smtpConfigured` | boolean | SMTP settings have been saved; does not test delivery. |\n| `tenant.keyIdentifier` | string | Masked identifier, not a usable key. |\n| `tenant.keyCreatedAt` | timestamp | Current key creation/rotation time. |\n\n```json\n{\n  \"tenant\": {\n    \"id\": \"11111111-1111-4111-8111-111111111111\",\n    \"name\": \"Example automation\",\n    \"contactEmail\": \"owner@example.com\",\n    \"isActive\": true,\n    \"createdAt\": \"2026-10-07T12:00:00Z\",\n    \"smtpConfigured\": false,\n    \"keyIdentifier\": \"pmt_12345678…\",\n    \"keyCreatedAt\": \"2026-10-07T12:00:00Z\"\n  },\n  \"apiKey\": \"pmt_<identifier>.<secret>\",\n  \"note\": \"Save this key now and hand it to the tenant securely. It cannot be retrieved later.\"\n}\n```\n\n## Important\nThe old key stops working immediately. No overlap or automatic handoff. Save the new key and update the tenant automation secret. SMTP and metadata are unchanged.\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. 404 unknown tenant. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{adminApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) {",
                  "  const data = pm.response.json();",
                  "  pm.test(\"New key and tenant returned\", () => { pm.expect(data.apiKey).to.be.a(\"string\"); pm.expect(data.tenant.id).to.be.a(\"string\"); });",
                  "  pm.collectionVariables.set(\"tenantId\", data.tenant.id);",
                  "  pm.collectionVariables.set(\"tenantApiKey\", data.apiKey);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Read tenant SMTP settings",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/admin/tenants/{{tenantId}}/smtp",
            "description": "## Overview\nReturns destination, sender and credential-presence information without revealing saved secrets.\n\n## Authentication\n**Admin key** in the `ApiKey` header. Tenant keys return 403.\n\n## Path and query parameters\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `tenantId` | Required path UUID | Target tenant created by the admin. |\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `host` | string | Configured destination. |\n| `port` | integer | SMTP port. |\n| `security` | string | TLS mode. |\n| `fromEmail` | string | Sender email. |\n| `fromName` | string | Sender display name. |\n| `hasCredentials` | boolean | Encrypted credentials exist; username/password/ciphertext are never returned. |\n\n```json\n{\n  \"host\": \"smtp.your-provider.example\",\n  \"port\": 587,\n  \"security\": \"StartTls\",\n  \"fromEmail\": \"reports@example.com\",\n  \"fromName\": \"Automation Reports\",\n  \"hasCredentials\": true\n}\n```\n\n## Important\nNeither username, password nor encrypted credentials are returned. HTTP 409 means SMTP has not been configured.\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. 404 unknown tenant. 409 missing/undecryptable SMTP configuration. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{adminApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) { pm.test(\"JSON response\", () => { pm.expect(pm.response.json()).to.be.an(\"object\"); }); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Update tenant SMTP settings",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{baseUrl}}/api/admin/tenants/{{tenantId}}/smtp",
            "description": "## Overview\nSave SMTP destination, TLS mode, encrypted credentials and sender details. This operation sends no email.\n\n## Authentication\n**Admin key** in the `ApiKey` header. Tenant keys return 403.\n\n## Path and query parameters\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `tenantId` | Required path UUID | Target tenant created by the admin. |\n\n## Request fields\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `host` | Required | Public SMTP hostname/address, maximum 253 characters. |\n| `port` | Optional | 1–65535; defaults to 587. |\n| `security` | Optional | StartTls (default, usually port 587) or SslOnConnect (required for port 465). TLS is required in production. |\n| `username` | Optional | Maximum 1000 characters. Stored encrypted and never returned. |\n| `password` | Optional on edit | Maximum 1000 characters. Empty/omitted preserves saved password, including when username changes. |\n| `fromEmail` | Required | Provider-authorized bare sender address. |\n| `fromName` | Optional | Sender name, maximum 200 characters; defaults to ProspectMailer. |\n| `clearCredentials` | Optional | Default false. True with empty username/password clears both for an authorized relay. |\n\n### JSON example\n```json\n{\n  \"host\": \"smtp.your-provider.example\",\n  \"port\": 587,\n  \"security\": \"StartTls\",\n  \"username\": \"your-smtp-user\",\n  \"password\": \"your-smtp-password\",\n  \"fromEmail\": \"reports@example.com\",\n  \"fromName\": \"Automation Reports\",\n  \"clearCredentials\": false\n}\n```\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `host` | string | Configured destination. |\n| `port` | integer | SMTP port. |\n| `security` | string | TLS mode. |\n| `fromEmail` | string | Sender email. |\n| `fromName` | string | Sender display name. |\n| `hasCredentials` | boolean | Encrypted credentials exist; username/password/ciphertext are never returned. |\n\n```json\n{\n  \"host\": \"smtp.your-provider.example\",\n  \"port\": 587,\n  \"security\": \"StartTls\",\n  \"fromEmail\": \"reports@example.com\",\n  \"fromName\": \"Automation Reports\",\n  \"hasCredentials\": true\n}\n```\n\n## Important\nBlank/omitted password preserves the old password, including when username changes. To clear credentials, set clearCredentials=true with empty username/password. Production permits only TLS and public destination addresses.\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. 404 unknown tenant. 409 missing/undecryptable SMTP configuration. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{adminApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  // SMTP hostname; Before request uses collection variable smtpHost.\n  \"host\": \"smtp.your-provider.example\",\n  // 1–65535. Usually 465 (implicit TLS) or 587 (STARTTLS). Uses smtpPort.\n  \"port\": 587,\n  // Values: SslOnConnect (465), StartTls (usually 587), None (explicit local Development testing only). smtpSecurity overrides this; blank selects SslOnConnect on 465.\n  \"security\": \"StartTls\",\n  // Uses smtpUsername. Leave username and password empty for an authorized relay.\n  \"username\": \"your-smtp-user\",\n  // Uses smtpPassword. Empty preserves the saved password; use clearCredentials to remove it.\n  \"password\": \"your-smtp-password\",\n  // Provider-authorized sender address. Uses smtpFromEmail.\n  \"fromEmail\": \"reports@example.com\",\n  // Display name, up to 200 characters. Edit this value directly.\n  \"fromName\": \"Automation Reports\",\n  // true or false. true requires empty smtpUsername and smtpPassword and removes saved credentials.\n  \"clearCredentials\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Remove full-line explanation comments before sending strict JSON to the API.",
                  "const body = JSON.parse(pm.request.body.raw.split(\"\\n\").filter(line => !line.trimStart().startsWith(\"//\")).join(\"\\n\"));",
                  "body.host = pm.collectionVariables.get(\"smtpHost\") || \"\";",
                  "body.username = pm.collectionVariables.get(\"smtpUsername\") || \"\";",
                  "body.password = pm.collectionVariables.get(\"smtpPassword\") || \"\";",
                  "body.fromEmail = pm.collectionVariables.get(\"smtpFromEmail\") || \"\";",
                  "body.port = Number(pm.collectionVariables.get(\"smtpPort\"));",
                  "if (!Number.isInteger(body.port) || body.port < 1 || body.port > 65535) { throw new Error(\"Set smtpPort to an integer from 1 to 65535.\"); }",
                  "body.security = pm.collectionVariables.get(\"smtpSecurity\") || (body.port === 465 ? \"SslOnConnect\" : body.security);",
                  "pm.request.body.update(JSON.stringify(body));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) { pm.test(\"JSON response\", () => { pm.expect(pm.response.json()).to.be.an(\"object\"); }); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Permanently delete a tenant [PERMANENT]",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": "{{baseUrl}}/api/admin/tenants/{{tenantId}}?confirmTenantId={{tenantId}}",
            "description": "## Overview\nDelete the tenant and its SMTP/API-key records transactionally.\n\n## Authentication\n**Admin key** in the `ApiKey` header. Tenant keys return 403.\n\n## Path and query parameters\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `tenantId` | Required path UUID | Target tenant created by the admin. |\n| `confirmTenantId` | Required query UUID | Must equal tenantId; confirms permanent deletion. |\n\n## Response\n**HTTP 204** — No content.\n\n## Important\nDestructive. Existing tenant keys stop working. Accepted/delivered messages cannot be recalled, and backups follow operator retention. No request body.\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. 404 unknown tenant. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{adminApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 204\", function () { pm.response.to.have.status(204); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Tenant",
      "description": "Tenant key required. Configure your own SMTP, preview reports and explicitly send real emails.",
      "item": [
        {
          "name": "Read your SMTP settings",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/tenant/smtp",
            "description": "## Overview\nReturns destination, sender and credential-presence information without revealing saved secrets.\n\n## Authentication\n**Tenant key** in the `ApiKey` header. Admin keys return 403.\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `host` | string | Configured destination. |\n| `port` | integer | SMTP port. |\n| `security` | string | TLS mode. |\n| `fromEmail` | string | Sender email. |\n| `fromName` | string | Sender display name. |\n| `hasCredentials` | boolean | Encrypted credentials exist; username/password/ciphertext are never returned. |\n\n```json\n{\n  \"host\": \"smtp.your-provider.example\",\n  \"port\": 587,\n  \"security\": \"StartTls\",\n  \"fromEmail\": \"reports@example.com\",\n  \"fromName\": \"Automation Reports\",\n  \"hasCredentials\": true\n}\n```\n\n## Important\nNeither username, password nor encrypted credentials are returned. HTTP 409 means SMTP has not been configured.\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. 409 missing/undecryptable SMTP configuration. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{tenantApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) { pm.test(\"JSON response\", () => { pm.expect(pm.response.json()).to.be.an(\"object\"); }); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Update your SMTP settings",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{baseUrl}}/api/tenant/smtp",
            "description": "## Overview\nSave SMTP destination, TLS mode, encrypted credentials and sender details. This operation sends no email.\n\n## Authentication\n**Tenant key** in the `ApiKey` header. Admin keys return 403.\n\n## Request fields\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `host` | Required | Public SMTP hostname/address, maximum 253 characters. |\n| `port` | Optional | 1–65535; defaults to 587. |\n| `security` | Optional | StartTls (default, usually port 587) or SslOnConnect (required for port 465). TLS is required in production. |\n| `username` | Optional | Maximum 1000 characters. Stored encrypted and never returned. |\n| `password` | Optional on edit | Maximum 1000 characters. Empty/omitted preserves saved password, including when username changes. |\n| `fromEmail` | Required | Provider-authorized bare sender address. |\n| `fromName` | Optional | Sender name, maximum 200 characters; defaults to ProspectMailer. |\n| `clearCredentials` | Optional | Default false. True with empty username/password clears both for an authorized relay. |\n\n### JSON example\n```json\n{\n  \"host\": \"smtp.your-provider.example\",\n  \"port\": 587,\n  \"security\": \"StartTls\",\n  \"username\": \"your-smtp-user\",\n  \"password\": \"your-smtp-password\",\n  \"fromEmail\": \"reports@example.com\",\n  \"fromName\": \"Automation Reports\",\n  \"clearCredentials\": false\n}\n```\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `host` | string | Configured destination. |\n| `port` | integer | SMTP port. |\n| `security` | string | TLS mode. |\n| `fromEmail` | string | Sender email. |\n| `fromName` | string | Sender display name. |\n| `hasCredentials` | boolean | Encrypted credentials exist; username/password/ciphertext are never returned. |\n\n```json\n{\n  \"host\": \"smtp.your-provider.example\",\n  \"port\": 587,\n  \"security\": \"StartTls\",\n  \"fromEmail\": \"reports@example.com\",\n  \"fromName\": \"Automation Reports\",\n  \"hasCredentials\": true\n}\n```\n\n## Important\nBlank/omitted password preserves the old password, including when username changes. To clear credentials, set clearCredentials=true with empty username/password. Production permits only TLS and public destination addresses.\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. 409 missing/undecryptable SMTP configuration. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{tenantApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  // SMTP hostname; Before request uses collection variable smtpHost.\n  \"host\": \"smtp.your-provider.example\",\n  // 1–65535. Usually 465 (implicit TLS) or 587 (STARTTLS). Uses smtpPort.\n  \"port\": 587,\n  // Values: SslOnConnect (465), StartTls (usually 587), None (explicit local Development testing only). smtpSecurity overrides this; blank selects SslOnConnect on 465.\n  \"security\": \"StartTls\",\n  // Uses smtpUsername. Leave username and password empty for an authorized relay.\n  \"username\": \"your-smtp-user\",\n  // Uses smtpPassword. Empty preserves the saved password; use clearCredentials to remove it.\n  \"password\": \"your-smtp-password\",\n  // Provider-authorized sender address. Uses smtpFromEmail.\n  \"fromEmail\": \"reports@example.com\",\n  // Display name, up to 200 characters. Edit this value directly.\n  \"fromName\": \"Automation Reports\",\n  // true or false. true requires empty smtpUsername and smtpPassword and removes saved credentials.\n  \"clearCredentials\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Remove full-line explanation comments before sending strict JSON to the API.",
                  "const body = JSON.parse(pm.request.body.raw.split(\"\\n\").filter(line => !line.trimStart().startsWith(\"//\")).join(\"\\n\"));",
                  "body.host = pm.collectionVariables.get(\"smtpHost\") || \"\";",
                  "body.username = pm.collectionVariables.get(\"smtpUsername\") || \"\";",
                  "body.password = pm.collectionVariables.get(\"smtpPassword\") || \"\";",
                  "body.fromEmail = pm.collectionVariables.get(\"smtpFromEmail\") || \"\";",
                  "body.port = Number(pm.collectionVariables.get(\"smtpPort\"));",
                  "if (!Number.isInteger(body.port) || body.port < 1 || body.port > 65535) { throw new Error(\"Set smtpPort to an integer from 1 to 65535.\"); }",
                  "body.security = pm.collectionVariables.get(\"smtpSecurity\") || (body.port === 465 ? \"SslOnConnect\" : body.security);",
                  "pm.request.body.update(JSON.stringify(body));"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) { pm.test(\"JSON response\", () => { pm.expect(pm.response.json()).to.be.an(\"object\"); }); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Read your tenant",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/api/tenant",
            "description": "## Overview\nReturn metadata and SMTP setup state for the tenant identified by your key. No target ID is accepted.\n\n## Authentication\n**Tenant key** in the `ApiKey` header. Admin keys return 403.\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `id` | UUID | Tenant identifier. |\n| `name` | string | Tenant display name. |\n| `contactEmail` | string/null | Optional contact address. |\n| `isActive` | boolean | Whether the key is enabled. |\n| `createdAt` | timestamp | Creation time (UTC). |\n| `smtpConfigured` | boolean | SMTP settings have been saved; does not test delivery. |\n| `keyIdentifier` | string | Masked identifier, not a usable key. |\n| `keyCreatedAt` | timestamp | Current key creation/rotation time. |\n\n```json\n{\n  \"id\": \"11111111-1111-4111-8111-111111111111\",\n  \"name\": \"Example automation\",\n  \"contactEmail\": \"owner@example.com\",\n  \"isActive\": true,\n  \"createdAt\": \"2026-10-07T12:00:00Z\",\n  \"smtpConfigured\": false,\n  \"keyIdentifier\": \"pmt_12345678…\",\n  \"keyCreatedAt\": \"2026-10-07T12:00:00Z\"\n}\n```\n\n## Important\nRequests are limited to 120 per minute per tenant (admin requests have a separate quota).\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{tenantApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) { pm.test(\"JSON response\", () => { pm.expect(pm.response.json()).to.be.an(\"object\"); }); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send an HTML email [REAL EMAIL]",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{baseUrl}}/api/emails/send",
            "description": "## Overview\nWrap AI-supplied HTML in the navy-header email design and send through your tenant SMTP with a plain-text alternative.\n\n## Authentication\n**Tenant key** in the `ApiKey` header. Admin keys return 403.\n\n## Request fields\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `to` | Required | 1–50 bare recipient email addresses. |\n| `subject` | Required | 1–200 characters; no control characters. |\n| `htmlBody` | Required | 1–500000 characters of AI HTML content. Wrapped in the navy-header design with inline styling; subject supplies the header title. Full HTML documents use their body content. |\n| `textBody` | Optional | Maximum 500000 characters; absent/blank uses generic HTML-view instruction. |\n\n### JSON example\n```json\n{\n  \"to\": [\n    \"recipient@example.com\"\n  ],\n  \"subject\": \"Your automation report\",\n  \"htmlBody\": \"<h1>Automation report</h1><p>Your results are ready.</p>\",\n  \"textBody\": \"Your results are ready.\"\n}\n```\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `status` | string | accepted means SMTP accepted the DATA transaction. |\n| `messageId` | string | Identifier for provider-log correlation. |\n| `note` | string | Acceptance is not confirmed inbox delivery. |\n\n```json\n{\n  \"status\": \"accepted\",\n  \"messageId\": \"example-message-id@example.com\",\n  \"note\": \"SMTP accepted the message; inbox delivery is not confirmed.\"\n}\n```\n\n## Important\nSends real email. Recipients are concealed from one another. The subject and body come from your AI workflow; the service supplies layout and default styling, not predefined report content. HTML is trusted caller content. Final bodies are stored in EmailLogs. Inspect provider logs before retrying an uncertain failure. SMTP must be configured first.\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. 409 missing/undecryptable SMTP configuration. 502 SMTP failure; 504 timeout. Delivery may be uncertain; inspect provider logs before retrying.\n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{tenantApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"to\": [\n    \"{{recipientEmail}}\"\n  ],\n  \"subject\": \"Your automation report\",\n  \"htmlBody\": \"<h1>Automation report</h1><p>Your results are ready.</p>\",\n  \"textBody\": \"Your results are ready.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) { pm.test(\"JSON response\", () => { pm.expect(pm.response.json()).to.be.an(\"object\"); }); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Render and send a prospect report [REAL EMAIL]",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{baseUrl}}/api/prospects/send",
            "description": "## Overview\nRender a navy/teal HTML digest and complete plain-text alternative from structured prospects, then send through your tenant SMTP.\n\n## Authentication\n**Tenant key** in the `ApiKey` header. Admin keys return 403.\n\n## Request fields\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `to` | Required | 1–50 bare recipient email addresses. |\n| `subject` | Required | 1–200 characters; no control characters. |\n| `title` | Optional | Maximum 200 characters. |\n| `summary` | Optional | Maximum 5000 characters. |\n| `reportDate` | Optional | YYYY-MM-DD; defaults to current UTC date. |\n| `prospects` | Required | 1–100 objects; companyName required; every field maximum 5000 characters. |\n\n### JSON example\n```json\n{\n  \"to\": [\n    \"recipient@example.com\"\n  ],\n  \"subject\": \"Business software opportunities\",\n  \"title\": \"Your opportunity digest\",\n  \"summary\": \"Example data supplied by your automation.\",\n  \"reportDate\": \"2026-10-07\",\n  \"prospects\": [\n    {\n      \"companyName\": \"Example Company (fictional)\",\n      \"industry\": \"Wholesale\",\n      \"websiteUrl\": \"https://example.com\",\n      \"ceoName\": \"Example Owner\",\n      \"ceoTitle\": \"Owner\",\n      \"ceoInfo\": \"Example professional background.\",\n      \"ceoEmail\": \"owner@example.com\",\n      \"ceoSourceUrl\": \"https://example.com/team\",\n      \"generalEmail\": \"hello@example.com\",\n      \"phone\": \"+1 555 0100\",\n      \"contactUrl\": \"https://example.com/contact\",\n      \"needEvidence\": \"Example request for inventory integration.\",\n      \"evidenceType\": \"Inferred\",\n      \"evidenceUrl\": \"https://example.com/news\",\n      \"signalDate\": \"2026-10-07\",\n      \"opportunity\": \"Inventory portal\",\n      \"budget\": \"Not published\",\n      \"priority\": \"Medium\",\n      \"location\": \"Example location\"\n    }\n  ]\n}\n```\n\n### Prospect fields\nAll fields permit at most 5000 characters.\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `companyName` | Required | Company name. |\n| `industry` | Optional | Business industry. |\n| `location` | Optional | Business location. |\n| `websiteUrl` | Optional | HTTP/HTTPS company website. |\n| `ceoName` | Optional | CEO/owner name. |\n| `ceoTitle` | Optional | Actual leadership title. |\n| `ceoInfo` | Optional | Professional background. |\n| `ceoEmail` | Optional | Published executive business email; never guessed. |\n| `ceoSourceUrl` | Optional | HTTP/HTTPS leadership source. |\n| `generalEmail` | Optional | General business email. |\n| `phone` | Optional | Business phone. |\n| `contactUrl` | Optional | HTTP/HTTPS contact page. |\n| `needEvidence` | Optional | Evidence of software need. |\n| `evidenceType` | Optional | Caller-provided confirmed/inferred classification. |\n| `evidenceUrl` | Optional | HTTP/HTTPS evidence source. |\n| `signalDate` | Optional | Caller-provided signal date text. |\n| `opportunity` | Optional | Proposed solution. |\n| `budget` | Optional | Published budget or explicitly missing. |\n| `priority` | Optional | Caller-assigned priority. |\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `status` | string | accepted means SMTP accepted the DATA transaction. |\n| `messageId` | string | Identifier for provider-log correlation. |\n| `note` | string | Acceptance is not confirmed inbox delivery. |\n\n```json\n{\n  \"status\": \"accepted\",\n  \"messageId\": \"example-message-id@example.com\",\n  \"note\": \"SMTP accepted the message; inbox delivery is not confirmed.\"\n}\n```\n\n## Important\nSends real email. Text is HTML-encoded and only HTTP/HTTPS links are clickable. Missing information is labeled; executive emails are never guessed. The caller verifies supplied facts. SMTP must be configured first.\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. 409 missing/undecryptable SMTP configuration. 502 SMTP failure; 504 timeout. Delivery may be uncertain; inspect provider logs before retrying.\n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{tenantApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"to\": [\n    \"{{recipientEmail}}\"\n  ],\n  \"subject\": \"Business software opportunities\",\n  \"title\": \"Your opportunity digest\",\n  \"summary\": \"Example data supplied by your automation.\",\n  \"reportDate\": \"2026-10-07\",\n  \"prospects\": [\n    {\n      \"companyName\": \"Example Company (fictional)\",\n      \"industry\": \"Wholesale\",\n      \"websiteUrl\": \"https://example.com\",\n      \"ceoName\": \"Example Owner\",\n      \"ceoTitle\": \"Owner\",\n      \"ceoInfo\": \"Example professional background.\",\n      \"ceoEmail\": \"owner@example.com\",\n      \"ceoSourceUrl\": \"https://example.com/team\",\n      \"generalEmail\": \"hello@example.com\",\n      \"phone\": \"+1 555 0100\",\n      \"contactUrl\": \"https://example.com/contact\",\n      \"needEvidence\": \"Example request for inventory integration.\",\n      \"evidenceType\": \"Inferred\",\n      \"evidenceUrl\": \"https://example.com/news\",\n      \"signalDate\": \"2026-10-07\",\n      \"opportunity\": \"Inventory portal\",\n      \"budget\": \"Not published\",\n      \"priority\": \"Medium\",\n      \"location\": \"Example location\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) { pm.test(\"JSON response\", () => { pm.expect(pm.response.json()).to.be.an(\"object\"); }); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Preview a report without sending",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{baseUrl}}/api/prospects/preview",
            "description": "## Overview\nRender the same HTML digest returned by structured sending, without contacting SMTP.\n\n## Authentication\n**Tenant key** in the `ApiKey` header. Admin keys return 403.\n\n## Request fields\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `to` | Required | 1–50 bare recipient email addresses. |\n| `subject` | Required | 1–200 characters; no control characters. |\n| `title` | Optional | Maximum 200 characters. |\n| `summary` | Optional | Maximum 5000 characters. |\n| `reportDate` | Optional | YYYY-MM-DD; defaults to current UTC date. |\n| `prospects` | Required | 1–100 objects; companyName required; every field maximum 5000 characters. |\n\n### JSON example\n```json\n{\n  \"to\": [\n    \"recipient@example.com\"\n  ],\n  \"subject\": \"Business software opportunities\",\n  \"title\": \"Your opportunity digest\",\n  \"summary\": \"Example data supplied by your automation.\",\n  \"reportDate\": \"2026-10-07\",\n  \"prospects\": [\n    {\n      \"companyName\": \"Example Company (fictional)\",\n      \"industry\": \"Wholesale\",\n      \"websiteUrl\": \"https://example.com\",\n      \"ceoName\": \"Example Owner\",\n      \"ceoTitle\": \"Owner\",\n      \"ceoInfo\": \"Example professional background.\",\n      \"ceoEmail\": \"owner@example.com\",\n      \"ceoSourceUrl\": \"https://example.com/team\",\n      \"generalEmail\": \"hello@example.com\",\n      \"phone\": \"+1 555 0100\",\n      \"contactUrl\": \"https://example.com/contact\",\n      \"needEvidence\": \"Example request for inventory integration.\",\n      \"evidenceType\": \"Inferred\",\n      \"evidenceUrl\": \"https://example.com/news\",\n      \"signalDate\": \"2026-10-07\",\n      \"opportunity\": \"Inventory portal\",\n      \"budget\": \"Not published\",\n      \"priority\": \"Medium\",\n      \"location\": \"Example location\"\n    }\n  ]\n}\n```\n\n### Prospect fields\nAll fields permit at most 5000 characters.\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `companyName` | Required | Company name. |\n| `industry` | Optional | Business industry. |\n| `location` | Optional | Business location. |\n| `websiteUrl` | Optional | HTTP/HTTPS company website. |\n| `ceoName` | Optional | CEO/owner name. |\n| `ceoTitle` | Optional | Actual leadership title. |\n| `ceoInfo` | Optional | Professional background. |\n| `ceoEmail` | Optional | Published executive business email; never guessed. |\n| `ceoSourceUrl` | Optional | HTTP/HTTPS leadership source. |\n| `generalEmail` | Optional | General business email. |\n| `phone` | Optional | Business phone. |\n| `contactUrl` | Optional | HTTP/HTTPS contact page. |\n| `needEvidence` | Optional | Evidence of software need. |\n| `evidenceType` | Optional | Caller-provided confirmed/inferred classification. |\n| `evidenceUrl` | Optional | HTTP/HTTPS evidence source. |\n| `signalDate` | Optional | Caller-provided signal date text. |\n| `opportunity` | Optional | Proposed solution. |\n| `budget` | Optional | Published budget or explicitly missing. |\n| `priority` | Optional | Caller-assigned priority. |\n\n## Response\n**HTTP 200** — HTML report.\n\n```html\n<!doctype html><html><body>Formatted prospect report...</body></html>\n```\n\n## Important\nSafe to preview. No email is sent and no SMTP configuration is required. Recipients and subject must still satisfy the sending validation. Response content type is text/html.\n\n## Errors\n400 invalid input; 401 invalid/revoked key; 403 wrong credential privilege; 413 body over 1 MiB; 429 quota exceeded; 503 service/database unavailable. \n\n```json\n{\"error\":\"A valid ApiKey header is required.\"}\n```",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "ApiKey",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{tenantApiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"to\": [\n    \"{{recipientEmail}}\"\n  ],\n  \"subject\": \"Business software opportunities\",\n  \"title\": \"Your opportunity digest\",\n  \"summary\": \"Example data supplied by your automation.\",\n  \"reportDate\": \"2026-10-07\",\n  \"prospects\": [\n    {\n      \"companyName\": \"Example Company (fictional)\",\n      \"industry\": \"Wholesale\",\n      \"websiteUrl\": \"https://example.com\",\n      \"ceoName\": \"Example Owner\",\n      \"ceoTitle\": \"Owner\",\n      \"ceoInfo\": \"Example professional background.\",\n      \"ceoEmail\": \"owner@example.com\",\n      \"ceoSourceUrl\": \"https://example.com/team\",\n      \"generalEmail\": \"hello@example.com\",\n      \"phone\": \"+1 555 0100\",\n      \"contactUrl\": \"https://example.com/contact\",\n      \"needEvidence\": \"Example request for inventory integration.\",\n      \"evidenceType\": \"Inferred\",\n      \"evidenceUrl\": \"https://example.com/news\",\n      \"signalDate\": \"2026-10-07\",\n      \"opportunity\": \"Inventory portal\",\n      \"budget\": \"Not published\",\n      \"priority\": \"Medium\",\n      \"location\": \"Example location\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Public links",
      "description": "Public information pages, Swagger, OpenAPI JSON and liveness.",
      "item": [
        {
          "name": "Presentation page",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/",
            "description": "Public presentation page",
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });"
                ]
              }
            }
          ]
        },
        {
          "name": "HTML API Docs",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/docs",
            "description": "Readable method reference",
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });"
                ]
              }
            }
          ]
        },
        {
          "name": "About us",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/about",
            "description": "Company information and service notice",
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Swagger UI",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/swagger",
            "description": "Interactive API documentation",
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });"
                ]
              }
            }
          ]
        },
        {
          "name": "OpenAPI document",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/swagger/v1/swagger.json",
            "description": "Machine-readable contracts",
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Check process liveness",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/health",
            "description": "## Overview\nConfirm that the API process is responding.\n\n## Authentication\nNo key required.\n\n## Response\n**HTTP 200** — JSON response.\n\n| Field | Requirement / type | Meaning |\n|---|---|---|\n| `status` | string | ok |\n| `check` | string | liveness only |\n\n```json\n{\n  \"status\": \"ok\",\n  \"check\": \"liveness only\"\n}\n```\n\n## Important\nNo authentication. This does not test PostgreSQL, SMTP or inbox delivery.\n\n## Errors\nThis is liveness only; it does not verify PostgreSQL or SMTP.",
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"Expected status 200\", function () { pm.response.to.have.status(200); });",
                  "if (pm.response.code === 200) { pm.test(\"JSON response\", () => { pm.expect(pm.response.json()).to.be.an(\"object\"); }); }"
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://localhost:24603",
      "type": "string"
    },
    {
      "key": "adminApiKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "tenantId",
      "value": "",
      "type": "string"
    },
    {
      "key": "tenantApiKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "smtpHost",
      "value": "smtp.your-provider.example",
      "type": "string"
    },
    {
      "key": "smtpPort",
      "value": "587",
      "type": "string",
      "description": "1–65535; normally 465 with SslOnConnect or 587 with StartTls."
    },
    {
      "key": "smtpSecurity",
      "value": "",
      "type": "string",
      "description": "SslOnConnect or StartTls. Blank automatically selects SslOnConnect for port 465; otherwise uses request security. None is only allowed for explicitly enabled local Development SMTP."
    },
    {
      "key": "smtpUsername",
      "value": "",
      "type": "string"
    },
    {
      "key": "smtpPassword",
      "value": "",
      "type": "string"
    },
    {
      "key": "smtpFromEmail",
      "value": "reports@example.com",
      "type": "string"
    },
    {
      "key": "recipientEmail",
      "value": "recipient@example.com",
      "type": "string"
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Use collection values consistently, even if an old environment is selected.",
          "pm.collectionVariables.toObject && Object.entries(pm.collectionVariables.toObject()).forEach(([key, value]) => pm.variables.set(key, value));"
        ]
      }
    }
  ]
}