It appears most people drive relays through a transistor rather than directly from a digital output pin on an Arduino.
I had wired the output pin directly to my relay before realizing this and it worked fine.
So far, you have been lucky.
What's the transistor for?
The GPIO ports tend to have high output impedances. This roughly translates to "there are lots and lots of relatively low-power transistors inside the IC which can't put out much current either because they simple are designed for that or because the IC couldn't dissipate that much heat quickly enough." There's also very little need to put out much current - why do that (and open up Pandora's Box of "how far do I go? Do I put out 1mA? 100mA? 10A? 100A?") when you can just output a small signal to drive external amplifiers?
Is it to protect the output pin from exceeding the max current if the relay coil has too low a resistance?
This is on the right track. More on this later. And before, actually.
Or, is it to conserve energy taking advantage of the common emitter amplification?
If you simply/naively add a transistor at the output, using the same voltage rails, this will technically actually use more power since you'll be using the current output of the IC as well as the current of the load.
To produce less heat perhaps?
To produce less heat from the IC, more accurately.
Really, the GPIO outputs are meant nearly exclusively for signaling. In Arduino's case, they DO beef-up the outputs a bit so you can drive LEDs and the like, but this is very much not the case in most microcontrollers.
The transistor does several things:
(1) It provides protection from low-impedance loads which'll want to draw too much current. A BJT and resistor can be configured such that you always know the maximum current draw of the load, as seen by the IC. A MOSFET (my personal favorite) can be configured so the IC sees an extremely high-impedance load, which may be desirable in this case. (Note: MOSFETs tend to be slower, but the Arduino can't go fast enough for this to be particularly meaningful).
(2) It provides minimal back-current protection (because of the diodes inside the external transistor).
(3) It allows you to put the power-hungry things elsewhere. Transistors drop voltage inside of them, which means they consume power and heat up. If you're driving a 100mA load, a single-stage BJT will burn 70mW. That 70mW is fairly significant to the IC, but really insignificant to an external transistor (which can have a dedicated heat-sink if needed). In this case, the external transistor may draw 1mA from the IC (assuming a BJT beta of 100), so now the IC has to dissipate only 700µW while the external transistor takes care of the 70mW.
(4) It allows the other circuit to have different power rails. The IC might run at 5V, but you might want to control a 12V load (like a small motor). The two sides of the transistor don't have to have the same rails - so you can use this to link two sides of a system. This is not a particularly good idea when controlling, say, 120V with a 5V microcontroller, but 12V isn't a big deal.
(5) It allows you to convert the 5V output into a current-controlled source if you want to. This is particularly important in some environments.
Transistors are magical devices that do lots and LOTS of very, very nice things. They come in different sizes for a reason, and the ones inside microcontrollers are small and can only do small things.