Skip to content

[Due for payment 2025-06-23] [$500] Add new Travel-specific report actions and their custom messages #60380

Description

@blimpich

Background

V1 of Expensify Travel launched in February of this year, and we've seen month over month growth of customers adopting it and using Expensify Travel to get shit done. We use Spotnana's webhook api to achieve this functionality. We want travel to be as deeply integrated into the app as possible.

Problem

Travel is messy and lots of things happen other than just booking flights. Flights get cancelled, refunded, seats get changed, people get bumped up in class (ex: economy to business), flight schedules change last minute, etcetera. We want our app to be as deeply integrated with travel as possible and to have all relevant information about a trip in the trip chat. Spotnana has a range of "operations" that the webhook API can produce and we handle only couple. So for example, if I book a flight from Los Angeles to Seattle, and that flight gets moved by an hour, there is no notification in the Expensify app to signify that change, even though Spotnana is sending us the data.

Solution

Have Concierge add a muted comment in the trip room chat when we have relevant updates about flights, hotels, cars, limos, and rail bookings. We'll add approximately 20 new types of operations that are provided by the Spotnana webhook API. When we receive a payload from Spotnana, if the payload has an operation for which we want to create an update message, we'll create a report action with the type TRAVEL_TRIP_ROOM_UPDATE. Each report action will have a originalMessage property, which will be a JSON object, and on the json object will be the attribute "operation", which will be the trip operation, and the rest of the attributes will be the reservation object data that we can use to make the message.

Example Message
Image

Example reportAction object for a flight pnr

{
    "accountEmail": "__FAKE__",
    "accountID": "0",
    "action": "TRAVEL_TRIP_ROOM_UPDATE",
    "created": "2025-05-09 08:02:04.976",
	"message": "",
    "originalMessage": {
        "arrivalGate": {
            "gate": "",
            "terminal": "F"
        },
        "company": {
            "longName": "American Airlines",
            "phone": "",
            "shortName": "AA"
        },
        "confirmations": [
            {
                "name": "Confirmation Number",
                "value": "MYYJPB"
            }
        ],
        "departureGate": {
            "gate": "",
            "terminal": ""
        },
        "duration": 9600,
        "end": {
            "cityName": "Philadelphia, PA, United States",
            "date": "2025-03-01T09:40:00",
            "longName": "Philadelphia International Airport",
            "shortName": "PHL",
            "timezoneOffset": ""
        },
        "isNewDot": false,
        "lastModified": "2025-05-09 08:02:04.976",
        "operation": "BOOKING_TICKETED",
        "route": {
            "airlineCode": "AA5115",
            "class": "ECONOMY",
            "number": "5115"
        },
        "seatNumber": "",
        "start": {
            "cityName": "New Orleans, LA, United States",
            "date": "2025-03-01T06:00:00",
            "longName": "Louis Armstrong New Orleans International Airport",
            "shortName": "MSY",
            "timezoneOffset": ""
        },
        "travelerPersonalInfo": {
            "email": "employee@travelconsumercorp.com",
            "name": "Falon Abram"
        },
        "type": "flight"
    },
    "reportActionID": "5755913763092787521",
    "reportID": 651638413310008
}

Example reportAction object for a rail pnr

{
    "accountEmail": "__FAKE__",
    "accountID": "0",
    "action": "TRAVEL_TRIP_ROOM_UPDATE",
    "created": "2025-05-02 20:50:28.378",
    "message": "",
    "originalMessage": {
        "coachNumber": "152",
        "company": {
            "longName": "Amtrak",
            "phone": "",
            "shortName": "Northeast Regional"
        },
        "confirmations": [
            {
                "name": "Ticket Number",
                "value": "3230884002346"
            }
        ],
        "end": {
            "cityName": "Boston",
            "date": "2024-11-23T16:28:00-05:00",
            "longName": "South Station",
            "shortName": "BOS",
            "timezoneOffset": ""
        },
        "operation": "EXCHANGE",
        "fareType": "Business Class Seat",
        "route": {
            "class": "BUSINESS",
            "name": "Northeast Regional 152"
        },
        "seatNumber": "9A",
        "start": {
            "cityName": "New York",
            "date": "2024-11-23T12:14:00-05:00",
            "longName": "New York Penn Station",
            "shortName": "NYP",
            "timezoneOffset": ""
        },
        "travelerPersonalInfo": {
            "email": "bstites+travel12@expensifail.com",
            "name": "brandon sasdf"
        },
        "type": "train",
    },
    "reportActionID": "8882719064301787065",
    "reportID": 3158835779047053
}

