code: func readRecipientsList(filename string) ([]recipient, error) {
  	f, err := os.Open(filename)
  	if err != nil {
  		return nil, err
  	}
  	reader := csv.NewReader(f)
  	record, err := reader.Read()
  	if err != nil {
  		return nil, err
  	}
  	if len(record) == 0 {
  		return nil, fmt.Errorf("no entries in CSV")
  	}
  	if record[0] != "id" {
  		return nil, fmt.Errorf("first field of CSV input must be an ID.")
  	}
  	var columnNames []string
  	for _, v := range record[1:] {
  		columnNames = append(columnNames, strings.TrimSpace(v))
  	}
  
  	results := []recipient{}
  	for {
  		record, err := reader.Read()
  		if err == io.EOF {
  			if len(results) == 0 {
  				return nil, fmt.Errorf("no entries after the header in CSV")
  			}
  			return results, nil
  		}
  		if err != nil {
  			return nil, err
  		}
  		if len(record) == 0 {
  			return nil, fmt.Errorf("empty line in CSV")
  		}
  		if len(record) != len(columnNames)+1 {
  			return nil, fmt.Errorf("Number of columns in CSV line didn't match header columns."+
  				" Got %d, expected %d. Line: %v", len(record), len(columnNames)+1, record)
  		}
  		id, err := strconv.Atoi(record[0])
  		if err != nil {
  			return nil, err
  		}
  		recip := recipient{
  			id:    id,
  			Extra: make(map[string]string),
  		}
  		for i, v := range record[1:] {
  			recip.Extra[columnNames[i]] = v
  		}
  		results = append(results, recip)
  	}
  }
docs: // readRecipientsList reads a CSV filename and parses that file into a list of
  // recipient structs. It puts any columns after the first into a per-recipient
  // map from column name -> value.