Building the front end for a webmail application

Updated February 10, 2019

Let's build our webmail UI using React and Redux. We'll create an inbox and message composer. Be sure to set up your backend first.

1. Create an Inbox

1.1 Using sample data in Redux

Let's start by creating a Mail reducer to initialize our webmail app with email data.

In Solution Explorer, right-click on store under ClientApp/src/store, then go to Add, click on New Item, and add a new JavaScript file called Mail.js.

Add the following content to Mail.js:

const initialState = {
    mail: [
        { id: 1, sender: "steve@cratemail.org", recipient: "jane@cratemail.org", mailSubject: "First meeting for Cratemail group", bodyHtml: "thanks for the update", mailDate: "06/06/18 10:15am" },
        { id: 2, sender: "bill@cratemail.org", recipient: "carl@cratemail.org", mailSubject: "First meeting for Cratemail group", bodyHtml: "will forward the info", mailDate: "06/06/18 11:15pm" },
        { id: 3, sender: "jane@cratemail.org", recipient: "admin@cratemail.org", mailSubject: "Second Meeting for Cratemail group", bodyHtml: "need more details", mailDate: "06/07/18 6:55pm" },
        { id: 4, sender: "steve@cratemail.org", recipient: "admin@cratemail.org", mailSubject: "RE: First for Cratemail group", bodyHtml: "please review when you get a chance", mailDate: "06/07/18 8:30pm" },
        { id: 5, sender: "admin@cratemail.org", recipient: "steve@cratemail.org", mailSubject: "RE: First Meeting for Cratemail group", bodyHtml: "appreciate the suggestion", mailDate: "06/09/18 5:15am" }
    ]
};

export const reducer = (state = initialState, action) => {
    return state;
};

Plug in the Mail reducer to ClientApp/src/store/configureStore.js:

import * as Mail from './Mail';

export default function configureStore(history, initialState) {
  const reducers = {
    mail: Mail.reducer
  };
	...
}

1.2 Inbox design

Let's use a 2-column grid layout for our inbox. One column will contain a list of selectable emails. Another column will display the contents of an email. In Solution Explorer, go to ClientApp/src/components, right-click on components, then go to Add, click on New Item, and add a new JavaScript file called Inbox.js with the following content:

import React from 'react';
import { connect } from 'react-redux';
import { Container, Row, Col, Table } from 'reactstrap';

class Inbox extends React.Component {
    state = {
        selectedMailID: 0
    }
    selectMail = (selectedMailID) => {
        this.setState({ selectedMailID });
    }
    render() {
        let selectedMail = this.props.mail.filter(mail => {
            return mail.id == this.state.selectedMailID
        });
        let selection = "Nothing selected";
        if (selectedMail.length) {
            selection = <div>
                <h2>{selectedMail[0].mailSubject}</h2>
                <p>{selectedMail[0].mailDate}</p>
                <div>{selectedMail[0].bodyHtml}</div>
             </div>;
        }
        return (
        <div>
        <Container>
            <Row>
                <Col>
                    <Table hover>
                        <thead>
                            <tr>
                                <th>From</th>
                                <th>Date</th>
                            </tr>
                        </thead>
                        <tbody>
                            {this.props.mail.map((mail, i) => 
                             <tr key={i} style={{ cursor: "pointer" }} onClick={() => this.selectMail(mail.id)}>
                                    <td>
                                        {mail.sender}
                                        <br />
                                        {mail.mailSubject}
                                    </td>
                                    <td>{mail.mailDate}</td>
                             </tr>
                             )}
                        </tbody>
                    </Table>
                </Col>
                <Col>
                    {selection}
                </Col>
            </Row>
        </Container>
        </div>)
    }
}

export default connect(state => state.mail)(Inbox);

Go to ClientApp/src/App.js, and plug the Inbox component into the main route:

import React from 'react';
import { Route } from 'react-router';
import Layout from './components/Layout';
import Inbox from './components/Inbox';

export default () => (
  <Layout>
    <Route exact path='/' component={Inbox} />
  </Layout>
);

