Ever get an invitation to a hackathon, a party or (yikes) a job interview, only to find it weeks or months later because it went to the wrong email or voicemail box? If you rely on an old email or cellphone number, this could happen to you! It’s critical to keep your contacts up to date so you don’t miss opportunities. How about we use Nylas to build our interface for managing contact info? We’re going to make a Contact Manager to update our contacts.
To build our Contact Manager, we’re going to use Reflex, a Python-based Web-UI Framework.
If you already have the Nylas Python SDK installed and your Python environment configured, skip to the next section.
If not, read the post How to Send Emails with the Nylas Python SDK, where I cover the basic steps to set up your environment.
When you run your Reflex Contact Manager, it will display five contacts that belong to a group that you specify. Of course, with Nylas you can display all contacts without worrying if they belong to a group or not. It’s your choice:

Next to each contact, we’ll add an update button that shows all the contact detail fields.

To update a contact, edit any of the fields and click Submit to save the change.

After that, the contact shows the change when you view it again.

One important limitation to mention – I haven’t found a way to enable notifications when the contact gets updated, but we can see that it was because the updated job title shows up in the contact card.
Besides the Nylas and Dotenv packages, we need to install the Reflex and the Pandas packages:
$ pip3 install reflex $ pip3 install pandas
That’s it! Nothing too complex or complicated.
And now it’s coding time. Go to your terminal window and type the following:
$ mkdir reflex_contacts_manager $ cd reflex_contacts_manager $ pc init
This creates some files and libraries, but importantly it creates a new folder called reflex_contacts_manager. Inside this folder, you’ll find the file reflex_contacts_mananer.py, which we need to update. The code in this file is big, so we’ll split our discussion into more digestible sections:
In this section, we load all libraries and fetch the list of contacts:
# Import your dependencies
from rxconfig import config
import reflex as rx
from dotenv import load_dotenv
import os
from nylas import APIClient # type: ignore
import pandas as pd
# Load the app configuration
filename = f"{config.app_name}/{config.app_name}.py"
# Load your env variables
load_dotenv()
# Initialize an instance of the Nylas SDK using the client credentials
nylas = APIClient(
os.environ.get("CLIENT_ID"),
os.environ.get("CLIENT_SECRET"),
os.environ.get("ACCESS_TOKEN"),
)
# Get a list of contacts, from a particular group
# limiting to 5 contacts only
def get_contacts():
ids = []
full_names = []
# Call the contacts endpoint
contacts = nylas.contacts.where(source = 'address_book', limit = 5, group = "517v55haghlcvnuu7lcm4f7k8")
# Loop the contacts
for contact in contacts:
# Append them to lists
ids.append(contact.id)
full_names.append(contact.given_name + " " + contact.surname)
# Create a dictionary
data = {'ids' : ids, 'full_names' : full_names}
# Create a Pandas Dataframe
df = pd.DataFrame(data)
return df
This section handles all the application variables, gets the details for each contact, binds the changes on each field and handles the contact updates:
# This class will handle the list of contacts
class Contact(rx.Model, table = True):
ids : str
full_names : str
def __init__(self, ids, full_names):
self.ids = ids
self.full_names = full_names
# This handles all the variables in our application
class State(rx.State):
form_data: dict = {}
contact_id : str = ""
contact_name : str = ""
contact_surname : str = ""
contact_company : str = ""
contact_jobtitle : str = ""
contact_email : str = ""
contact_profilepic : str = ""
contact_emailtype : str = ""
contact_country : str = ""
contact_street : str = ""
contact_city : str = ""
contact_state : str = ""
contact_postal_code : str = ""
contact_phone_type : str = ""
contact_address_type : str = ""
contact_phone_number : str = ""
# Fetch all contacts
_contacts = get_contacts()
# Create a contacts list
contacts:list[Contact] = []
for i in range(0, len(_contacts)):
# Fill up the list
contacts.append(Contact(_contacts.iloc[i]['ids'], _contacts.iloc[i]['full_names']))
# Get details for each selected contact
def get_contact_details(self, contact_id : str):
contact = nylas.contacts.get(contact_id)
file_name = f'{contact_id}.png'
# Download the contact picture
picture = contact.get_picture()
profile_image = open(f'assets/{file_name}', 'wb')
profile_image.write(picture.read())
profile_image.close()
# Store contact details on local variables
self.contact_id = contact_id
self.contact_profilepic = file_name
self.contact_name = contact.given_name
self.contact_surname = contact.surname
self.contact_company = contact.company_name
self.contact_jobtitle = contact.job_title if contact.job_title != None else "Empty"
self.contact_email = list(contact.emails.values())[0][0]
try:
self.contact_phone_number = list(contact.phone_numbers.values())[0][0]
self.contact_phone_type = list(contact.phone_numbers)[0]
except Exception as e:
print(f'{e}')
self.contact_phone_number = "123"
self.contact_phone_type = "Mobile"
try:
self.contact_address_type = list(contact.physical_addresses.values())[0][0]["type"]
self.contact_country = list(contact.physical_addresses.values())[0][0]["country"]
self.contact_street = list(contact.physical_addresses.values())[0][0]["street_address"]
self.contact_city = list(contact.physical_addresses.values())[0][0]["city"]
self.contact_state = list(contact.physical_addresses.values())[0][0]["state"]
self.contact_postal_code = list(contact.physical_addresses.values())[0][0]["postal_code"]
except Exception as e:
print(f'{e}')
self.contact_address_type = ""
self.contact_country = ""
self.contact_street = ""
self.contact_city = ""
self.contact_state = ""
self.contact_postal_code = ""
# These functions help us bind
# the updated value with the UI
def set_name(self, name: str):
self.contact_name = name
def set_surname(self, surname: str):
self.contact_surname = surname
def set_company(self, company: str):
self.contact_company = company
def set_jobtitle(self, jobtitle: str):
self.contact_jobtitle = jobtitle
def set_email(self, email: str):
self.contact_email = email
def set_country(self, country: str):
self.contact_country = country
def set_street(self, street: str):
self.contact_street = street
def set_city(self, city: str):
self.contact_city = city
def set_state(self, state: str):
self.contact_state = state
def set_postal_code(self, postal_code: str):
self.contact_postal_code = postal_code
def set_phone_number(self, phone_number: str):
self.contact_phone_number = phone_number
# When we press Submit
def handle_submit(self, form_data: dict):
# form_data is a dictionary that contains
# all the form variables
self.form_data = form_data
# Update values if any
self.form_data['id'] = self.contact_id
self.form_data['contact_name'] = self.contact_name
self.form_data['surname'] = self.contact_surname
self.form_data['company_name'] = self.contact_company
self.form_data['job_title'] = self.contact_jobtitle
self.form_data['email'] = self.contact_email
self.form_data['email_type'] = self.contact_emailtype
self.form_data['phone_number'] = self.contact_phone_number
self.form_data['country'] = self.contact_country
self.form_data['street_address'] = self.contact_street
self.form_data['city'] = self.contact_city
self.form_data['state'] = self.contact_state
self.form_data['postal_code'] = self.contact_postal_code
self.form_data['phone_type'] = self.contact_phone_type
self.form_data['address_type'] = self.contact_address_type
contact = nylas.contacts.get(self.form_data['id'])
contact.given_name = self.form_data['contact_name']
contact.surname = self.form_data['surname']
contact.company_name = self.form_data['company_name']
contact.job_title = self.form_data['job_title']
contact.emails[self.form_data['email_type']] = [self.form_data['email']]
contact.phone_numbers[self.form_data['phone_type']] = [self.form_data['phone_number']]
if self.form_data['street_address'] is not None or self.form_data['street_address'] != '':
contact.physical_addresses['Work'] = [{
'format': 'structured',
'city': self.form_data['city'],
'country': self.form_data['country'],
'state': self.form_data['state'],
'postal_code': self.form_data['postal_code'],
'type': self.form_data['address_type'],
'street_address': self.form_data['street_address']}]
try:
# Try to save the contact
contact.save()
except Exception as e:
print(f'{e}')
The last section belongs to the UI of our application:
# This is our UI
def index() -> rx.Component:
return rx.center(
rx.vstack(
rx.vstack(
rx.hstack(
rx.heading("Contacts"),
),
rx.flex(
rx.box(
rx.table_container(
rx.table(
rx.thead(
rx.tr(
rx.th("Names"),
)
),
rx.tbody(
rx.foreach(State.contacts, lambda contact:
rx.tr(
rx.td(contact.full_names),
rx.td(
# The update button next to each contact
rx.button(
"update",
on_click = lambda: State.get_contact_details(contact.ids),
bg = "red",
color = "white",
)
)
)
)
)
)
),
bg="#F7FAFC ",
border="1px solid #ddd",
border_radius="25px",
),
# This form holds all the contact information
# that we can update
rx.form(
rx.center(
rx.image(src=State.contact_profilepic, width="100px", height="auto"),
),
rx.hstack(
rx.text("First Name:", as_ = "b"),
rx.input(
placeholder = State.contact_name,
on_change = State.set_name,
id = "contact_name",
),
),
rx.hstack(
rx.text("Last Name:", as_ = "b"),
rx.input(
placeholder = State.contact_surname,
on_change = State.set_surname,
id = "surname",
),
),
rx.hstack(
rx.text("Company Name:", as_ = "b"),
rx.input(
placeholder = State.contact_company,
on_change = State.set_company,
id = "company_name",
),
),
rx.hstack(
rx.text("Job Title:", as_ = "b"),
rx.input(
placeholder = State.contact_jobtitle,
on_change = State.set_jobtitle,
id = "job_title",
),
),
rx.hstack(
rx.text("Email:", as_ = "b"),
rx.input(
placeholder = State.contact_email,
on_change = State.set_email,
id = "email",
),
),
rx.hstack(
rx.text("Phone Number:", as_ = "b"),
rx.input(
placeholder = State.contact_phone_number,
on_change = State.set_phone_number,
id = "phone_number",
),
),
rx.hstack(
rx.text("Country:", as_ = "b"),
rx.input(
placeholder = State.contact_country,
on_change = State.set_country,
id = "country",
),
),
rx.hstack(
rx.text("Address:", as_ = "b"),
rx.input(
placeholder = State.contact_street,
on_change = State.set_street,
id = "street_address",
),
),
rx.hstack(
rx.text("City:", as_ = "b"),
rx.input(
placeholder = State.contact_city,
on_change = State.set_city,
id = "city",
),
),
rx.hstack(
rx.text("State:", as_ = "b"),
rx.input(
placeholder = State.contact_state,
on_change = State.set_state,
id = "state",
),
),
rx.hstack(
rx.text("Postal Code:", as_ = "b"),
rx.input(
placeholder = State.contact_postal_code,
on_change = State.set_postal_code,
id = "postal_code",
),
),
rx.center(
rx.hstack(
rx.button(
"Submit",
type_="submit",
bg = "red",
color = "white",
),
),
),
on_submit=State.handle_submit,
),
bg="#F7FAFC ",
border="1px solid #ddd",
border_radius="25px",
)
)
)
)
# Add state and page to the app.
app = rx.App(state=State)
app.add_page(index)
app.compile()
To run our application, just type the following in your terminal window:
$ pc run

Our application runs on port 3000 of localhost, so open up your favourite browser and type:
http://localhost:3000/
We will be ready to use manage our contacts using Reflex.
If you want to learn more about our Contacts APIs, see our documentation Contacts API Overview.
You can sign up Nylas for free and start building!
Don’t miss the action, join our LiveStream Coding with Nylas!