Use casesWorkflow
AI Property Management Email Agent
Build an AI agent that handles tenant and landlord communication over email. Maintenance requests, lease inquiries, move-in coordination — your agent manages it all through a real mailbox.
The problem
Property managers drown in email. Tenants report maintenance issues at all hours. Landlords want status updates. Vendors need scheduling. A single property manager handling 50+ units can receive hundreds of emails per week, and every delayed response risks tenant dissatisfaction, regulatory complaints, or property damage from unreported issues.
Most property management software offers portals and apps, but tenants still default to email. They reply to old threads, CC roommates, forward photos of damage. The communication channel tenants actually use is email — and no portal changes that behavior.
Manual triage is slow and error-prone. Urgent requests (burst pipe, lockout) get buried under non-urgent ones (request to repaint a wall). Without immediate acknowledgment, tenants escalate — calling, texting, or filing complaints.
How an AI agent solves this
An AI property management agent operates a real email address — like maintenance@greenfield-properties.com — and handles every inbound message autonomously. It reads tenant emails, classifies the request type and urgency, creates work orders, schedules vendors, and replies within seconds.
- Instantly acknowledges every maintenance request with an estimated timeline
- Classifies urgency — a burst pipe gets escalated immediately, a cosmetic issue gets queued
- Creates work orders in your property management system automatically
- Follows up with tenants when work is scheduled or completed
- Sends landlords weekly digests of maintenance activity per property
- Handles lease renewal inquiries, move-in/move-out checklists, and common tenant questions
How it works with Robotomail
Robotomail gives your agent a real email address with full send and receive capability. No SMTP configuration, no mail server management. Provision a mailbox, register a webhook, and your agent is operational.
1. Provision a maintenance mailbox
curl -X POST https://api.robotomail.com/v1/mailboxes \
-H "Authorization: Bearer rm_live_key" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON'
{
"address": "maintenance",
"domainId": "dom_greenfield",
"displayName": "Greenfield Properties"
}
JSON2. Register a webhook for inbound emails
When a tenant sends an email to your maintenance address, Robotomail delivers the full message to your webhook endpoint in real time.
curl -X POST https://api.robotomail.com/v1/webhooks \
-H "Authorization: Bearer rm_live_key" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON'
{
"url": "https://your-app.com/api/property-inbox",
"events": [
"message.received"
],
"mailboxId": "mbx_maintenance_01"
}
JSON3. Receive and process tenant emails
Your webhook receives structured JSON with the full message content, sender, subject, and thread ID for maintaining conversation context.
{
"event": "message.received",
"timestamp": "2026-03-22T14:32:00Z",
"data": {
"message_id": "11111111-1111-4111-8111-111111111111",
"mailbox_id": "22222222-2222-4222-8222-222222222222",
"mailbox_address": "maintenance@greenfield-properties.com",
"from": "tenant-4821@gmail.com",
"to": [
"maintenance@greenfield-properties.com"
],
"cc": [],
"subject": "Leaking faucet in Unit 3B",
"body_text": "Hi, the kitchen faucet in unit 3B has been dripping since yesterday. Can someone take a look?",
"thread_id": "33333333-3333-4333-8333-333333333333",
"received_at": "2026-03-22T14:32:00Z",
"attachments": []
}
}4. Agent classifies, creates work orders, and replies
async function getReplyMessageId(mailboxId, messageId) {
const response = await fetch(
`https://api.robotomail.com/v1/mailboxes/${encodeURIComponent(mailboxId)}/messages/${encodeURIComponent(messageId)}`,
{ headers: { Authorization: `Bearer ${process.env.ROBOTOMAIL_API_KEY}` } },
);
if (!response.ok) throw new Error(`Fetch message failed: ${response.status}`);
const { message } = await response.json();
if (!message?.messageId) throw new Error("Original email has no Message-ID");
return message.messageId;
}
async function handleMaintenanceEmail(payload) {
const { from, subject, body_text: text, thread_id: threadId, mailbox_id: mailboxId, message_id: messageId } = payload.data;
// Classify the request with your LLM
const classification = await classifyRequest(text);
// => { type: "maintenance", urgency: "medium", category: "plumbing" }
// Look up tenant and unit from your property database
const tenant = await db.tenants.findByEmail(from);
const unit = tenant?.unit; // "3B"
// Create a work order in your system
const workOrder = await db.workOrders.create({
unitId: unit.id,
category: classification.category,
urgency: classification.urgency,
description: text,
threadId,
});
// Generate and send a reply via Robotomail
const reply = await generateReply({
tenantName: tenant.name,
category: classification.category,
workOrderId: workOrder.id,
});
await fetch(`https://api.robotomail.com/v1/mailboxes/${mailboxId}/messages`, {
method: "POST",
headers: {
"Authorization": "Bearer rm_live_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
to: [from],
subject: `Re: ${subject}`,
bodyText: reply,
inReplyTo: await getReplyMessageId(mailboxId, messageId),
}),
});
}5. Send replies and follow-ups
curl -X POST https://api.robotomail.com/v1/mailboxes/{mailbox_id}/messages \
-H "Authorization: Bearer rm_live_key" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON'
{
"to": [
"tenant-4821@gmail.com"
],
"subject": "Re: Leaking faucet in Unit 3B",
"bodyText": "Hi Sarah,\n\nThanks for reporting the faucet issue. We've scheduled a plumber for tomorrow between 9-11am. Please confirm if someone will be home.\n\nGreenfield Properties"
}
JSONKey benefits
- Instant response times. Tenants get acknowledgment within seconds, not hours. Urgent issues are escalated immediately — before damage compounds.
- Threaded conversations. Robotomail tracks threads automatically. Your agent maintains context across multi-message exchanges about the same repair, lease question, or inspection.
- Custom domain identity. Emails come from your property management domain, not a generic SaaS address. Tenants reply naturally — no portals, no apps to install.
- Scale without hiring. One agent handles communication for hundreds of units across multiple properties. Add new properties by provisioning new mailboxes — no additional staff needed.
- Full audit trail. Every email is stored and accessible via the API. Dispute resolution, compliance reporting, and maintenance history are all searchable.
Read the documentation to get started, or learn more about the platform in our launch post. Ready to build? Get started — paid plans come with a 30-day money-back guarantee.
Connect the workflow to a real reply
The webhook's data.message_id is a database ID used to retrieve or deduplicate a message. Fetch it with the GET message endpoint, then use the response's message.messageId (the RFC Message-ID) as inReplyTo. Keep data.thread_id for your application's conversation state. Do not use a database ID or thread ID as a reply header. Stop and investigate if the fetched message has no Message-ID.
Examples use illustrative IDs. Replace them with IDs from your account; never construct an email Message-ID from a database ID. Exchange mail only between your agent and your verified email address. Upgrade to email anyone. See plans for higher limits.
Verify the webhook signature over the raw body, persist the event and deduplicate on the database message ID before acknowledging it. Run your application logic from a durable queue. The database, calendar, CRM and model functions shown above belong to your application.
This shell example fetches an incoming email and builds all required send fields. Set ROBOTOMAIL_API_KEY, ROBOTOMAIL_MAILBOX_ID and ROBOTOMAIL_MESSAGE_ID in your environment. Apply your recipient and approval rules before sending.
# Requires jq. Use mailbox_id and message_id from the verified webhook.
original=$(curl --fail --silent --show-error \
"https://api.robotomail.com/v1/mailboxes/$ROBOTOMAIL_MAILBOX_ID/messages/$ROBOTOMAIL_MESSAGE_ID" \
-H "Authorization: Bearer $ROBOTOMAIL_API_KEY") || exit 1
payload=$(printf '%s' "$original" | jq -e --arg body "Thanks, I received your message." '
.message | if (.messageId | type == "string" and length > 0) then
{to: [.fromAddress], subject: ("Re: " + .subject), bodyText: $body, inReplyTo: .messageId}
else error("Original email has no Message-ID") end
') || exit 1
curl --fail --silent --show-error \
"https://api.robotomail.com/v1/mailboxes/$ROBOTOMAIL_MAILBOX_ID/messages" \
-H "Authorization: Bearer $ROBOTOMAIL_API_KEY" \
-H "Content-Type: application/json" \
--data-binary "$payload"
Read receive and reply and webhook verification for the full setup.