사용 방법 · 출처 기반 답변
Mailto in PHP: Build Mailto Links and Send Email
TL;DR: PHP has no mailto() function. To make a mailto link, build the query with http_build_query($fields, '', '&', PHP_QUERY_RFC3986) so spaces become %20 (not +), use \r\n for line breaks, and escape the href with htmlspecialchars(). To send email from the server, use mail(), PHPMailer over authenticated SMTP, or an HTTP email API; mail() returning true only means the local MTA queued the message.
Is there a mailto function in PHP?
'Mailto in PHP' covers two different jobs, and most confusion comes from mixing them. The first is generating a mailto: link, an HTML anchor such as <a href="mailto:support@example.com?subject=Order%201042">, which opens the visitor's own mail client with the address, subject, and body pre-filled. PHP only builds the string; the visitor's device sends the email, and only if they press Send. The second is sending email from the server, which PHP does with the built-in mail() function, a library such as PHPMailer or Symfony Mailer over SMTP, or an HTTP email API. There is no mailto() function in PHP. Use a mailto link for 'contact us' and 'report a problem' links where the user should write the message; use server-side sending for contact forms, password resets, receipts, and anything your application must send reliably without the user's mail client. The rest of this guide shows both, starting with the encoding rules that break most hand-built mailto links.
How do you build a mailto link in PHP correctly?
Build a mailto link in PHP by validating the address, percent-encoding the query with http_build_query using PHP_QUERY_RFC3986, and escaping the result for HTML. The mailto scheme is defined in RFC 6068, published October 2010, and three of its rules trip up PHP code. Spaces must be %20: urlencode() turns spaces into '+', which many mail clients, including Outlook and Apple Mail, show literally, so subjects arrive as 'Order+1042+question'; rawurlencode() and PHP_QUERY_RFC3986 produce %20. Line breaks in the body MUST be encoded as %0D%0A, so build the body with \r\n, not \n. And because '&' separates parameters and is reserved in HTML, the href attribute must be written with &, which htmlspecialchars() does. Do not percent-encode the '@' in the address; RFC 6068 allows it unencoded, and some clients fail on %40. Non-ASCII text such as accented names is encoded as UTF-8 bytes, which rawurlencode() handles when your strings are UTF-8.
<?php
function mailto_link(string $to, array $fields = []): string
{
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('invalid address');
}
$query = http_build_query($fields, '', '&', PHP_QUERY_RFC3986); // spaces -> %20
return 'mailto:' . $to . ($query !== '' ? '?' . $query : '');
}
$href = mailto_link('support@example.com', [
'subject' => 'Question about order 1042',
'body' => "Hi,\r\n\r\nI have a question about my order.", // \r\n -> %0D%0A
'cc' => 'billing@example.com',
]);
?>
<a href="<?= htmlspecialchars($href, ENT_QUOTES, 'UTF-8') ?>">Email support</a>What are the limits of mailto links?
Mailto links are limited by the visitor's setup, not by PHP. A link does nothing useful if the visitor has no desktop mail client configured, which is common on shared and work computers; the browser may open an empty Windows Mail or Apple Mail setup screen instead of their Gmail tab. Long bodies are also fragile: browsers, operating systems, and mail clients each cap the length of the URL they pass along, and long bodies get truncated without warning, so keep pre-filled text to a sentence or two. You cannot attach files with a mailto link, RFC 6068 has no attachment field, and clients ignore attachment parameters. You also cannot know whether anything was sent, because the message leaves from the visitor's mailbox. Finally, a plain mailto link exposes the address to scrapers. If you need the message to arrive, need attachments, or need to know it was sent, replace the link with a form that posts to your PHP backend and sends the email server-side, which the next sections cover.
What does the built-in mail() call actually do?
PHP's mail() function hands a message to the local mail system and returns true if that system accepted it. Its signature is mail(string $to, string $subject, string $message, array|string $additional_headers = [], string $additional_params = ""): bool, and since PHP 7.2 the headers argument can be an associative array. On Linux, mail() pipes the message to the program in the sendmail_path setting, usually /usr/sbin/sendmail provided by Postfix, Exim, or sendmail; on Windows, PHP talks SMTP directly to the server named in the SMTP and smtp_port settings. The manual says lines should be separated with CRLF and should not be longer than 70 characters, which is why its examples call wordwrap($message, 70, "\r\n"). The return value is the trap: true means the local MTA queued the message, not that it was delivered, and many hosts accept mail and drop it later. Use additional_params to set the envelope sender with -f, so bounces return to an address you control.
<?php
$to = 'customer@example.com';
$subject = 'Your order has shipped';
$message = wordwrap("Hello,\r\nYour order 1042 is on the way.", 70, "\r\n");
$headers = [
'From' => 'Acme Orders <orders@example.com>',
'Reply-To' => 'support@example.com',
'Content-Type' => 'text/plain; charset=UTF-8',
];
// -f sets the envelope sender (Return-Path) so bounces come back to you
$ok = mail($to, $subject, $message, $headers, '-forders@example.com');
// $ok === true only means the local MTA queued itWhy do emails sent with mail() go to spam?
Emails sent with PHP mail() go to spam, or vanish, because the message leaves from the web server with no authentication tying it to your domain. Since February 1, 2024, Gmail requires every sender to pass SPF or DKIM, and bulk senders (over 5,000 messages a day) to publish DMARC; Yahoo applies the same rules. A web server's sendmail usually does not DKIM-sign, its IP is not in your domain's SPF record, and shared hosting IPs carry the reputation of every other site on the box. Cloud providers including AWS, Google Cloud, and Microsoft Azure block or restrict outbound port 25 by default, so mail() on a fresh VM often fails outright. The From header also matters: setting From to the visitor's address in a contact form spoofs their domain and fails DMARC; put the visitor in Reply-To instead. Finally, passing user input into headers without validation allows header injection, where an attacker adds Bcc lines via CRLF and turns your form into a spam relay.
How do you use PHPMailer over SMTP?
PHPMailer sends email from PHP through an authenticated SMTP server, which solves the authentication and port problems of mail(). The current release is PHPMailer 7.1.1, published May 18, 2026; it supports PHP 5.5 through 8.5 and installs with composer require phpmailer/phpmailer. Point it at an SMTP relay from your email provider or mailbox host, use port 587 with STARTTLS or 465 with implicit TLS, and set From to an address on a domain whose SPF and DKIM the relay covers. PHPMailer builds correct MIME for HTML plus plain-text bodies (Body and AltBody), handles attachments and UTF-8 subjects, and validates addresses, which removes the header-injection risk of hand-built headers. Symfony Mailer (currently v8.1) is the equivalent for Symfony and Laravel-style codebases and supports both SMTP and HTTP API transports. Load credentials from environment variables, never from source control, and enable exceptions with new PHPMailer(true) so failures are caught instead of silently returning false.
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require 'vendor/autoload.php';
$mail = new PHPMailer(true); // throw on errors
try {
$mail->isSMTP();
$mail->Host = getenv('SMTP_HOST');
$mail->SMTPAuth = true;
$mail->Username = getenv('SMTP_USER');
$mail->Password = getenv('SMTP_PASS');
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->CharSet = 'UTF-8';
$mail->setFrom('orders@example.com', 'Acme Orders');
$mail->addAddress('customer@example.com');
$mail->addReplyTo('support@example.com');
$mail->isHTML(true);
$mail->Subject = 'Your order has shipped';
$mail->Body = '<p>Your order <b>1042</b> is on the way.</p>';
$mail->AltBody = 'Your order 1042 is on the way.';
$mail->send();
} catch (Exception $e) {
error_log('Mailer error: ' . $mail->ErrorInfo);
}How do you call an HTTP email API from PHP?
Sending through an HTTP email API replaces SMTP sessions with one HTTPS request, which works from any host, including those that block outbound SMTP ports, and returns a message ID you can track. With SendHQ, you POST JSON to https://sendhq.cc/api/v1/emails with a Bearer API key; the from address must be on a domain you verified, so SPF, DKIM, and DMARC alignment are already in place. Add an Idempotency-Key header (up to 200 characters) derived from the event, such as the order ID, so a retried request after a timeout replays the first response instead of sending a duplicate; reusing the key with a different payload returns HTTP 409. Treat 429, 502, and 503 responses as retryable with exponential backoff and jitter, and 400-range validation errors as bugs to fix. An accepted response means the message is queued; the message's status later moves to delivered, bounced, complained, or failed, which mail() can never tell you.
<?php
$payload = json_encode([
'from' => 'Acme Orders <orders@mail.example.com>',
'to' => ['customer@example.com'],
'reply_to' => 'support@example.com',
'subject' => 'Your order has shipped',
'html' => '<p>Your order <b>1042</b> is on the way.</p>',
'text' => 'Your order 1042 is on the way.',
]);
$ch = curl_init('https://sendhq.cc/api/v1/emails');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('SENDHQ_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: order-1042-shipped',
],
CURLOPT_POSTFIELDS => $payload,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
// 2xx: accepted (store the returned id); 409: key reused with a different payload;
// 429/502/503: retry with backoffWhich PHP email method should you choose?
Choose the PHP email method by who sends the message and how much you need to know about it. Use a mailto link when the visitor should write and send the email from their own mailbox, such as a 'contact sales' link, and you do not need confirmation. Use mail() only on servers where a properly configured local MTA already DKIM-signs and relays mail, such as a managed host that documents it; for new projects it is the weakest option because it reports nothing beyond 'queued'. Use PHPMailer or Symfony Mailer over SMTP when you already have an SMTP relay and want standards-based portability between providers. Use an HTTP email API when you need delivery status, bounce and complaint events, idempotent retries, or are on a host that blocks SMTP ports. For a contact form, the robust pattern is a form that posts to PHP, validates and rate-limits input, sends from your own verified domain with the visitor's address in Reply-To, and shows a confirmation only after the provider accepts the message.
팀에서 자주 묻는 질문
Why does my mailto subject show plus signs instead of spaces?
You encoded it with urlencode(), which turns spaces into '+'. RFC 6068 requires %20 for spaces in mailto URIs. Use rawurlencode() or http_build_query() with PHP_QUERY_RFC3986, which produce %20.
How do I add a line break to a mailto body?
RFC 6068 requires line breaks in the body to be encoded as %0D%0A. In PHP, write the body with "\r\n" and pass it through rawurlencode() or http_build_query() with PHP_QUERY_RFC3986.
Can a mailto link include an attachment?
No. RFC 6068 defines no attachment field, and mail clients ignore attachment parameters for security reasons. To accept files, use an upload form that posts to your PHP backend and send the email server-side.
Why does PHP mail() return true but no email arrives?
true only means the local mail transfer agent accepted the message. It can still be dropped, deferred, rejected for missing SPF or DKIM, or filtered as spam. Check the MTA log, usually /var/log/mail.log, or switch to an API that reports delivery status.
Is PHP mail() safe for contact forms?
Only with strict validation. Never place user input in headers without validating it with filter_var() and rejecting CR and LF characters, or attackers can inject Bcc headers. Put the visitor's address in Reply-To, not From, and rate-limit the form.
1차 출처
- PHP: mail - Manual — PHP Group
- RFC 6068: The 'mailto' URI Scheme — IETF / RFC Editor
- PHP: http_build_query - Manual — PHP Group
- PHPMailer — PHPMailer project
- Email sender guidelines — Google
- Errors and retries — SendHQ