Example reportAction object for a hotel pnr

{
    "accountEmail": "__FAKE__",
    "accountID": "0",
    "action": "TRAVEL_TRIP_ROOM_UPDATE",
    "created": "2025-05-02 20:50:28.378",
    "message": "",
    "originalMessage": {
        "cancellationDeadline": "2024-04-19T23:59:00-07:00",
        "cancellationPolicy": "FREE_CANCELLATION_UNTIL",
        "confirmations": [
            {
                "name": "Itinerary Number",
                "value": "74694882"
            }
        ],
        "end": {
            "address": "\"480 SUTTER STREET\" 94108, San Francisco, CA, US",
            "date": "2024-04-24T11:00:00",
            "longName": "MARRIOTT SAN FRANCISCO UNION SQ"
        },
        "operation": "BOOKING_REBOOKED",
        "longName": "MARRIOTT SAN FRANCISCO UNION SQ",
        "paymentType": "PREPAID",
        "roomClass": "STANDARD ROOM",
        "start": {
            "address": "\"480 SUTTER STREET\" 94108, San Francisco, CA, US",
            "date": "2024-04-23T16:00:00",
            "longName": "MARRIOTT SAN FRANCISCO UNION SQ"
        },
        "travelerPersonalInfo": {
            "email": "soniya@spotnana.com",
            "name": "Soniya Goyal"
        },
        "type": "hotel"
    },
    "reportActionID": "8882719064301787065",
    "reportID": 3158835779047053
}

Example reportAction object for a car pnr

{
    "accountEmail": "__FAKE__",
    "accountID": "0",
    "action": "TRAVEL_TRIP_ROOM_UPDATE",
    "created": "2025-05-02 20:50:28.378",
    "message": "",
    "originalMessage": {
        "cancellationDeadline": "2024-04-19T16:00:00",
        "cancellationPolicy": "FREE_CANCELLATION_UNTIL",
        "carInfo": {
            "engine": "UNKNOWN_ENGINE",
            "name": "Compact"
        },
        "end": {
            "date": "2024-05-01T14:00:00",
            "location": "BASEMENT 1 305 ALEXANDRA ROAD"
        },
        "operation": "BOOKING_REBOOKED",
        "paymentType": "PREPAID",
        "reservationID": "K812F742527",
        "start": {
            "date": "2024-04-19T13:00:00",
            "location": "BASEMENT 1 305 ALEXANDRA ROAD"
        },
        "travelerPersonalInfo": {
            "email": "spotnanauser@spotnanauser.com",
            "name": "Brandon Stites"
        },
        "type": "car",
        "vendor": "HERTZ"
    },
    "reportActionID": "8882719064301787065",
    "reportID": 3158835779047053
}

You can see that each object follows a similar, but not exact, pattern. Each one will always have a type attribute that you can use to distinguish between air, hotel, rail, and car pnrs, and each will always have an operation attribute to tell you what kind of message we want.

Each report action should cause the UI to display a custom message, as defined in this spreadsheet. The "Message displayed to user" column will be the English copy we'll use. The variables in the messages will be passed to the frontend as part of the report action.

It is difficult to test all of these new report actions because our sandbox environment isn't able to replicate all of them. So for now lets assume that the reservation object stored inside the report action will be similar to the examples listed above.

Contributor will need to:

  • create new report action
  • handle constructing new messages using the data passed to the frontend
  • be responsible for getting Spanish translations for the English copy

This will be a tricky thing to test since it is difficult/impossible to test a lot of these flows directly in staging and dev. @blimpich will work closely with the contributor working on this to test how the messages look in the final product.

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~021922333355980760454
  • Upwork Job ID: 1922333355980760454
  • Last Price Increase: 2025-05-13
Issue OwnerCurrent Issue Owner: @
Issue OwnerCurrent Issue Owner: @shubham1206agra

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionDailyKSv2ExternalAdded to denote the issue can be worked on by a contributorNewFeatureSomething to build that is a new item.

Type

No type
No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions