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.)

8 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->format($message);

  // 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;
    // Sending was originally requested and was not canceled.
    else {
      $message['result'] = $system->mail($message);
      // 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;


Make your emails support HTML. Put code snippet inside hook_mail() or hook_mail_alter() (remove php tags).

  // Make emails HTML friendly
  $message['headers']['Content-Type'] = 'text/html; charset=UTF-8; format=flowed';

This no longer appears to work in Drupal 7. Everything gets converted to plain text.

I tried it by using the following lines:

 $message = drupal_mail($module, $key, 'someone[@t]nilecode.com', language_default(), array(), 'anothermail[@t]nilecode.com', FALSE);

$message['headers']['Content-Type'] = 'text/html; charset=UTF-8; format=flowed';

$sentMessage = array();
$sentMessage[] = 'Line1..........';
$sentMessage[] = 'Line2..........';

$message['body'] = implode('<br/>', $sentMessage);

// Make sure the following line is commented/omitted
//  $message = $system->format($message);

// Send e-mail.
$message['result'] = $system->mail($message);

It actually worked perfectly on Drupal 7. I included it in the hook_mail function of my module, in which I switch among possible email ids. Be careful to set it before you set the 'body' elements. I had no HTML-based message setting it after the 'body' elements.

Example usage:

    $module = 'module_name';
    $key = 'key';
    $language = language_default();
    $params = array();
    $from = NULL;
    $send = FALSE;
    $message = drupal_mail($module, $key, $email, $language, $params, $from, $send);

    $message['subject'] = $subject;
    $message['body'] = array();
    $message['body'][] = $line1;
    $message['body'][] = $line2;

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

    // Format the message body.
    $message = $system->format($message);

    // Send e-mail.
    $message['result'] = $system->mail($message);

The "Examples for Developers" module (http://drupal.org/project/examples) has a really nice example and it explains how everything works in more detail that what's on this page. Here is the source code of their example:


Below is the tutorial of usage in D6:
drupal_mail usage tutorial

...and since many things have changed in Drupal 7, here's a tutorial on how to use a wrapper function to easily send mail using drupal_mail().

The given example code cannot correctly replace placeholders in email subject and body. Here is the correct code:

  $message['subject'] = t('Alert from !site', array('!site' => variable_get('site_name', "Default site name")), array('langcode' => $langcode));
  $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", array('!username' => $params['account']->name), array('langcode' => $langcode));

The Parameter $language must not be an object, but a string like "de" or "en".

This is actually completely wrong; the $language parameter must be an object and NOT a 2-letter langcode string. The docs really need updating to make this clearer, but if you read the source code of functions like language_default() you'll see they return a language object.

If you need further evidence, search Drupal 7 core's modules for drupal_mail() calls, and you'll find it either passes in language_default() as above, or the global $language object.

Then, besides language_default() is possible to use

global $language;
$language = language_list()[$language->language];
drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE);

how can i also include Cc and Bcc fields in $message so that drupal_mail sends mails to them aswell ....


$message['headers']['CC'] = user_email;

Parameters for BCC and CC

$params = array(
'body' => $body,
'subject' => $subject,
'headers' => array(
'Bcc' => $header_bcc,
'Cc' => $header_cc
$email = drupal_mail('ModuleName', 'message_key', $to, LANGUAGE_NONE, $params, $from, true);

The propper way to use this:

 * Implements hook_entity_insert().
function dummy_module_node_insert($node) {
  $module = 'dummy_module';
  $key = 'dummy_key';
  $language = language_default();
  $params = array(
    '@url' => url('node/' . $node->nid, array('absolute' => TRUE)),
    '@title' => $node->title,

  drupal_mail($module, $key, $mail, $language, $params);

 * Implements hook_mail().
function dummy_module_mail($key, &$message, $params) {
  $message['subject'] = t("@title event registration confirmation", $params);
  $message['body'] = array();
  $message['body'][] = t("Hello", $params);
  $message['body'][] = t("Node has been created @title (@url).", $params);

There may some errors but this the overall way to use it.

Thanks for posting this! It helped a lot. I wasn't including the array('absolute' => TRUE); part on my url parameter.

using this in a custom function built off the examples module caused a fail. renaming the module to MODULENAME rather than MODULENAME_OTHERNAME did the trick. FYI.

I tried to change from email address using hook_mail_alter(). The code is given below:

 * Implements hook_mail_alter() for send mail to admin in cc
function test_commerce_mail_alter(&$message) {
  if ($message['id'] == 'rules_rules_action_mail_commerce_checkout_order_email_3') {

    if (!empty($store_email)) {
      $message['from'] = '"'. variable_get('site_name', 'Test commerce') .'" ';
      $message['headers']['From'] = $message['from'];
      $message['headers']['Reply-To'] = $message['from'];
      //$message['headers']['Return-Path'] = $message['from'];
      //$message['headers']['Sender'] = $message['from'];
      $message['headers']['Cc'] = xxx@gmail.com;

But i can't change from email addres. By default it sets site_mail.

Found some useful tips from below link.

function yourmodule_mail_alter(&$message) {
  $message['from'] = $message['headers']['From'] = '"Full Name" <email@address.com>';
function mymodule_mail_alter(&$message){
  $default_from = variable_get('site_mail', ini_get('sendmail_from'));

  if($message['from'] == $default_from){
    $message['from'] = '"'. variable_get('site_name', 'Drupal') .'" <'. $default_from .'>';
    $message['headers']['From'] = $message['headers']['Sender'] = $message['headers']['Return-Path'] = $message['headers']['Errors-To'] = $message['headers']['Reply-To'] = $message['from'];