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)

Compose and optionally send 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_send() 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) {
  $language = $message['language'];
  $variables = user_mail_tokens($params['account'], $language);
  switch ($key) {
    case 'notice':
      $message['subject'] = t('Notification from !site', $variables, $language->language);
      $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, $language->language);


$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 must comply with RFC 5322. 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: Send the message directly, without calling drupal_mail_send() manually.

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

5 calls to drupal_mail()
contact_mail_page_submit in modules/contact/contact.pages.inc
Process the site-wide contact page form submission.
contact_mail_user_submit in modules/contact/contact.pages.inc
Process the personal contact page form submission.
hook_watchdog in developer/hooks/core.php
Log an event message
_update_cron_notify in modules/update/update.fetch.inc
Perform any notifications that should be done once cron fetches new data.
_user_mail_notify in modules/user/user.module
Conditionally create and send a notification email when a certain operation happens on the given user account.


includes/mail.inc, line 85


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,
    'to' => $to,
    'from' => isset($from) ? $from : $default_from,
    'language' => $language,
    'params' => $params,
    '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. Errors-To is redundant, but shouldn't hurt.
    $headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $headers['Errors-To'] = $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);

  // Concatenate and wrap the e-mail body.
  $message['body'] = is_array($message['body']) ? drupal_wrap_mail(implode("\n\n", $message['body'])) : drupal_wrap_mail($message['body']);

  // Optionally send e-mail.
  if ($send) {
    $message['result'] = drupal_mail_send($message);

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


webchick’s picture

...like you could in Drupal 5, without going through this convoluted e-mail template registry system, you can call the drupal_mail_send() function directly.

For example:

$message = array(
  'to' => 'example@mailinator.com',
  'subject' => t('Example subject'),
  'body' => t('Example body'),
  'headers' => array('From' => 'example@mailinator.com'),


I'm sure someone from the i18n team will jump in here and explain why this is pure, unadulterated evil, but sometimes this is all you need. :P


- Because drupal_mail() isn't called, other modules will not be able to hook_mail_alter() your output, which can cause unexpected results.
- drupal_mail_send() is ignorant about which language to send the message in, so this needs to be figured out beforehand.
- You'll have to manually specify any other e-mail headers that are required ('Content-Type', etc.). These are normally taken care of for you by drupal_mail().

In the case where your module sends several different types of e-mails, and you want those e-mail templates to be editable (for example, user module's various registration notification/password reset/etc. e-mails), using hook_mail() is still the best way to go.

dalin’s picture

Sk8erPeter’s picture

I'm using SMTP Authentication Support module in Drupal 6. I was curious about sending mails this way, and I don't know if the usage of the mentioned module is the reason your code fails (edit: yes, it is, see the reasons later, but it does NOT work if the 'From' part is under the 'headers' index, and 'from' index is missing from the $messages array: this way I get "The submitted from address () is not valid." error message.
This can really be caused by the module as 'from' index is not mentioned in drupal_mail_send()'s documentation.

But it DOES work like this:

$message = array(
  'to' => 'example@example.com',
  'from' => 'another_example@example.com',
  'subject' => t('Example subject'),
  'body' => t('Example body'),
  'headers' => array(),


This way that "freaking e-mail" really gets sent!

I was inspecting in smtp.module file's code, and the reason is in smtp_drupal_mail_wrapper() function.

function smtp_drupal_mail_wrapper($message) {
  $id   = $message['id'];
  $to   = $message['to'];
  $from = $message['from']; // --> it causes the problem if the index doesn't exist

Just wanted to explain it.

martin_q’s picture

I'm not officially on the i18n team, though I may one day be, so apologies if I'm talking out of turn, but you did more or less ask to be told this!

Your caveats are surely sufficiently major issues, especially the first two which look 'proper evil' (as the kids round here would say).

Why would you ever want to skip the mechanisms put in place to make the email system work flawlessly? I don't understand. I'm no advocate of excessive complexity, but the email registry system seems to me to be a two-step system rather than the one-step (with at least two big caveats) which you propose.

Perhaps all it needs is a significantly clearer documentation page here?

  • Spell out the need to use hook_mail followed by drupal_mail, and the part that each of those plays (but leave detailed documentation on hook_mail to that function's documentation, obviously);
  • Indicate how hook_mail_alter might affect it, or can be used to affect other mails (again leaving detailed documentation aside);
  • Mention that modules exist for translating emails, e.g. mail_edit

Done well, the registry needn't seem convoluted, the code need scarcely be longer than what I imagine you had in Drupal 5 (correct me if I'm wrong though) and more to the point no-one's module will break anyone else's. Drupal heaven!

pjsz’s picture

I agree with webchick. Although the system is robust and powerful for involved modules, sending a simple email is like having go to the next building to use the bathroom. drupal_mail just needs the obvious missing arguments -- subject, body, and maybe email_type = plain text or html. Then you would not have to implement hook_mail just so you can grab subject and body from the params array and put them where drupal_mail wants them in the first place. I think everyone expects to be able to just pass those arguments in along with to and from.

Alternatively, drupal_mail_send could take the email_type argument and it would be almost as good. But you don't get any of the benefits of drupal_mail. Thanks webchick for the helpful documentation.

Rob Carriere’s picture

Thanks webchick.

drupal_mail() looks like a great tool for module builders, but it's completely inappropriate for simple situations. A wrapper function might help, but I frankly find it difficult to imagine a use case for a site builder where drupal_mail() or wrappers thereof would have advantages over drupal_mail_send().

If the mail needs tweaking, I maintain the custom code that sends it, so that doesn't need an API.

In a monolingual site the whole language issue is irrelevant and in a multilingual site you still usually know the required language because the mail will usually be a response to a form submission.

If one of the above doesn't apply, then drupal_mail() will certainly be awesome and save lots of hair pulling; it's the other 99.44% of cases that people are complaining about.

Jztinfinity’s picture

Is this api excessive yes, sometimes useful, definitely. There are many situations where you want to make sure that emails sent from the system (which actually might be invoked by users with arbitrary input, like say in a "Share with a Friend" situation or in a feedback situation) with certain qualities are handled in certain ways.

Let me throw out some situations:

  • You want to log every email sent in the database, or emails sent with certain conditions
  • You want to remove all remote content in emails
  • You want to change all emails from a certain feedback from to plain text, however for your newsletters you want the emails to remain html
  • You want to conditionally add return receipts

Could you simply alter the code of drupal_mail - yes, and I've done so when working with Drupal 5 (in my situation my company wanted to use PEAR::MimeMail instead of generic Drupal mail - also, in my development environment I altered drupal_mail to simply log the email instead of sending it). However the same could be said with all the Drupal hooks et al. Moreover, in most situations drupal_mail is called on particular occasions (not constantly as is the case with say hook_nodeapi) and so performance concerns are not major.

tmsimont’s picture

If you are going to use the drupal_mail() function with the $from parameter, it should be noted that this will only alter the From header, not the Sender, Errors-to or Return-Path.

The Sender, Errors-to or Return-Path use the site default email address regardless of the given $from parameter.

This may be desirable if you want to have the $from parameter be secondary to the fact the email is coming from your site, but the mismatch causes the From line of the email to render inconsistently across email clients.

E.g. Gmail only shows the email to be from [From header], but Outlook will say the email is from [From header] on behalf of [Site default email]

See http://drupal.org/node/656472

rmp’s picture

Why isn't there a Reply-To header? I know it should be different to From, but very often you'd want to set different addresses in From and Reply-To.

  // Build the default headers
  $headers = array(
    'Reply-To'                  => $params['reply-to'],
    'MIME-Version'              => '1.0',
    'Content-Type'              => 'text/plain; charset=UTF-8; format=flowed; delsp=yes',
    'Content-Transfer-Encoding' => '8Bit',
    'X-Mailer'                  => 'Drupal'

I added one like so to mail.inc.

alegs14’s picture

This worked for you?

smk-ka’s picture

Just in case you are wondering how to create e-mail addresses that comply with RFC ding-dong:

  • In it's simplest form, it's just the address part, i.e. foo@domain.tld, which can be passed as-is.
  • However, if you want to pass a name with an address, e.g. "John Doe" <foo@domain.tld>, and the name consists of user entered data which may contain reserved characters, the following code ensures the email address complies to the RFC:
    $to = '"'. addslashes(mime_header_encode($user->name)) .'" mail .'>';

    Note this is actually a very elegant shorthand notation: if the name doesn't contain any non-ASCII characters there's nothing to MIME-encode, so addslashes() kicks in and escapes quotes (", ') and backslashes (\). If, however, the name contains umlauts and stuff, mime_header_encode() creates a base64 encoded string, leaving addslashes() deedless, since a MIME encoded string never contains any reserved characters that need escaping. In other words: don't mess with the order, it is important!

Angry Dan’s picture

Thanks! Shame that Drupal doesn't include that in the API somewhere.

valk’s picture

If you want to send a freaking html email - just set the headers like so:

'headers' => array(
          'From' => $from,
          'MIME-Version' => '1.0',
          'Content-Type' => 'text/html;charset=utf-8',),

then pour some html into the body:

'body' => "<html><head></head><body><b>Enjoy your easy life!</b></body></html>",
seriochka’s picture

...but here's a question I'm trying to figure out and it seems no amount of searching this site or googling has been able to point the way. It also might be I'm missing something obvious, so please bear with me.

When calling this mail function, where exactly does the message get placed once it's enacted? For instance, we have a Drupal 6/Postfix setup here with several forms for new user registration, address changes, CLE/Event registrations, and the like. All of them I've seen so far use this Drupal_mail() function to actually send the message.

However, we've also noticed numerous periods where the message does not actually make it to the staff person's mailbox itself. When we go to investigate, we find the transaction on the drupal side of things...but don't see an e-mail hanging around in queue anywhere in the server file structure.

I'm assuming (I know...I shouldn't) that the enacting of this mail sending process goes out and looks for an active MTA agent and sends the message along via that route. But what happens if an active MTA isn't present or available (in the case of some sort of e-mail downtime)....what does this call do at that point?



hoppurr’s picture

correct me if i'm wrong but this system makes it more difficult if you want the email sent to be not a response to a form submission but to BE the form submission, such as a custom contact form etc...

m4manas’s picture

Is there some permission issue which restrict the mail only to admin user. What if i want to send mail from normal user account.
I m able to send the mail using admin account but no through the normal user account.

massimoi’s picture

in my case I neeeded to change
$message['headers']['Sender']=$message['headers']['Return-Path']=$message['headers']['Errors-To'] = 'mail@mail.com';

I simply hooked into

mymodule_mail and set the value, without touching mail.inc

lenaf007’s picture

Thanks to webchick, rmp, and others for putting basic info in plain English. As an asp developer moving into php/drupal, having a basic email function is so much easier than trying to navigate the required structures in drupal_mail, which just isn't necessary for a basic, internal site.

You've saved me from wasting (more) hours searching for this stuff.

fledev’s picture


I've been looking for an solution at drupal_mail() (D6) function to add header information to be able to format the mail - output as html mail. Therefor I would like you to help me with this issue.
I've tried with the drupal_mail_send(); function which works great. Unfortunately the module is calling hook_mail(); I'm stuck with the drupal_mail() function.
Also I've try to use the hook_mail() and to add $headers, $header, $message['headers'] without any success.

Thank you in advance.

PS: btw: I don't think that the issue or support topics should get out of the function/thematic. As first the developers look at the documentation and in some cases it helps to have an example at them.