Well, it really depends.
First thing is what are really your constraints. You said the operator doesn't allow you to use CoAP/M2M so you have to stick with raw UDP but there are many other protocol stacks supported by the Quectel BC95, according to this official datasheet, it can use all of these:
- IPv4
- IPv6
- UDP
- CoAP
- LwM2M
- Non-IP
- DTLS
- TCP
- MQTT
So you can for example use raw TCP/IP or the mentioned by @behroozbc MQTT which basically wraps over TCP as well. But since you're not using it and talking about UDP I'm assuming you are allowed only UDP or all the protocol stacks not wrapped over TCP (which is probably the case since the three-way handshake of TCP is considered the most energy consuming part of the communication and is probably why you're not allowed to use it). Unfortunately this modem doesn't support MQTT-SN based on UDP, so, for the sake of universality of the question, let's stick with raw UDP.
UDP and TCP were developped for very different purposes so there is quite little overlapping when it comes to what works well using them. And regular quick sending a probably big data set every hour while trying to use as little energy as possible is not suitable for raw UDP.
But you know, when there is a no solution, you build one.
You can recreate some of the characteristic TCP features using UDP. Let's talk about each feature/problem separately.
Data corruption
Although data corruption is not really a frequent problem with UDP (well, at least not any more than in TCP) but can still happen. This should be easily manageable by simply adding a digest checksum to the message. A checksum can also be a part of the of the UDP header, but it's not obligatory, I don't know if that modem uses it.
If for some weird reason data corruption would tend to happen quite often you can think of implementing some simple Cyclic Redundancy Check like those used in CDs letting them be read even though having scratches.
Packet loss
That's the main problem with UDP - packets don't usually get changed but they do get lost often.
You can implement your own modified version of the three-way handshake. And here comes really handy the fact that the data is supposed to be sent cyclically every hour.
Every packet should contain a simplified timestamp (for the hour it is supposed to send the data), an ID and an indicator of the structure of the packet set. This is superficial when we send only one packet but in many cases the data won't fit only one and we'll have to send a whole set of packets. So for example each packet would have some bytes saying "sending data for time 16:52, packet # 4 of 23".
First shake
The modem sends the whole packet set to the server. The server hopefully recovers some of them. If the server received a non complete set (it would know thanks to the indicated structure of the packets that some are missing). Alternatively, if all the packets were lost, the server would know because it's supposed to receive some data on the hour given.
Second shake
The server sends to the modem information (also with UDP) which packets it did receive (or if if it received none) or the information it received them all.
Third shake
The modem hopefully receives the information from the server. If the information says some packets are missing, the modem returns to first shake and sends only the missing packets.
If the information says all packets were received, the modem sends server the third shake basically saying "ok, good to know."
Trigger points
Now what to do when when things don't go as planned.
If the information from the second shake says some packets are missing, the modem returns to first shake and sends only the missing packets.
If the modem didn't receive the feedback information from the server, it doesn't do anything (so all the dirty energy-consuming work is done by the server).
If the server sent feedback packet and maybe received some resent packets from the modem but after some time (for example 30-60 seconds) it's data is still incomplete or it didn't receive the third shake, it sends the second shake (the feedback info) again.
This way:
- all of the data will be delivered eventually as long as packet loss probability is more than 0%
- (almost) all the connection logic is handled by the server which saves on power usage of the modem
- we open an equivalent of a TCP connection only for every hour-to-hour session, not every packet, which is probably much more energy efficient
- these "connections" do not interfere with one another (thanks to the timestamps) so even if the conversation between the modem and the server takes more than 1 hour, still no data is lost.
Authentication
Using UDP over TCP makes it a bigger concern that the devices can trust that they are talking to each other, not some other in-the-middle device which could for example send wrong packets to the server or send constant fake second handshake data to the modem.
Probably the simplest way to achieve this is use not exactly encryption but authentication using some simple digital signature so that each message is signed with the devices private key.
The process of checking the message against the RSA hash and the public key is not computationally very cheap, so that's another reason the server should sens to the modem as few messages as possible and rather receive them.
Of course you also go in and also encrypt the data sent.
Packet size
Worth noting is the fact that packets can't be quite big in size. The MTU for Quectel BC95 according to this data sheet is 512 bytes. We need to use 20 bytes for the IPv4 protocol, 8 bytes for the UDP which leaves us with 484 bytes for the actual data and also the timestamp, the packet set structure and the authentication/encryption hash.
So yeah, that's the way you work around UDP to work more like TCP.