---
source_url: https://www.pubnub.com/docs/design-patterns/send-messages-effectively
title: Send messages effectively
updated_at: 2026-09-30T07:20:08.000Z
---

# Send messages effectively

## Documentation index

To discover more PubNub resources:

1. Fetch [PubNub's llms.txt](https://www.pubnub.com/llms-full.txt) for a list of available pages in Markdown format.
2. Identify relevant URLs from that index.
3. Fetch the target pages.

Do not assume a path exists, always check the index first.

This guide shows you how to structure the publish side of a PubNub application. You'll learn how to:

* Publish from a direct client connection instead of routing through your own server.
* Decide how your application responds to a failed publish. Your application owns retrying a failed publish, because PubNub SDKs don't retry one for you.
* Copy published data into your own systems without publishing it twice.
* Keep a single channel usable when its audience grows very large.

Every call on this page needs an SDK instance initialized with your publish key. If you don't have a keyset yet, start with [Set up your account](https://www.pubnub.com/docs/architecture/authentication/set-up-your-account.md). This guide assumes you already know how to call `publish()` itself. If you don't, start with [Send different message types](https://www.pubnub.com/docs/pub-sub/publish/send-different-message-types.md).

Examples use the JavaScript, Swift, Java, Kotlin, Python, and PHP SDKs, which document every parameter this guide uses. For any other language, refer to [Available SDKs](https://www.pubnub.com/docs/getting-started/available-sdks.md).

## Publish from the client, not through your server

Call `publish()` directly from the client that generates the message. Routing a publish through your own server first adds that server's own processing time on top of PubNub's delivery. It also turns your server into a dependency the message can't get past. If your server is slow, overloaded, or down, the publish doesn't happen either.

Initialize the client with your keyset's publish and subscribe keys as always. The keys identify the keyset, they don't limit what a client can do. To scope and revoke a client's permissions, enable [Access Manager](https://www.pubnub.com/docs/security/access-control/overview.md) and have your server grant the client a time-limited token before it publishes. With Access Manager enabled, a request without a valid token is denied:

```javascript
import PubNub from 'pubnub';

// The publish and subscribe keys identify your keyset. They stay in the client configuration.
const pubnub = new PubNub({
  publishKey: 'YOUR_PUBLISH_KEY',
  subscribeKey: 'YOUR_SUBSCRIBE_KEY',
  userId: 'YOUR_USER_ID',
});

// Your server holds the secret key, authenticates the user, and returns a token
// granted for this User ID. Replace the URL with your own token endpoint.
const response = await fetch('https://your-server.example.com/pubnub-token', {
  credentials: 'include',
});
if (!response.ok) {
  throw new Error(`Token request failed with HTTP ${response.status}`);
}
const { token } = await response.json();

// The token adds scoped authorization to the keyset access the keys provide.
pubnub.setToken(token);
```

For the reasoning behind a direct connection and the token-issuing flow it depends on, refer to [Architectural choices](https://www.pubnub.com/docs/design-patterns/architectural-choices.md#connect-clients-directly-keep-your-server-optional).

## Decide how to handle a failed publish

By default, PubNub SDKs retry only subscribe operations, using exponential backoff for up to 6 attempts with delays growing from 2 to 150 seconds. Publish isn't included, so a failed publish stays failed unless your own code retries it. Check the outcome of every `publish()` call. Choose a response per message type: retry immediately, retry with backoff, surface the failure to a user, or drop it.

### JavaScript

```javascript
async function publishWithRetry(channel, message, attempt = 1) {
  try {
    await pubnub.publish({ channel, message });
  } catch (error) {
    if (attempt < 3) {
      await new Promise((resolve) => setTimeout(resolve, attempt * 500));
      return publishWithRetry(channel, message, attempt + 1);
    }
    console.error('Publish failed after retries:', error);
  }
}
```

### Swift

```swift
func publishWithRetry(channel: String, message: [String: Any], attempt: Int = 1) {
  pubnub.publish(channel: channel, message: message) { result in
    if case let .failure(error) = result {
      if attempt < 3 {
        DispatchQueue.main.asyncAfter(deadline: .now() + Double(attempt) * 0.5) {
          publishWithRetry(channel: channel, message: message, attempt: attempt + 1)
        }
      } else {
        print("Publish failed after retries: \(error.localizedDescription)")
      }
    }
  }
}
```

### Java

```java
void publishWithRetry(Channel channel, JsonObject message, int attempt) {
    channel.publish(message).async(result ->
        result.onFailure(exception -> {
            if (attempt < 3) {
                publishWithRetry(channel, message, attempt + 1);
            } else {
                System.out.println("Publish failed after retries: " + exception.getMessage());
            }
        }));
}
```

### Kotlin

```kotlin
fun publishWithRetry(channel: Channel, message: Map<String, Any>, attempt: Int = 1) {
    channel.publish(message = message).async { result ->
        result.onFailure { exception ->
            if (attempt < 3) {
                publishWithRetry(channel, message, attempt + 1)
            } else {
                println("Publish failed after retries: ${exception.message}")
            }
        }
    }
}
```

### Python

```python
def publish_with_retry(channel, message, attempt=1):
    try:
        pubnub.publish().channel(channel).message(message).sync()
    except PubNubException as error:
        if attempt < 3:
            time.sleep(attempt * 0.5)
            publish_with_retry(channel, message, attempt + 1)
        else:
            print('Publish failed after retries:', error)
```

### PHP

```php
function publishWithRetry($pubnub, $channel, $message, $attempt = 1) {
    try {
        $pubnub->publish()->channel($channel)->message($message)->sync();
    } catch (PubNubException $error) {
        if ($attempt < 3) {
            usleep($attempt * 500000);
            publishWithRetry($pubnub, $channel, $message, $attempt + 1);
        } else {
            echo 'Publish failed after retries: ' . $error->getMessage() . PHP_EOL;
        }
    }
}
```

PubNub does not deduplicate publishes on the server. Publishing the same payload twice always creates two distinct messages with two different timetokens. A retry after an ambiguous failure, a timeout where you can't tell whether PubNub received the message, can therefore produce a duplicate. Design consumers to tolerate one, or attach your own idempotency key and deduplicate on the receiving side. For the full set of delivery guarantees this behavior follows from, refer to [Publish retry](https://www.pubnub.com/docs/pub-sub/publish/overview.md#publish-retry).

## Copy published data to your own systems without publishing twice

Don't publish the same payload once to PubNub and a second time straight to your own server. That doubles the mobile data and battery cost of every message, and it gives you two delivery paths that can disagree with each other.

Choose one of these instead:

* **Forward a copy asynchronously.** Attach an After Publish Function to the channel. It runs on a copy of the message after PubNub delivers the original. That lets it call your server's API or write to your database without adding latency for anyone waiting on the message. Refer to [Create a Function](https://www.pubnub.com/docs/message-processing/serverless/create-function.md) to build one, and to [PubNub Functions](https://www.pubnub.com/docs/message-processing/serverless/overview.md) for what a Function can do with the message.
* **Read it back later.** Enable [Message Persistence](https://www.pubnub.com/docs/data-storage/message-history/overview.md) on your keyset and fetch the messages your server needs on its own schedule, instead of receiving a copy of each one as it's published. Refer to [Retrieve message history](https://www.pubnub.com/docs/data-storage/message-history/retrieve-message-history.md) for the fetch call.

## Keep a very busy channel usable

A channel with a very large audience needs a different publish pattern than one with a handful of subscribers. Every message on it gets multiplied by the number of subscribers who receive it. Two independent options apply, and you can use either or both:

* **Shard publishes across a fixed set of channels.** Don't have every client publish to one channel that a single server subscriber has to keep up with. Publish into a small, fixed set of ingress channels chosen by a consistent hash instead, so the subscriber side stays fixed no matter how many clients you add. Refer to [Message aggregation](https://www.pubnub.com/docs/design-patterns/message-aggregation.md) for that pattern in full.
* **Throttle the rate on the channel itself.** If you want every subscriber to stay in one shared channel rather than being split into rooms, apply a Before Publish Function that rejects or samples messages once the channel exceeds a rate you define, instead of sharding. Refer to [Rate limiting](https://www.pubnub.com/docs/design-patterns/rate-limiting.md) for that pattern in full.

## Related tasks

* [Send different message types](https://www.pubnub.com/docs/pub-sub/publish/send-different-message-types.md). The publish and signal calls, custom message types, and metadata in code.
* [Publishing messages with PubNub](https://www.pubnub.com/docs/pub-sub/publish/overview.md). Delivery guarantees, message ordering, and what a publish response promises.
* [Create a Function](https://www.pubnub.com/docs/message-processing/serverless/create-function.md). Build the Before or After Publish Function that runs on your published messages.
* [Retrieve message history](https://www.pubnub.com/docs/data-storage/message-history/retrieve-message-history.md). Read back messages your server didn't capture live.
* [Message aggregation](https://www.pubnub.com/docs/design-patterns/message-aggregation.md). Shard publishes into a fixed ingress set for server-side aggregation.
* [Rate limiting](https://www.pubnub.com/docs/design-patterns/rate-limiting.md). Throttle or shard a channel whose audience has grown very large.
* [Architectural choices](https://www.pubnub.com/docs/design-patterns/architectural-choices.md). The reasoning behind a direct client connection and when your server belongs in the message path.

Last updated at: 2026-09-30T07:20:08.000Z
