Skip to main content

Road Maintenance Connector

The Road Maintenance connector watches a mailbox and turns the road works announced in those emails into roadblocks in Yggio.

Road works are normally announced by email, as an approved traffic arrangement plan. The decision has already been taken; the email carries the result, and it is written for a person to read: free text, an attachment, a street name, a date range. The connector polls the mailbox, hands each message to an LLM to read, and creates the roadblock from what the LLM extracts. Nobody has to retype it.

For what the roadblocks are then used for, including routing emergency and municipal vehicles around them, see Road Obstructions.

How it works

  1. The connector polls a mailbox over IMAP at an interval you set.
  2. A message is only processed if its sender is on the allowed senders list. Anything else is ignored, so an arbitrary email cannot close a road.
  3. The message is passed to an AI agent, a separate AI connector acting as a parser, which reads it and returns the structured details.
  4. A roadblock is created in Yggio from the result.
  5. Navigation in Yggio then routes around it, finding the fastest way past the roadblock for a car or for a truck.

Before you start

You need an AI connector of type Agent already created, because the Road Maintenance connector selects one when you set it up. An MCP connector will not appear in the list: MCP powers the chat assistant, while Agent runs a single LLM call with a fixed system prompt, which is what parsing an email needs. See AI Connector.

Give that agent a system prompt describing what to extract from the email, and set its response format to JSON so the result can be used directly.

You also need a mailbox the connector can read, and the addresses that are allowed to write to it.

Creating the Connector

  1. Navigate to Connectors → New Connector → Road Maintenance.
  2. Select the retention policy and flow, then continue.
  3. Fill in the connector details:

Mailbox

FieldDescription
HostIMAP server hostname, for example imap.example.com
PortIMAP port, normally 993
UsernameThe mailbox account
PasswordThe mailbox password
TLSWhether to connect over TLS. Normally true
Poll intervalHow often to check for new mail, in milliseconds. 60000 is one minute

Allowed senders

A list of email addresses whose messages will be processed. Add one entry per address.

Keep it as tight as the operation allows. This list is the access control on the whole feature: an address on it can cause a road to be marked closed, so it should contain the contractors and case handlers who are entitled to announce road works, and nobody else.

AI agent

Select the AI connector that will parse the messages. Only connectors of type Agent are listed.

The agent is stored with a role of parser and a purpose, a short description of what it is there to do, which is useful when more than one agent is involved later.

  1. Confirm the details and create the connector.

Example: the parser agent

This is the agent configuration from the project the connector was built for, with credentials and addresses replaced. Treat it as a working starting point rather than a specification: the prompt is what decides how well the emails are read, and it should be tuned to the documents your senders actually write.

The system prompt

Set this as the system prompt on the AI connector, with response format JSON Object and a low temperature, 0.3 here, since the job is extraction rather than composition.

You are an emergency service operator helping fire brigades navigate around road blocks in Sweden. Analyze the road closure information and provide location details and structured query in a format suitable for geocoding. Here is an example for "freeform_query" converted into URI component: encodeURIComponent("Klosterbrunnsgatan, Visby, 111 57, Sweden") "Klosterbrunnsgatan%2C%20Visby%2C%20111%2057%2C%20Sweden". The timings mentioned in the email are in the Europe/Stockholm timezone. Convert these timings to UTC (Coordinated Universal Time) before returning the response. If only dates are specified (e.g., 'Monday 2024.10.28 - Friday 2024.11.22'), interpret them as local dates in the Europe/Stockholm timezone, starting at midnight (00:00) on the start date and ending at 23:59:59 on the end date. Ensure that the 'canPass' attribute in 'emergencyAccess' is set to 'false' if emergency vehicles cannot pass through the road block. Set it to 'true' only under the following conditions: (1) there is explicit information allowing emergency vehicles to pass, or (2) the road block's end time (`endTimeAt`) has already passed, meaning the road block is no longer active (`active`=false). Return a JSON object with a "roadBlocks" using this format: { "roadBlocks": { "connectorId": "TBU","userId": "TBU", "roadLocation": [{ "name": "TBU", "tag": "TBU", "active": boolean, "startTimeAt": "YYYY-MM-DDTHH:mm:ssZ", "endTimeAt": "YYYY-MM-DDTHH:mm:ssZ", "reportedAt": "YYYY-MM-DDTHH:mm:ssZ" (copy of startTimeAt), "roadBlockage": string, "lnglat": { "type": "Point", "coordinates": [number, number] ([lon, lat] points) }, "roadName": string, "roadSection": string, "city": string, "restrictionType": string, "emergencyAccess": { "canPass": boolean, "instructions": string, "alternativeRoutes": [{ "description": string, "path": { "type": "LineString", "coordinates": [[number, number]] (array of [lon, lat] points) }, "estimatedTime": string (e.g. "5 min"), "restrictions": string[] }], "osmDetails": { "wayIds": string[], "nodeIds": string[], "boundingBox": { "north": number, "south": number, "east": number, "west": number }, "overpassQuery": string } }, "location": { "structured": { "street": string, "city": string, "county": string, "state": string, "country": "Sweden", "postalcode": string, "amenity": string }, "freeform": string }, "freeformQuery": string }], "email": { "subject": string, "sender": string, "receivedAt": "YYYY-MM-DDTHH:mm:ssZ", "text": string } } } Respond with only this JSON object. Do not include any explanatory text, preamble, or markdown code fences; the output must start with { and end with } and be valid JSON.

What the prompt is doing

Five jobs, each there for a reason:

  • Casting the role. It states the operator context up front, which anchors everything after it.
  • Producing a geocodable address. The email names a street; the routing engine needs coordinates. The prompt asks for a freeformQuery in a form a geocoder accepts, with a worked example of the URI encoding.
  • Fixing the time zone. The emails are written in local time and the platform stores UTC. The prompt names the source zone and defines what a bare date range means, midnight to 23:59:59, so an all-day closure is not read as a single instant.
  • Deciding emergency access. canPass defaults to false and may only be true on stated grounds: explicit permission in the email, or a closure whose end time has passed. A parser guessing this wrong is the error that matters most, so the rule is written as a whitelist.
  • Pinning the output shape. The JSON structure is given in full, ending with an instruction to return that object and nothing else.

Why the JSON-only instruction matters

It lets the same prompt run on either provider. OpenAI has a native JSON mode, so there the instruction is belt and braces. Anthropic has no such mode and the JSON is recovered by parsing the model's text: a code fence and surrounding whitespace are tolerated, but any prose around the JSON fails to parse. The instruction is what prevents it.

To switch providers, change the provider and the model id and keep everything else.

Verifying

  1. Send a test message to the mailbox from an address on the allowed senders list.
  2. Wait for one poll interval.
  3. Check that a roadblock device appears in Yggio with the details from the message.
  4. Send a message from an address that is not on the list and confirm nothing is created.

Troubleshooting

Nothing happens at all. Check the IMAP credentials and that the port and TLS setting match what the mail provider expects. Many providers also require an app password rather than the account password.

Some emails are processed and others are not. Check the sender against the allowed senders list. An address that differs only by an alias or a subdomain will not match.

Roadblocks are created with wrong or missing details. That is the agent's prompt rather than the connector. Refine the system prompt on the AI connector, and confirm its response format is JSON.