5.x common.inc drupal_mail($mailkey, $to, $subject, $body, $from = NULL, $headers = array())
6.x mail.inc drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE)
7.x mail.inc drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE)

Composes and optionally sends an e-mail message.

Sending an e-mail works with defining an e-mail template (subject, text and possibly e-mail headers) and the replacement values to use in the appropriate places in the template. Processed e-mail templates are requested from hook_mail() from the module sending the e-mail. Any module can modify the composed e-mail message array using hook_mail_alter(). Finally drupal_mail_system()->mail() sends the e-mail, which can be reused if the exact same composed e-mail is to be sent to multiple recipients.

Finding out what language to send the e-mail with needs some consideration. If you send e-mail to a user, her preferred language should be fine, so use user_preferred_language(). If you send email based on form values filled on the page, there are two additional choices if you are not sending the e-mail to a user on the site. You can either use the language used to generate the page ($language global variable) or the site default language. See language_default(). The former is good if sending e-mail to the person filling the form, the later is good if you send e-mail to an address previously set up (like contact addresses in a contact form).

Taking care of always using the proper language is even more important when sending e-mails in a row to multiple users. Hook_mail() abstracts whether the mail text comes from an administrator setting or is static in the source code. It should also deal with common mail tokens, only receiving $params which are unique to the actual e-mail at hand.

An example:

