How do I inspect mail messages in Behat tests? I am  writing a Behat Scenario that checks that an email generated by the Drupal system under test is sent to the correct recipient but I [could not] capture the mail messages from test_mail_collector. How do I do this?

[Update]
The code below is essentially my current working version and should work for you ... at least as long as you don't have an anti-spam module interceding ... in which case see the extra code in my answer.

In FeatureContext.php I switch over to the mail system sender test_mail_collector in the @BeforeScenario tagged with @email and revert to the site's original mail system sender in the @AfterScenario tagged with @email.

However, [before I discovered my answer, there were] never any messages in the array returned by \Drupal::state()-&gt;get('system.test_mail_collector') when a scenario should generate them,

Here's a typical scenario: 

...
@email
Scenario: Send an email when a user resets her password
Given users:
  | name    | mail           | status |
  | Jo User | jo@example.com | 1      |
When I visit "user/password"
And I fill in "name" with "jo@example.com" 
When I press the "Submit" button
Then an email should be sent to "jo@example.com"


And here are the FeatureContext.php snippets:

...
/**
*  @Then an email should be sent to :recipient
*/
public function anEmailShouldBeSentTo($recipient)
{
  // Reset state cache.
  \Drupal::state()-&gt;resetCache();
  $mails = \Drupal::state()-&gt;get('system.test_mail_collector') ?: array();
  $last_mail = end($mails);
  if(!$last_mail) {
    throw new Exception('No mail was sent.');
  }
  if ($last_mail['to'] != $recipient) {
    throw new \Exception("Unexpected recpient: " . $last_mail['to']);
  }
  $this-&gt;lastMail = $last_mail;
}

/** 
 *  @BeforeScenario @email
 */
public function beforeEmailScenario(BeforeScenarioScope $scope)
{
  $this-&gt;m_config = \Drupal::configFactory()-&gt;getEditable('mailsystem.settings');
  $this-&gt;saved_mailsystem_defaults = $this-&gt;m_config-&gt;get('defaults'); 
  $this-&gt;m_config-&gt;set('defaults.sender', 'test_mail_collector')-&gt;save();
  \Drupal::state()-&gt;set('system.test_mail_collector', array());
}

/** 
 *  @AfterScenario @email 
 */
public function afterEmailScenario(AfterScenarioScope $scope)
{
  // revert mail system after scenarios agged with @email
  $this-&gt;m_config-&gt;set('defaults.sender', $this-&gt;saved_mailsystem_defaults['sender'])-&gt;save();
}


The website is normally set up to run with swiftmailer enabled but I have tried all the other available mail system formatters and senders - like Default PHP Mailer - without success.

As a kind of "proof of setup" within FeatureContext.php, I can send test messages to the test_mail_collector:

$language_interface = \Drupal::languageManager()-&gt;getCurrentLanguage();
\Drupal::service('plugin.manager.mail')
    -&gt;mail('test', 'test', 'jo@example.com', $language_interface-&gt;getId());


This shows me that the test_mail_collector is working - but messages generated from the website [were] not being collected.
Ensure you don't have anti-spam modules installed and doing their job! 

The @BeforeScenario and @AfterScenario code in the original question is correct except in the case where you have an anti-spam module that is interfering with a Scenario.

For example, if  you've got honeypot installed, ensure that you disable the time_limit setting. time_limit is there to protect the Drupal site against spambots .... and test programs! ... on the assumption that humans can't enter stuff in less than a few seconds whereas bots and test programs do so almost instantaneously.

So, if you, as I did, have honeypot installed, then you will need to temporarily disable time_limit for the duration of the test. For example my @BeforeScenario and @AfterScenario code is now as follows:

/** 
   *  @BeforeScenario @email
   */
  public function beforeEmailScenario(BeforeScenarioScope $scope)
  {
    $this-&gt;m_config = \Drupal::configFactory()-&gt;getEditable('mailsystem.settings');
    $this-&gt;saved_mailsystem_defaults = $this-&gt;m_config-&gt;get('defaults'); 
    $this-&gt;m_config
      -&gt;set('defaults.sender', 'test_mail_collector')
      -&gt;set('defaults.formatter', 'test_mail_collector')
      -&gt;save();
    // Reset the state variable that holds sent messages.
    \Drupal::state()-&gt;set('system.test_mail_collector', array());

    // If we have honeypot installed then ensure that we disable time_limit
    // So that automated tests / bots can run  
    $this-&gt;h_config = \Drupal::configFactory()-&gt;getEditable('honeypot.settings');
    $this-&gt;saved_honeypot_time_limit = $this-&gt;h_config-&gt;get('time_limit');
    if ($this-&gt;saved_honeypot_time_limit) {
      $this-&gt;h_config
        -&gt;set('time_limit', '0')
        -&gt;save();
    }
  }

  /** 
   *  @AfterScenario @email 
   */
  public function afterEmailScenario(AfterScenarioScope $scope)
  {
    // revert mail system after scenarios agged with @email
    $this-&gt;m_config
      -&gt;set('defaults.sender', $this-&gt;saved_mailsystem_defaults['sender'])
      -&gt;set('defaults.formatter', $this-&gt;saved_mailsystem_defaults['formatter'])
      -&gt;save();

    // Ensure we protect against spambots again if honeypot is installed
    if ($this-&gt;saved_honeypot_time_limit) {
      $this-&gt;h_config
        -&gt;set('time_limit', $this-&gt;saved_honeypot_time_limit)
        -&gt;save();
    }
  }