# Send a newsletter

> Prepare a newsletter, get its template approved, look at the preview, send a test and send it in one step with an Idempotency-Key.

Last updated: 2026-10-30. Source: https://docs.smartchat.marketing/en/guides/send-a-newsletter/

You need a key with these permissions: `newsletters:write` to prepare, `templates:submit` to hand the template to WhatsApp, `newsletters:test` to test and `newsletters:send` to send. Read [Sending safely](https://docs.smartchat.marketing/en/security/) first.

A newsletter is sent as a WhatsApp **template**, and WhatsApp must approve the template before anything is sent. So the order is: draft, approval, preview, test, send. All answers on this page belong to one story: the newsletter "Autumn menu".

## 1. Create the draft

The text may contain the placeholder `{{first_name}}`. It is replaced by the first name of each contact. It is the only placeholder a newsletter allows. The text must not start or end with a placeholder.

**curl**

```bash
curl -X POST "https://api.smartchat.marketing/v1/newsletters" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Autumn menu",
  "text": "Hi {{first_name}}, our autumn menu is here. Book a table for this week.",
  "audience": {
    "tags": [
      "vip"
    ]
  }
}'
```

**Python**

```python
import os
import uuid
import requests

response = requests.post(
    "https://api.smartchat.marketing/v1/newsletters",
    headers={
        "Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"],
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "title": "Autumn menu",
        "text": "Hi {{first_name}}, our autumn menu is here. Book a table for this week.",
        "audience": {
            "tags": [
                "vip",
            ],
        },
    },
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/newsletters", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "title": "Autumn menu",
    "text": "Hi {{first_name}}, our autumn menu is here. Book a table for this week.",
    "audience": {
      "tags": [
        "vip"
      ]
    }
  }),
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (201):


```json
{
  "object": "newsletter",
  "id": "nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b",
  "title": "Autumn menu",
  "text": "Hi {{first_name}}, our autumn menu is here. Book a table for this week.",
  "language": "en",
  "button": {
    "type": "url",
    "text": "See the menu",
    "url": "https://example.com/menu"
  },
  "audience": {
    "tags": [
      "vip"
    ],
    "mode": "any",
    "sources": []
  },
  "status": "draft",
  "template_id": "tpl_0f8c1a2e4b7d4c3e9a5b6d7e8f901234",
  "template_status": "draft",
  "ready_to_send": false,
  "not_ready_reason": "template_not_approved",
  "recipients": 412,
  "scheduled_at": null,
  "sent_at": null,
  "created_at": "2026-10-25T09:41:07Z",
  "updated_at": "2026-10-25T09:41:07Z"
}
```

The newsletter is a draft. It has its own template (`template_id`) and the template is still a `draft` too, so `ready_to_send` is `false` and `not_ready_reason` is `template_not_approved`. The newsletter can be changed ([`update_newsletter`](https://docs.smartchat.marketing/en/api/reference/newsletters/update_newsletter/)) until it is scheduled.

## 2. Get the template approved

WhatsApp decides if a template may be used. You hand it over with [`create_template_submissions`](https://docs.smartchat.marketing/en/api/reference/templates/create_template_submissions/). This needs the permission `templates:submit`, and it takes two calls with **different** `Idempotency-Key` values.

First call: a preview. Nothing is submitted. The answer shows what would happen and gives a `plan_id` that is valid for 10 minutes.

**curl**

```bash
curl -X POST "https://api.smartchat.marketing/v1/template-submissions" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "template_ids": [
    "tpl_0f8c1a2e4b7d4c3e9a5b6d7e8f901234"
  ]
}'
```

**Python**

```python
import os
import uuid
import requests

response = requests.post(
    "https://api.smartchat.marketing/v1/template-submissions",
    headers={
        "Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"],
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "template_ids": [
            "tpl_0f8c1a2e4b7d4c3e9a5b6d7e8f901234",
        ],
    },
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/template-submissions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "template_ids": [
      "tpl_0f8c1a2e4b7d4c3e9a5b6d7e8f901234"
    ]
  }),
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (200):