To test the inbox, start debugging in Visual Studio by pressing F5. You should be able to see the list of emails we placed in the reducer.

1.3 Connecting to the database

Instead of using hardcoded data, we'll start reading emails captured in our database. Open Controller/MailController.cs and add the following references:

using System.Collections.Generic;
using System.Data;

Add the following GET handler to MailController.cs to retrieve all emails in MariaDB:

[HttpGet]
public IEnumerable<Mail> Get()
{
    string selectQuery = "SELECT * FROM Mail";
    MySqlConnection sqlConnection = new MySqlConnection(Resources.connString);
    sqlConnection.Open();
    MySqlCommand sqlCommand = new MySqlCommand(selectQuery, sqlConnection);
    MySqlDataAdapter adapter = new MySqlDataAdapter(sqlCommand);
    DataTable dt = new DataTable();
    adapter.Fill(dt);
    List<Mail> mails = new List<Mail>();
    foreach (DataRow row in dt.Rows)
    {
        mails.Add(new Mail
        {
            ID = Convert.ToInt32(row["ID"]),
            Sender = row["Sender"].ToString(),
            Recipient = row["Recipient"].ToString(),
            MailSubject = row["MailSubject"].ToString(),
            BodyHtml = row["BodyHtml"].ToString(),
            BodyPlain = row["BodyPlain"].ToString(),
            MailDate = row["MailDate"].ToString()
        });
    }
    sqlConnection.Close();
    return mails;
}

If you haven't received emails yet, you can run the following SQL query in HeidiSQL to seed your database:

INSERT INTO Mail(ID, Sender, Recipient, MailSubject, BodyHtml, BodyPlain, MailDate) VALUES(1'steve@cratemail.org''jane@cratemail.org''First meeting for Cratemail group''thanks for the update''thanks for the update''06/06/18 10:15am');
INSERT INTO Mail(ID, Sender, Recipient, MailSubject, BodyHtml, BodyPlain, MailDate) VALUES(2'bill@cratemail.org''carl@cratemail.org''First meeting for Cratemail group''will forward the info''will forward the info''06/06/18 11:15pm');
INSERT INTO Mail(ID, Sender, Recipient, MailSubject, BodyHtml, BodyPlain, MailDate) VALUES(3'jane@cratemail.org''admin@cratemail.org''Second meeting for Cratemail group''need more details''need more details''06/07/18 6:55pm');

Let's change ClientApp/src/store/Mail.js to retrieve email data from our API in the action creator. Modify Mail.js with the following content:

const requestMailType = 'REQUEST_MAIL';
const receiveMailType = 'RECEIVE_MAIL';
const initialState = { mail: [] }

export const actionCreators = {
    requestMail: () => async dispatch => {
        dispatch({ type: requestMailType });
        const url = `api/Mail`;
        const response = await fetch(url);
        const mail = await response.json();
        dispatch({ type: receiveMailType, mail });
    }
};
	
export const reducer = (state = initialState, action) => {
    if (action.type === requestMailType) {
        return {
            ...state,
            isLoading: true
        };
    }
    if (action.type === receiveMailType) {
        return {
            ...state,
            mail: action.mail,
            isLoading: false
        };
    }
    return state;
};

Now, we'll hook our action creator into our Inbox component. Add the following references to ClientApp/src/components/Inbox.js:

import { bindActionCreators } from 'redux';
import { actionCreators } from '../store/Mail';

In the last line of Inbox.js, add the dispatch parameter to the connect() call, to bind our action creator to props:

export default connect(state => state.mail,
    dispatch => bindActionCreators(actionCreators, dispatch)
)(Inbox);

Finally, add a componentDidMount() handler to the Inbox class, this will be called once the UI has finished rendering. Inside, we'll call the action creator to load our mail data:

componentWillMount() {
    this.props.requestMail();
}

Test your inbox once again to ensure your database connection works. You should be able to select emails stored in MariaDB.

2. Message Composer

2.1 Composer design

