If you are set on using UART as a transport mechanism, note the following:
1) The UART transport is byte framed - that is, you send and receive data one byte at a time. In a perfect world, if you'd like to send "hello" to the receiver, you'd simply send (in the transmitter) h, e, l, l , o. The receiver would read, byte by byte, and process this. However, in our imperfect noisy world you will need to account for a few things:
a) The receiver may receive false bytes. The transmitter might be completely idle, but the receiver may think it received a byte. You need to detect this and discard such bytes.
b) The receiver may receive a distorted version of what the transmitter sent. You need to either detect and fix this or discard such distorted data.
To help with a, you can simply send a pattern (synchronization) byte before each transaction. For example, a byte made up of 4 bits of 1101 (or whatever constant pattern) and then the length of the packet you are sending (up to 16 bytes). When the receiver receives a byte when it is idle (that is, it is not in the process of reading a packet), it first checks if the first 4 bits of the received byte are 1101. If they aren't, it simply discards the byte and returns to idle. If they are, it will wait for the next N bytes, as specified in the lower 4 bits of the synchronization byte.
To help with b, you append a checksum or CRC to the end of the packet.
This means that if you'd like to send "hello", you would send:
[sync byte = 11010101][h][e][l][l][o][8/16bit checksum or CRC]
2) You can operate the UART without using interrupts. This requires, however, the "attention" of your software. You MUST be able to service received bytes CONSTANTLY at the receiver side - otherwise you will overflow your 1 byte receive buffer. In other words, if the transmitter sent 2 bytes - the first byte will be read by the receiver's hardware UART into RCREG (UART receive register) and RCIF would be set (indicating - byte waiting, read me). If the software does not read RCREG by the time the second byte is received, you will get an overflow (lose the second byte and any consequent byte). You can do this at first, but very quickly you will see that you reach overflow conditions no matter what you do - unless you are literally doing zero processing of received bytes.
Using interrupts, you have a high priority ISR that will be called whenever a byte is ready to be read, no matter what your SW is doing. Your ISR will simply read this into a circular software buffer of your choice. The application can then read this circular buffer at its leisure, allowing for much more lax timing.