// Python Dev

RabbitMQ as a bridge between the external and internal networks

Published on 2026-04-03

Queue brokers are usually seen as a tool inside a single system — decouple microservices, smooth out load spikes, organize background jobs. But there’s a much less obvious use: use the broker as a data access point for data that physically resides inside the organization and does not leave it.

In practice any attempt to provide external access to internal data quickly turns into an infrastructure project: approvals, security, network restrictions. Even a simple task like “get a client’s balance” can hit weeks of work and involve multiple teams.


Data inside, request from outside

Typical situation: there is an external application — a personal account, mobile app, or partner service — that needs to get up-to-date data from an internal system. For example, a client’s balance from billing.

The standard options are all worse than each other. Opening a port to the outside is the most obvious one, and it’s the first thing security teams reject — correctly so. Setting up a VPN sounds reasonable, but in practice it’s a separate project with its own approvals, certificates, and support, and it’s not always possible on the external system side, especially if it’s someone else’s cloud. A public IP — there might simply not be one: the provider gives dynamic addressing behind NAT, and forwarding a connection from outside to inside without additional infrastructure is impossible. For small companies and branches this is a perfectly normal situation.

Real-world case

In one project we needed to implement a Telegram bot for a provider, through which a user could view their current balance. The problem was that the billing system was inside a closed network inside a datacenter and had a SOAP API accessible only from inside the network. Opening access to the outside was forbidden, implementing a VPN on the bot side was impossible, and the bot itself ran in the cloud. As a result, even a simple request “show balance” turned into an integration task between completely isolated systems.


Broker outside as a meeting point

The idea is simple but non-obvious: run a RabbitMQ instance not inside the organization, but outside — next to the external application, on a publicly accessible server. This broker is simultaneously reachable by both the external system and the internal one — because the internal service itself initiates an outgoing connection to the outside, and outgoing connections, unlike incoming ones, are almost never blocked by firewalls or NAT.

The scheme looks like this: the external application puts a request into a queue on the broker. The internal service listens to that queue, takes the request, goes to the billing or any other internal source, and puts the response into another queue. The external application retrieves the response.

Data goes outside exactly when it is needed, and only the data that was requested. The internal system remains closed — it decides when and what to give.


RPC over queues: asynchrony without pain

The obvious complexity of this scheme is asynchrony. When there are many requests, you need to understand which response corresponds to which request. You can tag each message manually, but RabbitMQ provides a ready-made mechanism — RPC over queues.

It works like this: when sending a request the client specifies the name of a temporary queue to which it expects the reply (reply_to), and a unique correlation_id. The internal service processes the request and sends the response not to a common queue but directly to this temporary queue with the same correlation_id. The client simply waits for its response — without extra routing logic.

You don’t have to implement this manually — RPC over RabbitMQ is already supported by ready libraries. For example, FastStream provides a convenient declarative interface: a queue subscriber is described as a regular async function, and the library handles the reply_to and correlation_id mechanism. The code becomes more compact and reads like a normal service rather than working with the broker directly.

Below is a minimal example in pika to make clear what happens under the hood.

Client (external system)

import pika, uuid

connection = pika.BlockingConnection(pika.ConnectionParameters('broker.example.com'))
channel = connection.channel()

# Temporary reply queue — RabbitMQ will delete it after disconnect
result = channel.queue_declare(queue='', exclusive=True)
reply_queue = result.method.queue
correlation_id = str(uuid.uuid4())

channel.basic_publish(
    exchange='',
    routing_key='billing.requests',
    properties=pika.BasicProperties(
        reply_to=reply_queue,
        correlation_id=correlation_id,
    ),
    body='{"client_id": 42, "action": "get_balance"}'
)

# Wait for the response to our specific request
for method, props, body in channel.consume(reply_queue):
    if props.correlation_id == correlation_id:
        print(f"Balance: {body.decode()}")
        channel.cancel()
        break

Handler (internal service)

import pika, json

connection = pika.BlockingConnection(pika.ConnectionParameters('broker.example.com'))
channel = connection.channel()
channel.queue_declare(queue='billing.requests')

def on_request(ch, method, props, body):
    request = json.loads(body)
    balance = get_balance_from_billing(request['client_id'])  # call to internal source

    ch.basic_publish(
        exchange='',
        routing_key=props.reply_to,
        properties=pika.BasicProperties(correlation_id=props.correlation_id),
        body=str(balance)
    )
    ch.basic_ack(delivery_tag=method.delivery_tag)

channel.basic_consume(queue='billing.requests', on_message_callback=on_request)
channel.start_consuming()

This turns asynchronous interaction into something almost synchronous from the code’s perspective: send a request — wait — receive a response. At the same time there is no need to store any additional state.


What this achieves

This scheme addresses several problems at once without extra infrastructure: no public IP needed, no VPN, no need to open access into the internal network. Authorization and encryption are handled at the RabbitMQ level — TLS and credentials replace the whole infrastructure of API keys and tokens. You can deploy this in a day or two: two small services and one broker instance.

It’s not a universal tool. But for tasks where you need to provide access to data from a closed network on demand — billing, internal CRMs, accounting systems — this is one of the simplest and cheapest options. For streaming large volumes or scenarios with strict latency requirements the scheme is less suitable — other tools are needed there.

// Python Dev

Другие статьи Python Dev

Все статьи

// Python Projects

Проекты Python Dev

Все проекты

2026-03-26

Telegram bot for voice pranks

An upgrade of an existing Telegram bot: calls through SIP and Telegram, response recording, and monetization through Telegram Stars.

// Contact

Need help?

Get in touch with me and I'll help solve the problem

Send request
Write and get a quick reply