Add partner SMS gateway via Gama SendQuick and document it.

Expose POST /public/sms/send with API key + domain allowlist for external backends like Balout, wire Meshkee OTP/message sends to Gama, and publish Partner SMS docs on /docs/website.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-04 11:09:21 +03:30
co-authored by Cursor
parent 267a218c26
commit f4295e780c
22 changed files with 803 additions and 16 deletions
+6 -2
View File
@@ -1,7 +1,7 @@
# Meshkee CMS API — Project Context
> Living reference for developers and AI assistants working on this codebase.
> Last updated: August 1, 2026
> Last updated: August 4, 2026
## What This Project Is
@@ -79,6 +79,7 @@ src/
├── storage/ # S3 driver abstraction
├── website-docs/ # Public website API docs pack
├── invoices/ # Platform invoices + item templates (super-admin; business-ready schema)
├── public-sms/ # Partner SMS gateway (API key + domain allowlist → Gama)
├── prisma/ # PrismaModule + PrismaService
├── redis/ # Redis client + OTP helpers
└── common/ # Shared interceptors (BigInt serialization)
@@ -361,6 +362,8 @@ Each resource typically has: `read`, `create`, `update`, `delete` (+ `publish` f
- Registration resolves tenant by `domain` → creates/links user → assigns `customer` role
- OTP stored in Redis (`otp:{cellNumber}`), 5-min TTL; disabled when `SMS_ENABLED=false`
- JWT payload: `sub`, `cellNumber`, `roles`, `dashboard`, `type`
- SMS provider: Gama (`sms.igama.ir`) SendQuick via service shortcode (`SMS_GAMA_*`)
- Partner gateway (external sites like Balout): `POST /api/v1/public/sms/send` with `X-Api-Key` + body `{ domain, to, message }`; partners configured in `SMS_PARTNERS` (`domain:apiKey` pairs). Rate limits: 30/partner/min and 5/destination/min. Not part of storefront website-api docs.
---
@@ -537,7 +540,7 @@ See `.env.example` for the full list. Key groups:
| Redis | `REDIS_URL`, `REDIS_HOST`, `REDIS_PORT` |
| API | `PORT` |
| JWT | `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET`, `JWT_*_EXPIRES_IN` |
| SMS | `SMS_ENABLED` |
| SMS | `SMS_ENABLED`, `SMS_GAMA_BASE_URL`, `SMS_GAMA_USERNAME`, `SMS_GAMA_PASSWORD`, `SMS_GAMA_SOURCE_SERVICE`, `SMS_PARTNERS` |
| S3 | `S3_ENDPOINT`, `S3_BUCKET`, `S3_PUBLIC_URL`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` |
| Legacy MySQL (WillaEngine migrate) | `OLD_MYSQL_HOST`, `OLD_MYSQL_PORT`, `OLD_MYSQL_USER`, `OLD_MYSQL_PASSWORD`, `OLD_MYSQL_DATABASE` |
| Legacy S3 source (media copy) | `OLD_S3_ENDPOINT`, `OLD_S3_BUCKET`, `OLD_S3_PUBLIC_URL`, `OLD_S3_ACCESS_KEY_ID`, `OLD_S3_SECRET_ACCESS_KEY` |
@@ -563,6 +566,7 @@ See `.env.example` for the full list. Key groups:
- Category variations & technical forms
- Tenant resolution by domain
- RBAC with granular permissions
- Partner SMS gateway (`POST /public/sms/send`) + Gama SendQuick integration
### Planned / partial
+1
View File
@@ -26,6 +26,7 @@ You are building a **Meshkee business website (storefront)**. You must use the M
4. Customer register body must include `"domain": "<WEBSITE_DOMAIN>"`.
5. Cell numbers are E.164 (`+98912...`).
6. Do not call dashboard/CMS routes (`/businesses/.../products` write APIs, media upload, domain-admin, etc.).
7. **Partner SMS** (`POST /public/sms/send`) is for external partner backends with an issued `X-Api-Key` only — not for normal storefront UI. See https://api.meshkee.com/docs/website/SMS.md
### Typical bootstrap sequence
1. `GET /tenants/{domain}` → branding + `businessId`
@@ -100,6 +100,10 @@
{
"key": "brandId",
"value": ""
},
{
"key": "smsApiKey",
"value": ""
}
],
"item": [
@@ -1669,6 +1673,34 @@
}
}
]
},
{
"name": "Partner SMS",
"description": "Server-to-server SMS for allowlisted partner domains. Set collection variable `smsApiKey`. Docs: https://api.meshkee.com/docs/website/SMS.md",
"item": [
{
"name": "Send SMS",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "X-Api-Key",
"value": "{{smsApiKey}}"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"domain\": \"baloutpastry.com\",\n \"to\": \"09127004945\",\n \"message\": \"سفارش شما به شماره ی ۱۲۱۱۳۲۲ اماده می باشد.\"\n}"
},
"url": "{{baseUrl}}/public/sms/send",
"description": "Requires allowlisted domain + matching API key. Not for browser JS."
}
}
]
}
]
}
+85
View File
@@ -0,0 +1,85 @@
# Partner SMS gateway
Server-to-server SMS via Meshkee → Gama (گاما). Use this when a **non-Meshkee** (or partner) backend needs to send SMS through the shared Meshkee account.
**Not for browser/storefront JavaScript.** Never put the API key in frontend code.
Hub: https://api.meshkee.com/docs/website
Endpoint docs also in OpenAPI / Postman under **Partner SMS**.
---
## Endpoint
```http
POST https://api.meshkee.com/api/v1/public/sms/send
Content-Type: application/json
X-Api-Key: <partner-secret>
```
### Body
| Field | Required | Description |
|-------|----------|-------------|
| `domain` | yes | Allowlisted partner apex, e.g. `baloutpastry.com` (`www.` is stripped) |
| `to` | yes | Mobile: `09…`, `9…`, `+989…`, or `989…` |
| `message` | yes | Free text, max 700 characters |
### Example (Balout)
```bash
curl -sS -X POST 'https://api.meshkee.com/api/v1/public/sms/send' \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: <YOUR_PARTNER_KEY>' \
-d '{
"domain": "baloutpastry.com",
"to": "09127004945",
"message": "سفارش شما به شماره ی ۱۲۱۱۳۲۲ اماده می باشد."
}'
```
### Success (200)
```json
{
"success": true,
"serverId": "1136923081051406337"
}
```
`serverId` is the Gama message id (delivery tracking).
### Errors
| HTTP | Meaning |
|------|---------|
| 400 | Invalid phone or empty/too-long message |
| 401 | Missing/invalid `X-Api-Key` or domain not allowlisted |
| 429 | Rate limit: **30**/partner/minute or **5**/destination/minute |
| 503 | SMS disabled, missing provider config, or Gama unreachable |
---
## Auth model
1. Meshkee configures `SMS_PARTNERS=domain:apiKey,...` on the API server.
2. Partner backend sends `X-Api-Key` + matching `domain` in the JSON body.
3. Key must match that domain (timing-safe compare). `www.baloutpastry.com` normalizes to `baloutpastry.com`.
First allowlisted partner: **baloutpastry.com**.
---
## Sender line (v1)
Uses the **service** shortcode only (`SendQuick`). Advertising / bulk / OTP pattern APIs are not exposed yet.
---
## Rules for partner backends
1. Call from your **server** only (Balout API → Meshkee API).
2. Keep the API key in server env / secrets — never in the website frontend.
3. Prefer short transactional messages (order ready, OTP-style text, etc.).
4. Respect rate limits; backoff on `429`.
5. Meshkee websites that already use customer auth OTP go through Meshkees own auth/SMS path — they do **not** need this partner endpoint.
+12
View File
@@ -77,6 +77,7 @@
<a class="btn" href="/docs/website/openapi.json">OpenAPI JSON</a>
<a class="btn secondary" href="/docs/website/Meshkee-Website-API.postman_collection.json">Download Postman</a>
<a class="btn secondary" href="/docs/website/AI_PROMPT.md">AI prompt</a>
<a class="btn secondary" href="/docs/website/SMS.md">Partner SMS</a>
</div>
<h2>Base URL</h2>
@@ -104,6 +105,17 @@
Postman → Import → Link → paste<br />
<code>https://api.meshkee.com/docs/website/Meshkee-Website-API.postman_collection.json</code>
</p>
<h2>Partner SMS gateway</h2>
<p>
External backends (e.g. Balout) can send transactional SMS through Meshkee → Gama.
Server-to-server only — API key per allowlisted domain. See
<a href="/docs/website/SMS.md">SMS.md</a>.
</p>
<div class="panel">
<p style="margin:0"><code>POST /api/v1/public/sms/send</code></p>
<p style="margin:0.5rem 0 0">Header <code>X-Api-Key</code> + body <code>{ domain, to, message }</code></p>
</div>
</main>
</body>
</html>
+64 -1
View File
@@ -36,7 +36,8 @@
{ "name": "Cities" },
{ "name": "Cart" },
{ "name": "Orders" },
{ "name": "Favorites" }
{ "name": "Favorites" },
{ "name": "Partner SMS" }
],
"components": {
"securitySchemes": {
@@ -44,6 +45,12 @@
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
},
"apiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "X-Api-Key",
"description": "Partner SMS API key (server-to-server only). Issued per allowlisted domain."
}
},
"parameters": {
@@ -897,6 +904,62 @@
],
"responses": { "200": { "description": "{ message }" } }
}
},
"/public/sms/send": {
"post": {
"tags": ["Partner SMS"],
"summary": "Send SMS via Meshkee (partner gateway)",
"description": "Server-to-server only. For external/partner backends (e.g. Balout) that need to send SMS through Meshkee → Gama. Not for browser/storefront JS. Requires an allowlisted `domain` + matching `X-Api-Key`. See /docs/website/SMS.md.",
"security": [{ "apiKeyAuth": [] }],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["domain", "to", "message"],
"properties": {
"domain": {
"type": "string",
"example": "baloutpastry.com",
"description": "Allowlisted partner apex (www. is stripped)"
},
"to": {
"type": "string",
"example": "09127004945",
"description": "Iranian mobile: 09…, 9…, +989…, or 989…"
},
"message": {
"type": "string",
"maxLength": 700,
"example": "سفارش شما به شماره ی ۱۲۱۱۳۲۲ اماده می باشد."
}
}
}
}
}
},
"responses": {
"200": {
"description": "Accepted by Gama",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": { "type": "boolean", "example": true },
"serverId": { "type": "string", "example": "1136923081051406337" }
}
}
}
}
},
"400": { "description": "Invalid phone or message" },
"401": { "description": "Missing/invalid X-Api-Key or domain" },
"429": { "description": "Rate limited (30/partner/min or 5/destination/min)" },
"503": { "description": "SMS disabled or provider unreachable" }
}
}
}
}
}