function example_notify($accounts) {
  foreach ($accounts as $account) {
    $params['account'] = $account;

    // example_mail() will be called based on the first drupal_mail() parameter.
    drupal_mail('example', 'notice', $account->mail, user_preferred_language($account), $params);
function example_mail($key, &$message, $params) {
  $data['user'] = $params['account'];
  $options['language'] = $message['language'];
  user_mail_tokens($variables, $data, $options);
  switch ($key) {
    case 'notice':

      // If the recipient can receive such notices by instant-message, do
      // not send by email.
      if (example_im_send($key, $message, $params)) {
        $message['send'] = FALSE;
      $langcode = $message['language']->language;
      $message['subject'] = t('Notification from !site', $variables, array(
        'langcode' => $langcode,
      $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, array(
        'langcode' => $langcode,

Another example, which uses drupal_mail() to format a message for sending later:

$params = array(
  'current_conditions' => $data,
$to = 'user@example.com';
$message = drupal_mail('example', 'notice', $to, $language, $params, FALSE);

// Only add to the spool if sending was not canceled.
if ($message['send']) {


$module: A module name to invoke hook_mail() on. The {$module}_mail() hook will be called to complete the $message structure which will already contain common defaults.

$key: A key to identify the e-mail sent. The final e-mail id for e-mail altering will be {$module}_{$key}.

$to: The e-mail address or addresses where the message will be sent to. The formatting of this string will be validated with the PHP e-mail validation filter. Some examples are:

$language: Language object to use to compose the e-mail.

$params: Optional parameters to build the e-mail.

$from: Sets From to this value, if given.

$send: If TRUE, drupal_mail() will call drupal_mail_system()->mail() to deliver the message, and store the result in $message['result']. Modules implementing hook_mail_alter() may cancel sending by setting $message['send'] to FALSE.

Return value

The $message array structure containing all details of the message. If already sent ($send = TRUE), then the 'result' element will contain the success indicator of the e-mail, failure being already written to the watchdog. (Success means nothing more than the message being accepted at php-level, which still doesn't guarantee it to be delivered.)

7 calls to drupal_mail()
contact_personal_form_submit in modules/contact/contact.pages.inc
Form submission handler for contact_personal_form().
contact_site_form_submit in modules/contact/contact.pages.inc
Form submission handler for contact_site_form().
hook_watchdog in modules/system/system.api.php
Log an event message.
MailTestCase::testCancelMessage in modules/simpletest/tests/mail.test
Test that message sending may be canceled.
MailTestCase::testPluggableFramework in modules/simpletest/tests/mail.test
Assert that the pluggable mail system is functional.

... See full list


includes/mail.inc, line 122
API functions for processing and sending e-mail.


function drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE) {
  $default_from = variable_get('site_mail', ini_get('sendmail_from'));

  // Bundle up the variables into a structured array for altering.
  $message = array(
    'id' => $module . '_' . $key,
    'module' => $module,
    'key' => $key,
    'to' => $to,
    'from' => isset($from) ? $from : $default_from,
    'language' => $language,
    'params' => $params,
    'send' => TRUE,
    'subject' => '',
    'body' => array(),

  // Build the default headers
  $headers = array(
    'MIME-Version' => '1.0',
    'Content-Type' => 'text/plain; charset=UTF-8; format=flowed; delsp=yes',
    'Content-Transfer-Encoding' => '8Bit',
    'X-Mailer' => 'Drupal',
  if ($default_from) {

    // To prevent e-mail from looking like spam, the addresses in the Sender and
    // Return-Path headers should have a domain authorized to use the originating
    // SMTP server.
    $headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $default_from;
  if ($from) {
    $headers['From'] = $from;
  $message['headers'] = $headers;

  // Build the e-mail (get subject and body, allow additional headers) by
  // invoking hook_mail() on this module. We cannot use module_invoke() as
  // we need to have $message by reference in hook_mail().
  if (function_exists($function = $module . '_mail')) {
    $function($key, $message, $params);

  // Invoke hook_mail_alter() to allow all modules to alter the resulting e-mail.
  drupal_alter('mail', $message);

  // Retrieve the responsible implementation for this message.
  $system = drupal_mail_system($module, $key);

  // Format the message body.
  $message = $system

  // Optionally send e-mail.
  if ($send) {

    // The original caller requested sending. Sending was canceled by one or
    // more hook_mail_alter() implementations. We set 'result' to NULL, because
    // FALSE indicates an error in sending.
    if (empty($message['send'])) {
      $message['result'] = NULL;
    else {
      $message['result'] = $system

      // Log errors.
      if (!$message['result']) {
        watchdog('mail', 'Error sending e-mail (from %from to %to).', array(
          '%from' => $message['from'],
          '%to' => $message['to'],
        ), WATCHDOG_ERROR);
        drupal_set_message(t('Unable to send e-mail. Contact the site administrator if the problem persists.'), 'error');
  return $message;


hass’s picture

kscheirer’s picture

Instead of doMail(), you can use the plugin manager like this:

$mailManager = \Drupal::service('plugin.manager.mail');
// ...
$result = $mailManager->mail($module, $key, $to, $langcode, $params, NULL, $send);

I found it in this helpful blog post, http://code.tutsplus.com/series/using-and-extending-the-drupal-8-mail-ap...

amittgaur’s picture

custom drupal_mail with cc/bcc.
$cc = 'yourmail@mail.com';
$params = array(
'body' => $body,
'subject' => 'Your Subject',
'headers' => array(
'Cc' => $cc,


Huntelaer’s picture

Exactly what I needed

mlelacheur’s picture

I'm not seeing where the example code for 7.x comes from; Would anyone be familiar enough with it to shed some light for me how $variables is being instantiated?

function example_mail($key, &$message, $params) {
    $data['user'] = $params['account'];
    $options['language'] = $message['language'];
    user_mail_tokens($variables, $data, $options);
    switch($key) {
      case 'notice':
        // If the recipient can receive such notices by instant-message, do
        // not send by email.
        if (example_im_send($key, $message, $params)) {
          $message['send'] = FALSE;
        $langcode = $message['language']->language;
        $message['subject'] = t('Notification from !site', $variables, array('langcode' => $langcode));
        $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, array('langcode' => $langcode));

Specifically, I see it first shows up with user_mail_tokens(), and I see that function will put 2 indices/keys in the $variables array.

Where I'm confused is when you get to $message['subject'] and $message['body'][], the $variables array is passed in for replacements, but without something else acting on $variables here, it shouldn't have the '!site' and '!username' arguments within its array.

I've tried the example code within my own site to see if I was missing something, but I'm still not clear on what's being done with that array.

wsantell’s picture

If you look at the definition for user_mail_tokens, $variables is being passed by reference as the $replacements parameter. Since the function assigns a value to $variables, that is when it's created.

kingandy’s picture

TBH I think the example code is pretty outdated, user_mail_tokens only adds two tokens to the $variables array ('[user:one-time-login-url]' and '[user:cancel-url]') but the example code goes on as though the full set of site tokens are present ('!site' and '!username' etc).

Still, this is only really supposed to be an illustration of how to tackle a hook_mail rather than a full and functional implementation in itself - you can see from this how to get the appropriate message language, switch by $key, and set up your subject and (array-of-lines) body. If you want to go further the full hook_mail() page might be more useful.

TheHTMLCoder’s picture

Great That's the thing i am looking for.
TheHTMLCoder.com Team

SurendraYellu’s picture

Is there any limit for the number of recipients for a single email?
I know, mail servers can have some limits, but, does Drupal has any limit?

naiduharish’s picture

$to = 'undisclosed-recipients:;';
$params[''headers']['Bcc'] = 'yourmail@mail.com';

drupal_mail('mymodule', 'key', $to, $lang, $params, $from);

This will send email to all the email address mentioned in Bcc/cc and $to would be blank.