```json
{
  "object": "template_submission_plan",
  "plan_id": "pln_9a8b7c6d5e4f43a2b1c0d9e8f7a6b5c4",
  "expires_at": "2026-10-25T09:52:00Z",
  "templates": [
    {
      "template_id": "tpl_0f8c1a2e4b7d4c3e9a5b6d7e8f901234",
      "name": "Autumn menu",
      "status": "draft",
      "outcome": "would_submit",
      "issues": [],
      "error_code": null
    }
  ],
  "submitted_at": null
}
```

Second call: send the same `template_ids` with the `plan_id`. Now the template goes to WhatsApp.

**curl**

```bash
curl -X POST "https://api.smartchat.marketing/v1/template-submissions" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "template_ids": [
    "tpl_0f8c1a2e4b7d4c3e9a5b6d7e8f901234"
  ],
  "plan_id": "pln_9a8b7c6d5e4f43a2b1c0d9e8f7a6b5c4"
}'
```

**Python**

```python
import os
import uuid
import requests

response = requests.post(
    "https://api.smartchat.marketing/v1/template-submissions",
    headers={
        "Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"],
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "template_ids": [
            "tpl_0f8c1a2e4b7d4c3e9a5b6d7e8f901234",
        ],
        "plan_id": "pln_9a8b7c6d5e4f43a2b1c0d9e8f7a6b5c4",
    },
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/template-submissions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "template_ids": [
      "tpl_0f8c1a2e4b7d4c3e9a5b6d7e8f901234"
    ],
    "plan_id": "pln_9a8b7c6d5e4f43a2b1c0d9e8f7a6b5c4"
  }),
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (200):


```json
{
  "object": "template_submission",
  "plan_id": null,
  "expires_at": null,
  "templates": [
    {
      "template_id": "tpl_0f8c1a2e4b7d4c3e9a5b6d7e8f901234",
      "name": "Autumn menu",
      "status": "pending",
      "outcome": "submitted",
      "issues": [],
      "error_code": null
    }
  ],
  "submitted_at": "2026-10-25T09:42:10Z"
}
```

The template is `pending`. WhatsApp decides within a few minutes to 24 hours. Read the newsletter from time to time ([`get_newsletter`](https://docs.smartchat.marketing/en/api/reference/newsletters/get_newsletter/)) until `template_status` is `approved` and `ready_to_send` is `true`. If WhatsApp rejects the template, `template_status` is `rejected`; change the text and submit again.

**curl**

```bash
curl "https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY"
```

**Python**

```python
import os
import requests

response = requests.get(
    "https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b",
    headers={"Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"]},
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
  },
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (200):


```json
{
  "object": "newsletter",
  "id": "nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b",
  "title": "Autumn menu",
  "text": "Hi {{first_name}}, our autumn menu is here. Book a table for this week.",
  "language": "en",
  "button": {
    "type": "url",
    "text": "See the menu",
    "url": "https://example.com/menu"
  },
  "audience": {
    "tags": [
      "vip"
    ],
    "mode": "any",
    "sources": []
  },
  "status": "draft",
  "template_id": "tpl_0f8c1a2e4b7d4c3e9a5b6d7e8f901234",
  "template_status": "approved",
  "ready_to_send": true,
  "not_ready_reason": null,
  "recipients": 412,
  "scheduled_at": null,
  "sent_at": null,
  "created_at": "2026-10-25T09:41:07Z",
  "updated_at": "2026-10-25T10:05:00Z"
}
```

## 3. Look at the preview

The preview reads and calculates. It sends nothing.

**curl**

```bash
curl "https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b/send-preview?when=now" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY"
```

**Python**

