If you have started down the path of exploring DeltaChat you may have wondered about the difficulty of integrating any automations or bots with the platform. There do exist some nice libraries like the Python deltabot-cli-py, but what if you don't want to use Python? Requiring robust SMTP/IMAP and PGP libraries, handling account management, etc for your language of choice is a tall order, but fret not -- you don't need to do any of that.
DeltaChat's core is written in Rust and has everything you need because it comes with a JSON-RPC server which makes it simple to integrate with any language. You must build the deltachat-rpc-server
binary:
$ git clone https://github.com/chatmail/core && cd core
$ cargo build --locked -p deltachat-rpc-server --release
You can find the binary in the target/release
directory.
Using the JSON-RPC
The deltachat-rpc-server
is designed to operate over stdio much like the language servers for IDEs. You simply execute the binary from your language of choice and communicate with it over stdio with JSON formatted newline terminated strings.
📢 Remember, the "id" is an integer you control to identify the RPC call's response.
The basic operation is as follows:
1. Get the list of accounts stored in the sqlite database
{
"id": 610,
"params": [],
"jsonrpc": "2.0",
"method": "get_all_accounts"
}
2. If there are no accounts, you'll need to add one. This simply adds a stub into the sqlite database and returns the id of the account. It's probably going to return account id 1.
{
"id": 642,
"params": [],
"jsonrpc": "2.0",
"method": "add_account"
}
3. Now you need to set some values for the account.
{
"id": 674,
"params": [
1,
{
"addr": "your-random-addr@nine.testrun.org",
"bot": "1",
"displayname": "DeltaChatBot",
"mail_pw": "your-random-password"
}
],
"jsonrpc": "2.0",
"method": "batch_set_config"
}
4. The account has enough configuration to operate. Now we need to mark the account as 'configured' so it is ready to be used.
{
"id": 706,
"params": [
1
],
"jsonrpc": "2.0",
"method": "configure"
}
5. You probably want to get the invite link for the account so you can add it as a contact and start sending it test messages.
{
"id": 770,
"params": [
1,
null
],
"jsonrpc": "2.0",
"method": "get_chat_securejoin_qr_code"
}
📢 If you pass a value of a chat_id instead of
null
as the second parameter you will get an invite for that chat group.
6. Start the IO for all configured accounts.
{
"id": 802,
"params": [],
"jsonrpc": "2.0",
"method": "start_io_for_all_accounts"
}
7. Now tell it to asynchronously wait for messages for your account_id. You will receive a list of one or more integer message ids.
{
"id": 1186,
"params": [
1
],
"jsonrpc": "2.0",
"method": "wait_next_msgs"
}
📢 After every time you receive a list of message IDs you will have to issue the "wait_next_msgs" again.
8. Take the list of IDs and fetch the messages
{
"id": 1194,
"params": [
1, [17,18]
],
"jsonrpc": "2.0",
"method": "get_messages"
}
Now you will receive the JSON encoded messages. An example looks like the following when fetching message id "36".
{
"jsonrpc": "2.0",
"id": 899,
"result": {
"36": {
"chatId": 10,
"dimensionsHeight": 0,
"dimensionsWidth": 0,
"downloadState": "Done",
"duration": 0,
"error": null,
"file": null,
"fileBytes": 0,
"fileMime": null,
"fileName": null,
"fromId": 10,
"hasDeviatingTimestamp": false,
"hasHtml": false,
"hasLocation": false,
"id": 36,
"isBot": false,
"isEdited": false,
"isForwarded": false,
"isInfo": false,
"isSetupmessage": false,
"kind": "message",
"originalMsgId": null,
"overrideSenderName": null,
"parentId": 35,
"quote": null,
"reactions": null,
"receivedTimestamp": 1742767997,
"savedMessageId": null,
"sender": {
"address": "az2g6a4rm@chat.feld.me",
"authName": "Mark",
"color": "#008b16",
"displayName": "Mark",
"e2eeAvail": true,
"id": 10,
"isBlocked": false,
"isBot": false,
"isProfileVerified": true,
"isVerified": true,
"lastSeen": 1742767997,
"name": "",
"nameAndAddr": "Mark (az2g6a4rm@chat.feld.me)",
"profileImage": "accounts/7c4e2fd6-6c51-4f63-bc08-aa9adbdfd107/dc.db-blobs/d5e20b3667354ce4f02d60b61a76bc5.jpg",
"status": "",
"verifierId": 1,
"wasSeenRecently": true
},
"setupCodeBegin": null,
"showPadlock": true,
"sortTimestamp": 1742767997,
"state": 10,
"subject": "Re: Message from Mark",
"systemMessageType": "Unknown",
"text": "test message",
"timestamp": 1742767997,
"vcardContact": null,
"videochatType": null,
"videochatUrl": null,
"viewType": "Text",
"webxdcHref": null,
"webxdcInfo": null
}
}
}
📢 The message is deleted from the IMAP server once you've fetched it. This is handled by DeltaChat automatically.
- You need to mark them as seen before issuing that next
wait_next_msgs
again:
{
"id": 1266,
"params": [
1,
[36]
],
"jsonrpc": "2.0",
"method": "markseen_msgs"
}
📢 You're technically marking it as 'seen' in the local sqlite database.
That's basically it. To send a message for account id 1 to chat_id 10 you use this method:
{
"id": 355,
"params": [
1,
10,
{
"text": "testing"
}
],
"jsonrpc": "2.0",
"method": "send_msg"
}
📢 An OpenRPC JSON schema file can be output with
deltachat-rpc-server --openrpc
so you can investigate all available methods. However, it does not include all of the optional paramters that methods accept so you will have to do a little digging into the DeltaChat / Chatmail Rust core codebase until there is a better documentation source for these parameters.
This is just scratching the surface of what is possible with DeltaChat's JSON-RPC. All of the pain of email and key management has been abstracted away for you. Writing your own custom integrations for this E2EE messaging ecosystem is easier than you probably imagined.