# Direct Secure Messaging

Receive referrals, discharge summaries and results over Direct - an IMAP connection to your HISP reads the inbox and your service gets each clinical document, out of its attachment or its XDM package, in Python.

Direct Secure Messaging is how providers in the United States send referrals, discharge summaries and results to one another - an encrypted email from one Direct address to another, with the clinical document, usually a [C-CDA](https://zato.io/docs/dev/healthcare/hl7/ccda/index.html), as an attachment. Zato reads the messages from your inbox, hands each document to your service and lets the service send Direct messages back, all in Python.

The encryption and the trust between organizations are the work of the HISP - the Health Information Service Provider that hosts your Direct address. The HISP signs and encrypts outgoing messages with S/MIME, decrypts and verifies incoming ones against the DirectTrust trust bundles, and sends the message disposition notifications that confirm delivery to the sender's HISP. What reaches your inbox is a plain email with its attachments, which makes Zato an edge client of the HISP - a program that reads the inbox over IMAP and sends through the HISP's SMTP relay, with no certificates to manage.

## Connect to your HISP {#connect-to-your-hisp}

An IMAP connection points at the inbox the HISP gives you, and its Scheduler section makes it poll the inbox and invoke your service with what arrives. Go to **Connections** > **Outgoing** > **E-mail** > **IMAP** in the Dashboard, click **Create a new IMAP connection** and fill in the form:

1. **Name:** Direct Inbox
2. **User / e-mail:** your Direct address, e.g. `referrals@direct.example.com`
3. Click **Toggle options** in the Generic IMAP row and fill in **Host** with the IMAP server of your HISP, e.g. `imap.hisp.example.com`
4. Click **Toggle options** in the Scheduler row and fill in **Run every** 1 minute, a **Start time**, the **Service** to invoke and **Invoke with** set to **Each attachment**
5. Click **OK**

[Video: New Direct inbox connection](https://zatosource-production.b-cdn.net/docs/gfx/email/imap-direct-create.webm?v=1791135994)

Dashboard menu: Connections > Outgoing > E-mail > IMAP

Saving the connection creates a scheduler job for it. On each run, the job reads the unseen messages in the inbox and, in the **Each attachment** mode, invokes your service once per attachment, so a message with a C-CDA and its PDF rendering invokes the service twice. A message is marked as seen once all of its attachments were handled without an exception, and one that raised stays unseen, so the next run delivers it again and nothing is lost. The connection's password is set after the connection is created, through the **Change password** link on its row.

The same connection in an [enmasse](https://zato.io/docs/admin/enmasse.html) file:

```yaml
email_imap:
  - name: Direct Inbox
    host: imap.hisp.example.com
    port: 993
    username: referrals@direct.example.com
    password: Zato_Enmasse_Env.DirectInboxPassword
    mode: ssl
    get_criteria: UNSEEN
    scheduler_service: direct.referral-intake
    scheduler_run_every: 1
    scheduler_run_unit: minutes
    scheduler_start_date: 2026-10-05T08:00:00
    scheduler_invoke_with: each_attachment
```

## A complete service {#a-complete-service}

A service is a Python class that Zato runs when something invokes it. The service below receives each attachment of each Direct message, converts the C-CDA it carries to FHIR and stores the result in a FHIR server through an [outgoing FHIR connection](https://zato.io/docs/dev/healthcare/hl7/fhir/connections/index.html):

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class ReferralIntake(Service):
    """ Receives the attachments of Direct messages and stores each clinical document they carry in a FHIR server.
    """
    name = 'direct.referral-intake'

    def handle(self) -> 'None':

        # The attachment the message carried, with the subject and the sender of the message it came from ..
        attachment = self.request.input
        self.logger.info('Received `%s` from %s -> %s', attachment.filename, attachment.sent_from, attachment.subject)

        # .. each document inside it - the attachment itself or, if it is an XDM package, each document the package names ..
        for document in attachment.documents():

            # .. the human-readable renderings that travel alongside are not converted ..
            if document.mime_type == 'application/pdf':
                continue

            # .. and each clinical document becomes a FHIR bundle that goes to the server as a whole.
            bundle = self.ccda.to_fhir(document.data)

            client = self.fhir['FHIR.Registry']
            client.execute('', method='post', data=bundle.to_dict())
```

The `documents()` call is what makes the service the same whether the sender attached the C-CDA directly or packaged it as XDM - the service reads documents, not attachments. Each document has `data`, `file_name`, `mime_type` and `size`, and a document that came out of an XDM package also has the title, patient ID and document codes its metadata gave it, as [IHE XDM document packages](https://zato.io/docs/dev/healthcare/xdm/index.html) describes.

## Documents packaged as XDM {#documents-packaged-as-xdm}

Many senders put the documents of a message in an IHE XDM package - a zip with a metadata file describing each document - rather than attaching them one by one, and a message whose subject starts with `XDM/1.0/DDM` carries one. The `documents()` call above opens the package and returns each document it names, with the metadata, so the service above handles such messages with no change. [IHE XDM document packages](https://zato.io/docs/dev/healthcare/xdm/index.html) covers what is in a package and what is checked when it is read.

## Send a Direct message {#send-a-direct-message}

A Direct message is sent the way any email is - through an [SMTP connection](https://zato.io/docs/dev/examples/smtp.html) pointing at the HISP's relay, from your Direct address to the recipient's. The HISP signs and encrypts the message once it leaves your service:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.common import SMTPMessage
from zato.server.service import Service

class SendReferral(Service):
    """ Sends a referral with its C-CDA to another provider's Direct address.
    """
    name = 'direct.send-referral'

    def handle(self) -> 'None':

        conn = self.email.smtp.get('HISP Relay').conn

        msg = SMTPMessage()
        msg.from_ = 'referrals@direct.example.com'
        msg.to = 'intake@direct.clinic.example.com'
        msg.subject = 'Referral for Mary Jones'
        msg.body = 'Please see the attached continuity of care document.'

        # Delivery notifications come back to this address, and the Message-ID is how they are matched to this message
        msg.headers['Disposition-Notification-To'] = 'referrals@direct.example.com'
        msg.headers['Message-ID'] = '<referral-20260114-0042@direct.example.com>'

        msg.attach('CCD.xml', self.request.input['document'])

        conn.send(msg)
```

## Receipts and delivery notifications {#receipts-and-delivery-notifications}

Direct confirms delivery with message disposition notifications - MDNs - and both of them are sent by the receiving HISP, not by the program reading the inbox. A `processed` MDN says the HISP decrypted and verified the message, and a `dispatched` MDN says the HISP placed it in the recipient's inbox. Your service receives Direct messages without sending anything in return, and a message was received once it was read from the inbox.

When your service is the sender, the MDNs for its messages arrive in your inbox as messages of their own, with a `multipart/report` content type and the `Message-ID` of the original message in their `Original-Message-ID` field, which is how a service tracking its own deliveries recognizes them. A sender that does not track deliveries lets the HISP handle them, which is what most do.

## Troubleshooting {#troubleshooting}

**Nothing arrives.** The connection's host is the HISP's IMAP server rather than its web address, the user is the full Direct address, and the Scheduler row has its service and start time filled in - a connection without them reads nothing on its own. The job the connection created is listed under **Scheduler** as `imap.Direct Inbox`.

**Every message arrives twice.** The service raised an exception on the first run, so the message stayed unseen and the next run delivered it again. The exception is in the server log under the service's name.

**The attachment is a zip.** It is an XDM package, and `documents()` returns the documents inside it rather than the zip - a service reading `attachment.data` directly reads the zip itself. [IHE XDM document packages](https://zato.io/docs/dev/healthcare/xdm/index.html) has the details.

**The MDN never came back.** MDNs are sent by the recipient's HISP once it processed the message, and a recipient whose HISP does not send `dispatched` MDNs confirms nothing beyond `processed`. The `Disposition-Notification-To` header is what asks for them.

## See also {#see-also}

- [IHE XDM document packages](https://zato.io/docs/dev/healthcare/xdm/index.html) - The packages that Direct messages carry their documents in
- [C-CDA to FHIR](https://zato.io/docs/dev/healthcare/hl7/ccda/index.html) - Converting the documents that arrive over Direct
- [IMAP](https://zato.io/docs/dev/examples/imap.html) - Reading email on a schedule, with messages or attachments on input
- [SMTP](https://zato.io/docs/dev/examples/smtp.html) - Sending email through a connection
- [FHIR connections](https://zato.io/docs/dev/healthcare/hl7/fhir/connections/index.html) - The outgoing connection that stores the bundle

## Learn more {#learn-more}

- [Healthcare interface engine](https://zato.io/docs/dev/healthcare/) - Clinical messages, FHIR, conversion and operations
- [HL7 v2 parsing](https://zato.io/docs/dev/healthcare/hl7/v2/parsing/) - Messages as typed objects, with field access and validation
- [MLLP channels](https://zato.io/docs/dev/healthcare/hl7v2/mllp/) - Receiving and sending HL7 v2 over MLLP sockets
- [FHIR integrations](https://zato.io/docs/dev/healthcare/hl7/fhir/) - Read and write FHIR resources, with paths, bundles and extensions
- [HL7 v2 to FHIR](https://zato.io/docs/dev/healthcare/hl7/to-fhir/) - Converting v2 messages into FHIR bundles, codes and references included
- [EDIFACT](https://zato.io/docs/dev/healthcare/edifact/) - Parsing interchanges, dialects and the transports they arrive on
- [Transformation](https://zato.io/docs/dev/healthcare/transformation/) - One message in, another standard out, in ordinary Python
- [Audit log](https://zato.io/docs/dev/healthcare/audit-log.html) - Every message with its acknowledgment, searchable by patient identifier
- [AI in clinical interfaces](https://zato.io/docs/dev/healthcare/ai/) - Services calling LLMs and AI agents calling clinical services as tools
