Taavi V n nen: Tracking my train travel by parsing tickets in emails
Rumour has it that I might be a bit of a train nerd. At least I want to
collect various nerdy data about my travels. Historically that data has
lived in manual form in several places,1 but over the past year and
a half I've been working on a toy project to collect most of that
information into a custom tool.
That toy project2 uses various sources to get information about
trains to fill up its database: for example, in Finland Fintraffic, the
organization responsible for railway traffic management, publishes very
comprehensive open data about almost everything that's moving on the
Finnish railway network. Unfortunately, I cannot be on all of the
trains.3 Thus I need to tell the system details about my journeys.
The obvious solution is to make a form that lets me save that data.
Which I did, but I got very quickly bored of filling out that form, and
as regular readers of this blog know, there is no reason to settle for
a simple but boring solution when the alternative is to make something
that is ridiculously overengineered.
Parsing data out of my train tickets
Finnish long-distance trains generally require train-specific seat
reservations, which means VR (the train company) knows which trains
I am on. We just need to find a way to extract that information in some
machine-readable format. So my plan for the ridiculously overengineered
solution was to parse the booking emails to get the details I need.
Now, VR ticket emails include the data I want in a couple of different
formats: they're included as text in the HTML email body, they're in
the embedded calendar invite, as text in the included PDF ticket, and
encoded in the Aztec Code in the included PDF ticket. I chose to
parse the last option with the hopes of building something that could
be ported to parse other operators' tickets with relative ease.
Parsing data out of my train tickets
Finnish long-distance trains generally require train-specific seat
reservations, which means VR (the train company) knows which trains
I am on. We just need to find a way to extract that information in some
machine-readable format. So my plan for the ridiculously overengineered
solution was to parse the booking emails to get the details I need.
Now, VR ticket emails include the data I want in a couple of different
formats: they're included as text in the HTML email body, they're in
the embedded calendar invite, as text in the included PDF ticket, and
encoded in the Aztec Code in the included PDF ticket. I chose to
parse the last option with the hopes of building something that could
be ported to parse other operators' tickets with relative ease.
Example Aztec code
After a bit of digging (thank you to the KDE Itinerary people for
documenting this!) I stumbled upon an European Union Agency for
Railways PDF titled ELECTRONIC SEAT/BERTH RESERVATION AND ELECTRONIC
PRODUCTION OF TRANSPORT DOCUMENTS - TRANSPORT DOCUMENTS (RCT2
STANDARD) which, in its Appendix C.1, describes how the information is
encoded in the code.4 (As a side note, various sources call these
codes SSB version 1 codes, although that term isn't used in this
specification. So maybe there are more specifications about the format
that I haven't discovered yet!)
I then wrote a parser in Go for the binary data embedded in these
codes. So far it works, although I wouldn't be surprised if there are
some edge cases that it doesn't handle. In particular, the spec
specifies a custom lookup table for converting between text and binary
data, and that only has support for characters 0-9 and A-Z. But Finnish
railway station codes can also use and .. maybe I need to buy a
ticket to a station with one of those.
Extracting barcodes out of emails
A parser just for the binary format isn't enough here if the intended
source input is the emails that VR sends upon making a booking. Time to
write a single-purpose email server! In short, the logic in the server,
again written in Go and with the help of go-smtp and go-message,
is:
- Accept any mail with a reasonable body size
- Process through all body parts
- For all PDF parts, extract all images
- For all images, run them through ZXing
- For all decoded barcodes, try to parse them with my new ticket
parsing library I mentioned earlier
- If any tickets are found, send the data from them and any metadata
to the main backend, which will save them to a database
The custom mail server exposes an LMTP interface over TCP for my
internet-facing mail servers to forward to. I chose LMTP for this
because it seemed like a better fit in theory than normal (E)SMTP.
I've since discovered that curl doesn't support LMTP which makes
development much harder, and in practice there's no benefit of LMTP
here as all mails are being sent to the backend in a single request
regardless of the number of recipients, so maybe I'll migrate it to
regular SMTP at some point.
Side quest time
The last missing part is automatically forwarding the ticket mails to
the new service. I've routed a dedicated subdomain to the new service,
and the backend is configured to allocate addresses like
i2v44g2pygkcth64stjgyuqz@somedomain.example for each user. That's
great if we wanted to manually forward mails to the service, but we can
go one step above that. I created a dedicated email alias in my mail
server config that routes both to my regular mailbox and the service
address. That way I can update my VR account to use the alias and have
mails automatically processed while still receiving backup copies of
the tickets (and any other important mail that VR might send me).
Unfortunately that last part turns out something that's easier said
than done. Logging in on the website, I'm greeted by this text stating
I need to contact customer service by phone to change the address
associated with my account.5 After a bit of digging, I noticed that
the mobile app suggests filling out a feedback form in order to change
the address. So I filled that, and after a day or two I got a "confirm
you want to change your email" mail. Success!
-
Including (but not limited to): a page of this website, the notes
app on my phone, and an uMap map.
-
Which I'm not directly naming here because I still think it needs
a lot more work before being presentable, but if you're really
interested it's not that hard to find out.
-
Someone should invent human cloning so that we can fix this.
-
People who know much more about railway ticketing than I do were
surprised when I told them this format is still in use somewhere.
So, uh, sorry if you were expecting a nice universal worldwide
standard!
-
In case you have not guessed yet, I do not like making phone
calls.
- Accept any mail with a reasonable body size
- Process through all body parts
- For all PDF parts, extract all images
- For all images, run them through ZXing
- For all decoded barcodes, try to parse them with my new ticket parsing library I mentioned earlier
- If any tickets are found, send the data from them and any metadata to the main backend, which will save them to a database
curl doesn't support LMTP which makes
development much harder, and in practice there's no benefit of LMTP
here as all mails are being sent to the backend in a single request
regardless of the number of recipients, so maybe I'll migrate it to
regular SMTP at some point.
Side quest time
The last missing part is automatically forwarding the ticket mails to
the new service. I've routed a dedicated subdomain to the new service,
and the backend is configured to allocate addresses like
i2v44g2pygkcth64stjgyuqz@somedomain.example for each user. That's
great if we wanted to manually forward mails to the service, but we can
go one step above that. I created a dedicated email alias in my mail
server config that routes both to my regular mailbox and the service
address. That way I can update my VR account to use the alias and have
mails automatically processed while still receiving backup copies of
the tickets (and any other important mail that VR might send me).
Unfortunately that last part turns out something that's easier said
than done. Logging in on the website, I'm greeted by this text stating
I need to contact customer service by phone to change the address
associated with my account.5 After a bit of digging, I noticed that
the mobile app suggests filling out a feedback form in order to change
the address. So I filled that, and after a day or two I got a "confirm
you want to change your email" mail. Success!
-
Including (but not limited to): a page of this website, the notes
app on my phone, and an uMap map.
-
Which I'm not directly naming here because I still think it needs
a lot more work before being presentable, but if you're really
interested it's not that hard to find out.
-
Someone should invent human cloning so that we can fix this.
-
People who know much more about railway ticketing than I do were
surprised when I told them this format is still in use somewhere.
So, uh, sorry if you were expecting a nice universal worldwide
standard!
-
In case you have not guessed yet, I do not like making phone
calls.
- Including (but not limited to): a page of this website, the notes app on my phone, and an uMap map.
- Which I'm not directly naming here because I still think it needs a lot more work before being presentable, but if you're really interested it's not that hard to find out.
- Someone should invent human cloning so that we can fix this.
- People who know much more about railway ticketing than I do were surprised when I told them this format is still in use somewhere. So, uh, sorry if you were expecting a nice universal worldwide standard!
- In case you have not guessed yet, I do not like making phone calls.
And this is the time when one realizes that she only has one white
camisole left. And it s summer, so I m wearing a lot of white shirts,
and I always wear a white camisole under a white shirt (unless I m
wearing a full chemise).
Not a problem, I have
At bust level it s now a bit too wide, and it gapes a bit under the
arms, but I don t think that it s going to cause significant problems,
and (other than everybody on the internet) nobody is going to see it, so
it s not a big deal.
I still have some linen, but I don t think I m going to make another one
with the same pattern: maybe I ll try to do something with a front
opening, but I ll see later on, also after I ve been looking for the
missing jersey in a few more potential places.
As for now, the number of white camisoles I have has doubled, and this
is progress enough for today.
With a friendly Canadian hand wave from vacation in Beautiful British
Columbia, and speaking on behalf of the Rcpp Core Team, I am excited to
shared that the (regularly scheduled bi-annual) update to
As I often do, this year I have also prepared a set of personalized maps
for your OpenPGP keysigning in DebConf25, in Brest!
What is that, dare you ask?


Debian uses LDAP for storing information about users, hosts and other
objects. The wrapping around this is called userdir-ldap, or ud-ldap
for short. It provides a mail gateway, web UI and a couple of schemas
for different object types.
Back in late 2018 and early 2019, we (DSA) removed support for ISO5218
in userdir-ldap, and removed the corresponding data. This made some
people upset, since they were using that information, as imprecise as
it was, to infer people s pronouns. ISO5218 has four values for sex,
unknown, male, female and N/A. This might have been acceptable when
the standard was new (in 1976), but it wasn t acceptable any longer in
2018.
A couple of days ago, I finally got around to adding support to
userdir-ldap to let people specify their pronouns. As it should be,
it s a free-form text field. (We don t have localised fields in LDAP,
so it probably makes sense for people to put the English version of
their pronouns there, but the software does not try to control that.)
So far, it s only exposed through the LDAP gateway, not in the web UI.
If you re a Debian developer, you can set your pronouns using

Everybody is trying out AI assistants these days, so I figured I'd jump on that train and see how fast it derails.
I went with
The Internet has changed a lot in the last 40+ years. Fads have come and gone.
Network protocols have been designed, deployed, adopted, and abandoned.
Industries have come and gone. The types of people on the internet have changed
a lot. The number of people on the internet has changed a lot, creating an
information medium unlike anything ever seen before in human history. There s a
lot of good things about the Internet as of 2025, but there s also an
inescapable hole in what it used to be, for me.
I miss being able to throw a site up to send around to friends to play with
without worrying about hordes of AI-feeding HTML combine harvesters DoS-ing my
website, costing me thousands in network transfer for the privilege. I miss
being able to put a lightly authenticated game server up and not worry too much
at night wondering if that process is now mining bitcoin. I miss being able
to run a server in my home closet. Decades of cat and mouse games have rendered
running a mail server nearly impossible. Those who are brave enough to try
are met with weekslong stretches of delivery failures and countless hours
yelling ineffectually into a pipe that leads from the cheerful lobby of some
disinterested corporation directly into a void somewhere 4 layers below ground
level.
I miss the spirit of curiosity, exploration, and trying new things. I miss
building things for fun without having to worry about being too successful,
after which security offices start demanding my supplier paperwork in
triplicate as heartfelt thanks from their engineering teams. I miss communities
that are run because it is important to them, not for ad revenue. I miss
community operated spaces and having more than four websites that are all full
of nothing except screenshots of each other.
Every other page I find myself on now has an AI generated click-bait title,
shared for rage-clicks all brought-to-you-by-our-sponsors completely covered
wall-to-wall with popup modals, telling me how much they respect my privacy,
with the real content hidden at the bottom bracketed by deceptive ads served by
companies that definitely know which new coffee shop I went to last month.
This is wrong, and those who have seen what was know it.
I can t keep doing it. I m not doing it any more. I reject the notion that
this is as it needs to be. It is wrong. The hole left in what the Internet used
to be must be filled. I will fill it.

There were two aspects to address. First, the subscription side
needed to be covered in either plain C or C++. That, it turns out, is
very straightforward and there are existing documentation and prior
examples (e.g. at StackOverflow) as well as the ability to have an LLM
generate a quick stanza as I did with Claude. A modified variant is now
in the