I think your second idea can be made to work:
Or we could intervene here: hook_civicrm_tokenValues() But at this point we no longer know for which event we need to get the additional custom field data from.
As far as I can tell, the parameters passed into this hook during the processing of a Scheduled Reminder actually do include the event ID -- the structure is just a little off as discussed elsewhere.
Here's an implementation that provides the value of a custom field:
/**
* Populates a token named "mytoken" with the value from a custom field
* (id 123) for use in Scheduled Reminders
* Implements hook_civicrm_tokenValues().
*/
function foo_civicrm_tokenValues(&$values, $cids, $job = null, $tokens = array(), $context = null) {
static $events = array();
if (is_numeric($cids)) {
// We're being called by CRM/Core/BAO/ActionSchedule.php:465
// Params are structured differently than they're supposed to be, but we
// can work with it. See https://civicrm.stackexchange.com/questions/3573/can-tokenvalue-hooks-be-used-in-scheduled-reminder-emails
if ($event_id = CRM_Utils_Array::value('event.event_id', $values)) {
// We've been passed an event ID
if (!array_key_exists($event_id, $events)) {
// We haven't looked up the custom field value yet
// Fetch the custom field value from the database
$event = civicrm_api3('Event', 'getsingle', array(
'sequential' => 1,
'return' => "custom_123",
'id' => $event_id,
));
$val = CRM_Utils_Array::value('custom_123', $event);
// Keep this value in memory to use with the other event participants
$events[$event_id] = $val;
} else {
// We stored the value previously
$val = $events[$event_id];
}
$values['event.mytoken'] = $val;
}
}
}
Note: event information is not passed to the hook for additional (non-participant) recipients of the Scheduled Reminder.