Blockchain technology is not just backing cryptocurrency, it has a wide range of real-world use cases. Here, we are talking about a decentralized mailing software based on blockchain technology which is unique to the traditional centralized server-based email system.
Also, it offers enhanced security, privacy, and trust.
The centralized server-based email platforms are at risk of single-point failure problems. Attackers are using different techniques like Brute Force Attacks, and IMAP protocol or email servers hacks to get unauthorized access to user’s accounts. It suggests this modern generation demands an immediate solution to control their data and privacy with a secure platform to send or receive emails.
By keeping decentralization in mind and utilizing blockchain's unique capabilities, this article teaches you to develop decentralized mailing software.
Here, we are going to build a decentralized mailing software on Rootstock’s testnet which can be ultimately deployed to Rootstock’s mainnet too. Our software will be able to send or receive emails, send files as attachments, reply to emails, and delete messages. So, it is going to be interesting. Let’s get started by creating the project’s directory say “decentralized-mail
”.
Before getting started in the development process, you need to have ideas about the code functionalities of the mentioned programming languages and tools.
Ideas about the code’s functionalities of JavaScript, Solidity, and CSS.
Node.js: Download and install from nodejs.org.
Truffle: For deploying smart contracts.
npm install -g truffle
Pinata SDK: For interacting with Pinata
npm install @pinata/sdk
Axios: For making HTTP requests to Pinata.
npm install axios
Please note the project’s directory first. Create the folders and files inside folders stepwise as follows.
We need to create a Solidity contract file. Create DecentralizedMail.sol
in the directory contracts/DecentralizedMail.sol
as follows:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract DecentralizedMail {
struct Message {
address sender;
address receiver;
string content; // IPFS hash or plain text
string imageHash; // IPFS hash of the image
uint256 timestamp;
bool isDeleted; // Flag to mark message as deleted
}
Message[] public messages;
mapping(address => uint256[]) public userMessages;
event MessageSent(address indexed sender, address indexed receiver, uint256 messageId);
event MessageDeleted(address indexed user, uint256 messageId);
function sendMessage(address _receiver, string memory _content, string memory _imageHash) public {
uint256 messageId = messages.length;
messages.push(Message({
sender: msg.sender,
receiver: _receiver,
content: _content,
imageHash: _imageHash,
timestamp: block.timestamp,
isDeleted: false
}));
userMessages[_receiver].push(messageId);
userMessages[msg.sender].push(messageId); // Add message to sender's message list
emit MessageSent(msg.sender, _receiver, messageId);
}
function getMessages() public view returns (Message[] memory) {
return messages;
}
function getUserMessages(address _user) public view returns (uint256[] memory) {
return userMessages[_user];
}
function getMessageById(uint256 messageId) public view returns (Message memory) {
require(messageId < messages.length, "Message does not exist");
return messages[messageId];
}
function deleteMessage(uint256 messageId) public {
require(messageId < messages.length, "Message does not exist");
Message storage message = messages[messageId];
require(message.receiver == msg.sender || message.sender == msg.sender, "Not authorized to delete this message");
message.isDeleted = true;
emit MessageDeleted(msg.sender, messageId);
}
function getUserInbox(address _user) public view returns (Message[] memory) {
uint256[] memory messageIds = userMessages[_user];
Message[] memory inbox = new Message[](messageIds.length);
uint256 counter = 0;
for (uint256 i = 0; i < messageIds.length; i++) {
Message storage message = messages[messageIds[i]];
if (!message.isDeleted) {
inbox[counter] = message;
counter++;
}
}
// Resize the array to fit the number of non-deleted messages
Message[] memory resizedInbox = new Message[](counter);
for (uint256 j = 0; j < counter; j++) {
resizedInbox[j] = inbox[j];
}
return resizedInbox;
}
}
Our contract provides a decentralized platform for users to send ( sendMessage
function is used), retrieve (getMessages
function is used), and delete messages (deleteMessage
function is used) securely using Rootstock blockchain technology. Messages are stored on-chain with details such as sender, receiver, content, image hash, timestamp, and deletion status.
We need a migration script to simplify the smart contract development lifecycle. Its main role in our project is to automate and manage the deployment process. Create a migration script named 1_deploy_contracts.js
in the root directory as migrations/1_deploy_contracts.js
as follows:
const DecentralizedMail = artifacts.require("DecentralizedMail");
module.exports = function(deployer) {
deployer.deploy(DecentralizedMail);
};
A Truffle configuration file is essential to define how and where our smart contract is deployed.
First, install the dependency using npm:
npm install @truffle/hdwallet-provider
It is designed to specify network settings, compiler versions, and other deployment parameters as follows:
const HDWalletProvider = require('@truffle/hdwallet-provider');
require('dotenv').config();
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*" // Match any network id
},
testnet: { // Renamed from rskTestnet to testnet
provider: () => new HDWalletProvider(process.env.MNEMONIC, 'https://public-node.testnet.rsk.co'),
network_id: 31, // RSK Testnet network id
gas: 5500000, // Gas limit (adjust if necessary)
gasPrice: 1000000000, // Gas price in wei (1 gwei)
networkCheckTimeout: 10000 // Timeout for network checks
}
},
compilers: {
solc: {
version: "0.8.0"
}
}
};
This configuration sets up deployment environments and compiler options. It ensures contracts are deployed and compiled correctly for development and test networks.
Note: Create .env
file in the root directory. Now, you create the file with the following codes and update them later.
You need to put your .env
file in the project’s root directory like .env.example
file as shown in Figure 1
.
MNEMONIC=your_mnemonic_phrase_here //You need to add your wallet's 12 words mnemonic phrase here
REACT_APP_PINATA_API_KEY=your_pinata_api_key
REACT_APP_PINATA_SECRET_KEY=your_pinata_secret_key
REACT_APP_CONTRACT_ADDRESS=your_rsk_testnet_contract_address
Please remember this step ** because you are updating this code later.
Our mailing app is also supporting file sharing features through the attachments. So, we are using the Upload Files feature to IPFS using Pinata. First, create the free Pinata account here.
Create a JavaScript file to allow file upload in the directory scripts/pinata-upload.js as follows:
const axios = require('axios');
const FormData = require('form-data');
require('dotenv').config();
const API_KEY = process.env.PINATA_API_KEY;
const SECRET_KEY = process.env.PINATA_SECRET_KEY;
async function uploadToPinata(content) {
const form = new FormData();
form.append('file', content);
try {
const response = await axios.post('https://api.pinata.cloud/pinning/pinFileToIPFS', form, {
headers: {
...form.getHeaders(),
'pinata_api_key': API_KEY,
'pinata_secret_api_key': SECRET_KEY,
},
});
return response.data.IpfsHash;
} catch (error) {
console.error('Error uploading to Pinata:', error);
throw error;
}
}
// Example usage
const fs = require('fs');
const filePath = './example.txt'; // Replace with your file path
const fileContent = fs.createReadStream(filePath);
uploadToPinata(fileContent)
.then(cid => console.log('File uploaded to IPFS with CID:', cid))
.catch(err => console.error(err));
You need the Pinata API key and secret key to update the .env
file at Step** as mentioned earlier. Go to your Pinata Dashboard>API Keys>New Key, and create your new Admin API key (Tick the Admin option). Save the API keys and secret keys to a safe place.
It is a crucial step. Our front end consists of all the basic features and codes to operate our react apps on the Rootstock testnet. To simplify the Front-end development process, I have already uploaded the codes on GitHub. You can download them from here. Also, the codes are long and our tutorial is already going to be long, you will be guided to download proper files from GitHub stepwise.
Set Up React Project
Open the terminal from your project’s directory, and enter the following commands.
npx create-react-app mail-dapp
cd mail-dapp
npm install web3 @pinata/sdk
Create React Components
In your src
folder, create another components
folder, and add the Upload.js
file by downloading from GitHub.
Main App Component
Again, upload the App.js file in src/components/App.js
directory by downloading the file from GitHub.
Configure React Entry Point
In src
folder, upload index.js
file from GitHub.
Now, let's go through the other components of development to assist the process of sending and receiving emails through the decentralized mailing system on the Rootstock testnet.
Make sure to download the files SendMessage.js , Inbox.js, web3.js
, and index.css
from the GitHub repository, and place them in the respective directories as shown in the project’s directory Figure 1.
We are going to test and deploy our contract on RSK Testnet by using Truffle. Please follow the instructions stepwise.
Compile The Contract
Run the following command in the terminal.
truffle compile
You can see the following messages in the terminal if everything goes correctly.
Migrate (Deploy) Your Contracts
Then, run the following command to deploy the contract:
truffle migrate --network testnet
You will see something like this with the contract address.
Please copy the contract address and update .env
file in Step ** by replacing your_rsk_testnet_contract_address
by your actual contract address and update the file. Please also note, after a successful contract migration, you’ll find the DecentralizedMail.json
file in \decentralized-mail\build
directory. Copy the JSON file, and paste it into decentralized-mail/src/utils/
Please make sure you have updated .env
files with all the details correctly.
Run the following command in the terminal.
npm run build
After that, install the MetaMask extension on your browser and switch the RSK testnet by adding the following details:
Go to Select Network>Add network>Custom RPC, and then enter the following information.
RSK Testnet
https://public-node.testnet.rsk.co
31
tRBTC
Now, run the following command in the terminal.
npm start
You should be redirected to the browser. The react app will invoke the MetaMask. Please approve the call. The main interface should have appeared as follows. Try sending a message to an address but please note that you have some tRBTC in your wallet to cover the gas in the RSK testnet. You can get free tRBTC from the RSK faucet.
You can write the texts and attach files using the Choose File option. I have uploaded a photo of Nikola Tesla. Make sure to confirm the MetaMask’s call after sending the email to the recipient’s address. Wait for the transaction to be confirmed from the blockchain. Once the process is completed, you will see a message sent notice.
Let’s check whether the recipient address has my sent message in the inbox or not.
Perfect! You can see the sent message with the attached file of Nikola Tesla. You can reply to the inbox or also delete the inbox. Once you reply to the message, it will appear in the inbox section of the original sender.
After replying to the message, it is appeared in the inbox.
Let’s see how about the message delete feature. Once you click the delete icon, the React app invokes the MetaMask to confirm the call. Once the transaction is completed, it will be flagged and disappear from the inbox.
In this way, we have successfully developed a blockchain-based decentralized-mail software on the RSK testnet which can also be deployed on the RSK mainnet just by modifying the codes.
Please note that this is the tutorial for developing a decentralized messaging platform with the basic functionalities. Security has been the priority since the beginning but I caution you to check everything at your own risk if you’re rushing away to develop your production-ready software.
For detailed code, please kindly refer to the GitHub repository: Decentralized Mail GitHub.
1.EIP-1559 Not Supported
Error: Invalid value given "Eip1559NotSupportedError
".
Error: Network doesn't support eip-1559.
Solution: Make sure that the gasPrice
is specified in the transaction options. You should also update the frontend code to handle gas prices correctly.
You should install the MetaMask extension in your browser and configure the RSK testnet in your Wallet’s network configuration. Carefully inspect the code that window.ethereum.enable()
is correctly used to prompt Metamask for account access.
Verify that messages are correctly pushed to the recipient’s inbox. If not, check the smart contract logic for message storage and retrieval.
This tutorial assist you in creating a blockchain-based decentralized messaging platform with the basic features and functionalities. With this messaging software, you can send, receive, delete messages, and reply to the messages through the blockchain. It also supports the file-sharing features by using IPFS Pinata. You can modify the code to add more features with attractive UI to meet the demands of your users.
Decentralized blockchain is backing the message-sending and receiving process with enhanced encryption which means a decentralized email exchange platform can be the best choice in the future.
It assists in protecting content from notorious phishing tactics from attackers. In addition, this sort of messaging platform is secured from the notorious single point of failure problem and other attacks from cybercriminals because the address doesn’t consist of distinguishable clues to guess the user’s personal information.