To send emails, we'll create a message composer. In Solution Explorer, right-click on components under ClientApp/src/components, then go to Add, click on New Item, and add a new JavaScript file called Compose.js with the following content:

import React from 'react';
import { Button, Form, FormGroup, Label, Input } from 'reactstrap';

export default class Compose extends React.Component {
    render() {
        return <div>
            <Form>
                <FormGroup>
                    <Label for="recipient">To</Label>
                    <Input type="text" name="recipient" id="recipient" />
                </FormGroup>
                <FormGroup>
                    <Label for="subject">Subject</Label>
                    <Input type="text" name="subject" id="subject" />
                </FormGroup>
                <FormGroup>
                    <Label for="message">Message</Label>
                    <Input type="textarea" name="message" id="message" />
                </FormGroup>
                <Button>Send</Button>
            </Form>
        </div>
    }
}

2.2 Sending API

We'll send messages to Mailgun's API using an HTTP client. Add the following references to Controller/MailController.cs:

using RestSharp;
using RestSharp.Authenticators;

Add the following PUT method to MailController.cs to send emails through our API:

[HttpPut]
public bool Put([FromBody]Mail msg)
{
    try
    {
        string mailgunAPI = "https://api.mailgun.net/v3";
        string mailgunDomain = "cratemail.org";
        RestClient client = new RestClient();
        client.BaseUrl = new Uri(mailgunAPI);
        client.Authenticator = new HttpBasicAuthenticator("api"Resources.mailgunKey);
        RestRequest request = new RestRequest();
        request.AddParameter("domain", mailgunDomain, ParameterType.UrlSegment);
        request.Resource = "{domain}/messages";
        request.AddParameter("from", msg.Sender);
        request.AddParameter("to", msg.Recipient);
        request.AddParameter("subject", msg.MailSubject);
        request.AddParameter("text", msg.BodyHtml);
        request.Method = Method.POST;
        IRestResponse rr = client.Execute(request);
        return rr.IsSuccessful;
    }
    catch
    {
        return false;
    }
}

2.3 Finishing up

Let's add a link to our message composer in the navigation menu. Modify ClientApp/src/components/NavMenu.js with the following content:

import React from 'react';
import { Collapse, Container, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';
import './NavMenu.css';

export default class NavMenu extends React.Component {
  constructor (props) {
    super(props);

    this.toggle = this.toggle.bind(this);
    this.state = {
      isOpen: false
    };
  }
  toggle () {
    this.setState({
      isOpen: !this.state.isOpen
    });
  }
  render () {
    return (
      <header>
         <Navbar className="navbar-expand-sm navbar-toggleable-sm border-bottom box-shadow mb-3" light >
          <Container>
            <NavbarBrand tag={Link} to="/">Cratemail</NavbarBrand>
            <NavbarToggler onClick={this.toggle} className="mr-2" />
            <Collapse className="d-sm-inline-flex flex-sm-row-reverse" isOpen={this.state.isOpen} navbar>
              <ul className="navbar-nav flex-grow">
                <NavItem>
                  <NavLink tag={Link} className="text-dark" to="/">Home</NavLink>
                </NavItem>
                <NavItem>
                  <NavLink tag={Link} className="text-dark" to="/compose">Compose</NavLink>
                </NavItem>
              </ul>
            </Collapse>
          </Container>
        </Navbar>
      </header>
    );
  }
}

Go to ClientApp/src/App.js, and add a route for the message composer.

import React from 'react';
import { Route } from 'react-router';
import Layout from './components/Layout';
import Inbox from './components/Inbox';
import Compose from './components/Compose';

export default () => (
    <Layout>
        <Route exact path='/' component={Inbox} />
        <Route exact path='/compose' component={Compose} />
    </Layout>
);

Test your email composer by pressing F5 in Visual Studio. You should be able to send emails through your Mailgun account.

Feel free to continue building awesome new features into this webmail client. You can add search and sorting to the email list panel, add a login page, and more. Comment below with your suggestions!

Table of Contents

Recent Posts