Skip to content

Manual Setup

This guide walks through every component the installer would normally configure for you. Use this path if your security team requires manual review of each step, or if you want to understand the architecture before it runs.

The result is identical to the Quick Start — a Mirth channel that polls Synura for messages over a secure TLS tunnel.


Mirth Connect stunnel Synura
┌──────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ JavaScript │ │ TLS proxy │ │ Synura Connect │
│ Reader polls │─MLLP─│ localhost:6661 │─TLS──│ subdomain.synura.io │
│ every 5 min │ │ client mode │ 1.3 │ port 443 │
└──────────────┘ └──────────────────┘ └─────────────────────┘
  1. The Mirth channel sends a QBP^Q11 query (HL7 mailbox drain request) via plain MLLP to localhost:6661
  2. stunnel encrypts the connection and forwards it to {subdomain}.synura.io:443 over TLS 1.3
  3. Synura responds with an RSP^K11 containing any queued messages
  4. The channel extracts the HL7 payloads and presents them as received messages in the Mirth dashboard

No inbound ports are opened. No firewall changes are needed. All traffic is outbound on port 443.


  • Mirth Connect 3.8.1 or later installed and running
  • Oracle JDK 8 installed (includes javaws for Mirth Administrator)
  • Administrator access on the Mirth server
  • Outbound TCP 443 from the Mirth server to synura.io
  • Your Synura subdomain and sending facility code

stunnel is a free, open-source TLS proxy. It wraps plain MLLP traffic in TLS so your Mirth instance can communicate securely with Synura without commercial plugins.

  1. Download the stunnel Windows installer from stunnel.org/downloads.html (the 64-bit .exe installer).
  2. Run the installer. Accept the defaults — it installs to C:\Program Files (x86)\stunnel\.
  3. stunnel registers itself as a Windows service automatically.
Terminal window
# Debian/Ubuntu
sudo apt-get install stunnel4
# RHEL/CentOS
sudo yum install stunnel

Create or replace the stunnel configuration file.

Edit C:\Program Files (x86)\stunnel\config\stunnel.conf:

[synura]
client = yes
accept = 127.0.0.1:6661
connect = {subdomain}.synura.io:443

Replace {subdomain} with your assigned Synura subdomain (e.g. acmelab).

Edit /etc/stunnel/synura.conf:

[synura]
client = yes
accept = 127.0.0.1:6661
connect = {subdomain}.synura.io:443

Terminal window
Start-Service stunnel
Set-Service stunnel -StartupType Automatic

Verify it is listening:

Terminal window
netstat -an | findstr 6661

You should see LISTENING on 127.0.0.1:6661.

Terminal window
sudo systemctl enable stunnel4
sudo systemctl start stunnel4
ss -tlnp | grep 6661

Before configuring Mirth, verify the tunnel works.

Terminal window
Test-NetConnection 127.0.0.1 -Port 6661

Expected result: TcpTestSucceeded : True

Terminal window
echo | openssl s_client -connect {subdomain}.synura.io:443 -servername {subdomain}.synura.io 2>&1 | head -10

You should see CN=*.synura.io in the certificate chain. If you see a TLS error, check Troubleshooting.


Mirth’s Java runtime needs to trust the Let’s Encrypt certificate authority. Most modern Java installations already include it, but older versions may not.

Locate the keytool binary in your Java installation:

Terminal window
# Windows — typically:
"C:\Program Files\Java\jdk1.8.0_431\bin\keytool.exe"

Check if the cert is already trusted:

Terminal window
keytool -list -keystore "C:\Program Files\Java\jdk1.8.0_431\jre\lib\security\cacerts" -storepass changeit -alias "isrg-root-x1"

If it is already present, skip to Step 6.

If it is not present, import it. The Synura installer package includes the certificate at certs/isrg-root-x1.pem:

Terminal window
keytool -importcert -noprompt -trustcacerts -alias "isrg-root-x1" `
-file "C:\path\to\mirth-installer\certs\isrg-root-x1.pem" `
-keystore "C:\Program Files\Java\jdk1.8.0_431\jre\lib\security\cacerts" `
-storepass changeit

Alternatively, download the ISRG Root X1 certificate directly from letsencrypt.org/certificates/.


Open the Mirth Connect Administrator and create a new channel.

Mirth Connect login screen

FieldValue
NameSynura - Mailbox Poller
DescriptionPolls Synura for messages via QBP^Q11 over MLLP/TLS

Channel Summary tab

  1. Set Connector Type to JavaScript Reader.

Source tab showing JavaScript Reader

  1. Set the Poll Interval to 300000 (5 minutes in milliseconds). Adjust as needed — lower values poll more frequently.
  2. Paste the following JavaScript into the Script field:
var CONFIG = {
sendingApplication : 'SYNURA_POLLER',
sendingFacility : 'YOUR_FACILITY_CODE',
batchSize : '1',
processingId : 'P',
stunnelHost : '127.0.0.1',
stunnelPort : 6661
};
try {
var now = new java.util.Date();
var sdf = new java.text.SimpleDateFormat('yyyyMMddHHmmss');
var timestamp = sdf.format(now);
var msgCtrlId = org.apache.commons.lang3.StringUtils.remove(
java.util.UUID.randomUUID().toString(), '-');
var cursor = $gc('synura_cursor');
var batchTag = $gc('synura_batch_tag');
if (!cursor || !batchTag) {
batchTag = 'BATCH_' + timestamp + '_'
+ Math.floor(1000 + Math.random() * 9000);
}
var msg = '';
msg += 'MSH|^~\\&'
+ '|' + CONFIG.sendingApplication
+ '|' + CONFIG.sendingFacility
+ '|SYNURA|SYNURA'
+ '|' + timestamp
+ '||QBP^Q11^QBP_Q11'
+ '|' + msgCtrlId
+ '|' + CONFIG.processingId
+ '|2.5\r';
msg += 'QPD|MailboxDrain'
+ '|' + batchTag
+ '|' + CONFIG.batchSize
+ '\r';
if (cursor) {
msg += 'DSC|' + cursor + '|I\r';
}
var socket = new java.net.Socket(
CONFIG.stunnelHost, CONFIG.stunnelPort);
socket.setSoTimeout(30000);
var out = socket.getOutputStream();
var inStream = socket.getInputStream();
var mllpMsg = String.fromCharCode(0x0B) + msg
+ String.fromCharCode(0x1C) + '\r';
out.write(new java.lang.String(mllpMsg).getBytes('UTF-8'));
out.flush();
var buffer = new java.io.ByteArrayOutputStream();
var b;
var foundFS = false;
while ((b = inStream.read()) !== -1) {
if (b === 0x1C) { foundFS = true; continue; }
if (foundFS && b === 0x0D) { break; }
if (b !== 0x0B) { buffer.write(b); }
}
socket.close();
// IMPORTANT: Coerce Java String to JavaScript string
var response = '' + buffer.toString('UTF-8');
if (response.length > 0) {
var segments = response.split('\r');
var qakSeg = null;
var obxSeg = null;
var dscSeg = null;
for (var i = 0; i < segments.length; i++) {
var seg = segments[i];
if (seg.indexOf('QAK') === 0) qakSeg = seg;
if (seg.indexOf('OBX') === 0) obxSeg = seg;
if (seg.indexOf('DSC') === 0) dscSeg = seg;
}
if (qakSeg) {
var qakFields = qakSeg.split('|');
var qakStatus = qakFields[2];
if (qakStatus === 'OK' && obxSeg) {
var obxFields = obxSeg.split('|');
var msgId = obxFields[3] || 'unknown';
var rawPayload = '';
for (var k = 5; k < obxFields.length; k++) {
if (k > 5) rawPayload += '|';
rawPayload += obxFields[k];
}
// IMPORTANT: Coerce Java String to JavaScript string
var payload = '' + org.apache.commons.lang3.StringUtils
.replace(rawPayload, '~', '\r');
logger.info('Synura poll: received ' + msgId);
if (dscSeg) {
var dscFields = dscSeg.split('|');
$gc('synura_cursor', dscFields[1]);
$gc('synura_batch_tag', qakFields[1]);
} else {
// IMPORTANT: Use empty string, not null
$gc('synura_cursor', '');
$gc('synura_batch_tag', '');
}
return payload;
}
}
}
} catch (e) {
logger.error('Synura poll error: ' + e.toString());
}
return null;

Before saving, replace YOUR_FACILITY_CODE with your sending facility code (the MSH-4 value registered with Synura).

Source tab with polling script

Ensure these settings:

SettingValue
Process BatchNo
Source QueueOFF (Respond after processing)

Source Settings

Leave the default Channel Writer destination. Messages will appear in the Mirth dashboard. You can route them to a file writer, database writer, or another channel as needed.


  1. Click Save Changes.
  2. Go to the Dashboard.
  3. Select the channel and click Deploy Channel.
  4. Status should show Started.

Dashboard showing channel started

The channel polls Synura on the interval you configured. When messages are waiting:

  • The Received count increments
  • Messages appear in the channel’s Messages tab
  • The Server Log shows entries like Synura poll: received MAILBOX^{message-id}

When the mailbox is empty, the channel polls silently with no log output.

Dashboard showing received messages

Channel messages list

Raw HL7 message content

Server log showing poll entries


The channel requests one message per poll using a QBP^Q11 query. If more messages are waiting, Synura includes a continuation token in the response. The channel automatically sends follow-up requests using that token until the mailbox is drained.

At a 5-minute poll interval, the channel drains up to 60 messages per hour. For higher throughput, reduce the poll interval — 5 seconds has been tested and works reliably (720+ messages/hour).


If you are writing or modifying the channel JavaScript on Mirth 3.8.1, be aware of these Rhino engine behaviours:

  • Java String coercion: buffer.toString('UTF-8') and StringUtils.replace() return Java Strings, not JavaScript strings. Always prefix with '' + before calling .split(), .indexOf(), or checking .length. Without this, .split('|')[2] returns charAt(2) instead of the third array element.
  • .length not .length(): After coercion to a JavaScript string, use .length (property), not .length() (method).
  • $gc does not accept null: $gc('key', null) throws a NullPointerException. Use $gc('key', '') (empty string) instead.
  • Batch processing is not supported: Do not enable Process Batch on the source connector. The Rhino engine cannot return multiple messages from a JavaScript Reader. Use batchSize=1 with DSC continuation instead.