Signature
mb_decode_mimeheader(string $string): stringPurpose
Email headers such as Subject were designed for plain ASCII. To carry characters like é or ß, the sender wraps the text in an encoded-word as described by RFC 2047. An encoded-word looks like =?charset?encoding?payload?=, where the encoding is B (Base64) or Q (a quoted-printable variant).
mb_decode_mimeheader() reads a header value, finds the encoded-words, decodes them, converts the result from the declared charset to PHP's current internal encoding, and returns the readable string. Text outside encoded-words is passed through as it is.
Signature
The function has been available since PHP 4.0.6 and is part of the mbstring extension.
Parameters and defaults
$string: the header field value to decode. It may contain one or more encoded-words, ordinary text, or a mixture. There are no other parameters and therefore no defaults.
The target character set is not a parameter. It is whatever mb_internal_encoding() currently returns, so it is a good idea to set it explicitly before decoding.
Return value and failure cases
The function returns a string: the decoded text in the internal character encoding. The signature has no false return, so you do not need to check for a failure value. If the input contains no encoded-words, you get the same text back.
The PHP manual records one change. As of PHP 8.3.0, underscores are converted to spaces as RFC 2047 specifies. In Q encoding an underscore stands for a space, so on PHP 8.4 Caf=C3=A9_au_lait decodes to Café au lait.
Example 1: decoding a Base64 encoded-word
The payload is generated with base64_encode() so the example does not depend on a hand-typed Base64 string.
mb_internal_encoding('UTF-8');
$word = '=?UTF-8?B?' . base64_encode('Grüße') . '?=';
echo json_encode(mb_decode_mimeheader($word), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);"Grüße"
Example 2: Q encoding and underscores
In Q encoding, bytes outside plain ASCII are written as = followed by two hex digits, and _ stands for a space.
mb_internal_encoding('UTF-8');
$header = '=?UTF-8?Q?Caf=C3=A9_au_lait?=';
echo json_encode(mb_decode_mimeheader($header), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);"Café au lait"
Example 3: plain text, mixed text and another charset
A header without encoded-words comes back unchanged. When an encoded-word declares a charset other than the internal one, such as ISO-8859-1, the decoded bytes are converted to the internal encoding.
mb_internal_encoding('UTF-8');
$values = [
mb_decode_mimeheader('Hello world'),
mb_decode_mimeheader('Re: =?ISO-8859-1?Q?Caf=E9?= menu'),
];
echo json_encode($values, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);["Hello world","Re: Café menu"]
Edge cases
- Internal encoding matters. The result is always in the current internal encoding. If that is not UTF-8, the output bytes will not be UTF-8 either, and JSON or HTML output may break. Call
mb_internal_encoding('UTF-8')first, or make sure your application's default is what you expect. - Only the value is decoded. Pass
Café menu, notSubject: Café menu. The function does not parse header names, unfold continuation lines or split address lists. - Mixed content is allowed. Encoded-words can sit between ordinary words, and the surrounding text is kept.
- Unusual or malformed input. The manual page does not describe in detail what happens with unknown charsets, wrong payloads or truncated words. If you process untrusted mail, test those inputs against your runtime and validate the result instead of assuming it is clean.
- Decoded text is still untrusted. A decoded header can contain any characters, including control characters. Escape it for the context where you display it, for example with
htmlspecialchars()in HTML.
Common mistakes
- Decoding twice. Once an encoded-word has been decoded, the text is ordinary. Running it through the function again is harmless for plain text but pointless, and it can hide where a bug really is.
- Forgetting the internal encoding. Code that works on one server can print mojibake on another because the default internal encoding differs.
- Passing a whole raw email. The function is meant for one header field value, not an entire message body or a list of headers.
- Expecting
+or%decoding. Q encoding in headers uses=XXand_, not URL-style escapes. - Relying on old underscore behavior. Before PHP 8.3.0, underscores were not converted to spaces. On PHP 8.4 they are, so tests written for older behavior may need updating.
Related functions
mb_encode_mimeheader()is the inverse. It builds encoded-words from text so it can be placed in a header.mb_internal_encoding()gets or sets the character set of the decoded result.mb_convert_encoding()converts text between encodings when you already have the raw bytes and a charset name.iconv_mime_decode()is a similar decoder from theiconvextension, with different options and behavior.
Recap
mb_decode_mimeheader() takes a header value and returns a string in which RFC 2047 encoded-words have been decoded and converted to the internal encoding. It has a single parameter and no documented false return. Set the internal encoding deliberately, pass only the header value, remember that underscores become spaces on PHP 8.3 and later, and escape the result for whatever output context you use.
Tested examples
mb_internal_encoding('UTF-8');
$word = '=?UTF-8?B?' . base64_encode('Grüße') . '?=';
echo json_encode(mb_decode_mimeheader($word), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);"Grüße"
mb_internal_encoding('UTF-8');
$header = '=?UTF-8?Q?Caf=C3=A9_au_lait?=';
echo json_encode(mb_decode_mimeheader($header), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);"Café au lait"
mb_internal_encoding('UTF-8');
$values = [
mb_decode_mimeheader('Hello world'),
mb_decode_mimeheader('Re: =?ISO-8859-1?Q?Caf=E9?= menu'),
];
echo json_encode($values, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);["Hello world","Re: Café menu"]