Send Email from PHP: cURL to the API or PHPMailer over SMTP (Not mail())
Send email from plain PHP on your own domain: a cURL call to the API, PHPMailer on port 587 with STARTTLS, and why mail() lands in spam.
PHP's built-in mail() function hands the message to whatever sendmail the web server has, which sends it from the server's IP with no authentication and no signature. That is why contact-form mail from shared hosting lands in spam or never arrives. Two fixes exist: a cURL call to an HTTPS API, or PHPMailer talking to a real mailbox over SMTP. Both take about twenty lines, both send from your domain with SPF and DKIM passing, and both are shown here.
Quick answer
Put the API key in an environment variable or a .env file outside the web root, and POST JSON to https://api.oqumail.com/api/v1/emails with cURL: to as an array, subject, and html or text. Or install PHPMailer with Composer and set Host mail.oqumail.com, Port 587, SMTPSecure ENCRYPTION_STARTTLS, SMTPAuth true, with the mailbox login. Stop using mail().
Option 1: cURL to the API
<?php
// send.php (PHP 8+)
function oqumail_send(array $to, string $subject, string $html, ?string $fromName = null): array
{
$payload = ['to' => $to, 'subject' => $subject, 'html' => $html];
if ($fromName !== null) {
$payload['fromName'] = $fromName;
}
$ch = curl_init('https://api.oqumail.com/api/v1/emails');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('OQUMAIL_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status !== 202) {
throw new RuntimeException("OquMail send failed: $status $body");
}
return json_decode($body, true); // ['id' => ..., 'messageId' => ..., 'status' => 'queued']
}
$receipt = oqumail_send(['customer@example.com'], 'Your quote', '<p>Quote attached as a link below.</p>', 'Acme');
error_log('queued ' . $receipt['id']);Option 2: PHPMailer over SMTP
composer require phpmailer/phpmailer, then:
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require __DIR__ . '/vendor/autoload.php';
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'mail.oqumail.com';
$mail->Port = 587;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // not ENCRYPTION_SMTPS
$mail->SMTPAuth = true;
$mail->Username = 'app@yourdomain.com';
$mail->Password = getenv('OQUMAIL_SMTP_PASSWORD');
$mail->setFrom('app@yourdomain.com', 'Acme');
$mail->addAddress('customer@example.com');
$mail->Subject = 'Your quote';
$mail->isHTML(true);
$mail->Body = '<p>Quote attached as a link below.</p>';
$mail->AltBody = 'Quote attached as a link below.';
$mail->send();
} catch (Exception $e) {
error_log('Mail error: ' . $mail->ErrorInfo);
}Contact forms: three rules
- Never put the visitor's address in From. Send from your own mailbox and put their address in the body (the API has no Reply-To field; in PHPMailer you can use addReplyTo).
- Validate and rate-limit the form. One honeypot field and a per-IP limit of a few submissions an hour stop most abuse; the contact form guide has the rest.
- Keep the key and password out of the web root: a .env file one level up, loaded with vlucas/phpdotenv or set in the server config.
Free business email on your own domain
OquMail gives you up to 15 mailboxes on your domain — free — with guided SPF/DKIM/DMARC, webmail, IMAP/SMTP for any mail app, and a send API. Setup is a few DNS records; changes usually take effect within minutes, occasionally up to 48 hours. Start at oqumail.com.
Why mail() goes to spam
mail() sends from the web server's IP, which is not in your SPF record, with no DKIM signature, often with a From address the server rewrites. Gmail rejects such mail outright with 550 5.7.26. Any shared host where other sites have sent spam makes it worse. Sending through a mailbox on your domain fixes all of it in one move.
What the API answers
- 202 Accepted: the message is queued. The JSON carries id, messageId and status; store the id so a support question can be matched to the delivery log.
- 400: the body failed validation, usually to missing or not an array, or neither html nor text present.
- 401: the key is missing, revoked or mistyped.
- 403: the mailbox is suspended, has reached its daily limit of 250, or sent more than 10 messages in a minute ("Sending too fast"). Back off; do not retry in a tight loop.
- 422: the message was blocked by content checks; the response explains why. Do not retry unchanged.
- 429: requests arrived faster than the per-key limit. Honour the Retry-After header.
Common questions
cURL error 28 or 7: what is wrong?
28 is a timeout and 7 is "could not connect": the server cannot reach API (a way for your app or website to send email automatically).oqumail.com on port 443, usually a firewall or a missing CA bundle. Check with curl from the shell on the same server.
PHPMailer says "SMTP connect() failed".
Enable $mail->SMTPDebug = 2 to see the exchange. The common causes are port 465 with STARTTLS, port 587 with ENCRYPTION_SMTPS, or the host blocking outbound SMTP; see port 25 timeouts.
Can I send to several people at once?
Yes, to accepts several addresses, and cc and bcc exist too. New workspaces are capped at a few recipients per message until they have some history.
Want to see how your own domain is set up? Use the free SPF checker: it runs in your browser and stores nothing.