```python
import os
import requests

response = requests.get(
    "https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b/send-preview",
    headers={"Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"]},
    params={
        "when": "now",
    },
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b/send-preview?when=now", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
  },
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (200):


```json
{
  "object": "newsletter_send_preview",
  "newsletter_id": "nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b",
  "when": "now",
  "can_send": true,
  "blocked_by": [],
  "recipients": 412,
  "audience_total": 938,
  "excluded": {
    "opted_out": 12,
    "not_confirmed": 30,
    "suppressed": 2,
    "no_phone": 3,
    "outside_audience": 524
  },
  "estimated_days": 1,
  "daily_limit": 1000,
  "text": "Hi {{first_name}}, our autumn menu is here. Book a table for this week."
}
```

Check the number of recipients, the excluded contacts and the sample text. Only contacts with a confirmed opt-in are included. `daily_limit` is the daily limit of your WhatsApp number (how many new people WhatsApp lets it write to per day, or `null` if there is none), and `estimated_days` is how many days the send takes because of it. Here the 412 recipients fit into one day.

## 4. Send a test

A test goes to the phone number you name. It uses approved templates only and never goes to a contact who unsubscribed. It needs an `Idempotency-Key`.

**curl**

```bash
curl -X POST "https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b/test-send" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "+4915112345678"
}'
```

**Python**

```python
import os
import uuid
import requests

response = requests.post(
    "https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b/test-send",
    headers={
        "Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"],
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "to": "+4915112345678",
    },
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b/test-send", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "to": "+4915112345678"
  }),
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (202):


```json
{
  "object": "newsletter_test_send",
  "newsletter_id": "nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b",
  "to": "+4915112345678",
  "status": "handed_to_whatsapp"
}
```

## 5. Send

Sending is **one step**. It needs the permission `newsletters:send` and an `Idempotency-Key`. If the network fails, repeat the call with the **same** key: the newsletter is not sent twice.

**curl**

```bash
curl -X POST "https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b/send" \
  -H "Authorization: Bearer $SMARTCHAT_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "when": "now"
}'
```

**Python**

```python
import os
import uuid
import requests

response = requests.post(
    "https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b/send",
    headers={
        "Authorization": "Bearer " + os.environ["SMARTCHAT_API_KEY"],
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "when": "now",
    },
)
response.raise_for_status()
print(response.json())
```

**TypeScript**

```typescript
const response = await fetch("https://api.smartchat.marketing/v1/newsletters/nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b/send", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SMARTCHAT_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "when": "now"
  }),
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());
```

Response (202):


```json
{
  "object": "newsletter_send",
  "id": "nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b",
  "newsletter_id": "nwl_3c9e1f7a5b2d4e8f9a0b1c2d3e4f5a6b",
  "status": "sending",
  "recipients": 412,
  "scheduled_at": null
}
```

The answer is 202: the newsletter is queued. Check the progress with [`get_newsletter`](https://docs.smartchat.marketing/en/api/reference/newsletters/get_newsletter/). The [events page](https://docs.smartchat.marketing/en/webhooks/events/) shows which events exist for newsletters and whether they are available yet.

## If something goes wrong

| Code | Meaning |
|---|---|
| [`template_not_approved`](https://docs.smartchat.marketing/en/errors/#template_not_approved) | The template is not approved yet. Go back to step 2 and wait for the approval. |
| [`audience_empty`](https://docs.smartchat.marketing/en/errors/#audience_empty) | The audience has no reachable contacts. |
| [`newsletter_locked`](https://docs.smartchat.marketing/en/errors/#newsletter_locked) | The newsletter is already scheduled or sent. |
| [`idempotency_key_required`](https://docs.smartchat.marketing/en/errors/#idempotency_key_required) | You did not send the `Idempotency-Key` header. |
| [`plan_expired`](https://docs.smartchat.marketing/en/errors/#plan_expired) | The `plan_id` of step 2 is older than 10 minutes. Make the preview call again. |

To pause or cancel a running newsletter use [`control_newsletter`](https://docs.smartchat.marketing/en/api/reference/newsletters/control_newsletter/).

