New guide: how to warm up a dedicated IP without hurting your reputation. Read now →
Log in
MigoSMTP
SMTP and APIUpdated 18 Oct 2026

Send Email from Your App: JavaScript, Python and PHP SMTP Examples With Error Handling

Working SMTP examples for Node.js, Python and PHP on port 587 with STARTTLS, plus credentials, text and HTML parts, header injection, attachments, retries, logging, safe testing and common mistakes.

AM
Written by Aarav Mehta
Head of Deliverability
Reviewed by Tomasz Kowalski
  • Published6 Oct 2026
  • Read time7 min
  • Length1,486 words
Share this guide

"Send email API JavaScript" and "Python email API" are searches from developers who want working code, not a feature matrix. The practical question is how to get a message from an application to an inbox in Node.js, Python or PHP, with proper authentication, errors handled and secrets kept out of the repository. This guide gives you minimal, correct SMTP examples for each language that connect to smtp.migosmtp.com on port 587 with STARTTLS, explains the pieces around them (credentials, from addresses, HTML and plain text, attachments, retries and logging), and points to our API reference for the HTTPS endpoints so that you use the exact request format from the documentation rather than from an article. We use SMTP in the examples because it works with the standard libraries each ecosystem already has and keeps your code portable between providers. The credentials shown are placeholders; use the values from your dashboard.

Secrets in envnever commit credentials or put them in client-side code
Port 587STARTTLS on smtp.migosmtp.com, with certificate validation on
Text and HTMLsend both parts, and handle errors and retries

Before you write code

  1. Verify your sending domain. Add the SPF and DKIM records from your dashboard domain settings. Copy them from the dashboard rather than from an article, and check them with the SPF, DKIM and DMARC checker. See authenticating your sending domain.
  2. Create credentials in the dashboard and store them as environment variables, for example SMTP_USER and SMTP_PASS.
  3. Choose a From address on your own domain, such as [email protected], with a monitored reply path.
  4. Confirm outbound port 587 is open from your server. Some hosts block it. See which SMTP port to use.

Node.js with Nodemailer

import nodemailer from "nodemailer";

const transporter = nodemailer.createTransport({
  host: "smtp.migosmtp.com",
  port: 587,
  secure: false,          // STARTTLS upgrades the connection
  requireTLS: true,
  auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
});

export async function sendEmail({ to, subject, text, html }) {
  const info = await transporter.sendMail({
    from: "Example <[email protected]>",
    to, subject, text, html,
  });
  return info.messageId;
}

Use secure: false with requireTLS: true for port 587; that means the connection starts in plain text and is upgraded with STARTTLS before credentials are sent. Create the transporter once and reuse it. The full walk-through, including pooling and errors, is in the Nodemailer guide.

Python with smtplib

import os, smtplib, ssl
from email.message import EmailMessage

def send_email(to, subject, text, html=None):
    msg = EmailMessage()
    msg["From"] = "Example <[email protected]>"
    msg["To"] = to
    msg["Subject"] = subject
    msg.set_content(text)
    if html:
        msg.add_alternative(html, subtype="html")

    context = ssl.create_default_context()
    with smtplib.SMTP("smtp.migosmtp.com", 587, timeout=20) as server:
        server.starttls(context=context)
        server.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
        server.send_message(msg)
    return msg["Message-ID"]

ssl.create_default_context() validates the server certificate, which you should never switch off to "make it work". The timeout prevents a stuck connection from hanging your worker. Python's EmailMessage builds correct multipart messages, so you do not need to assemble MIME by hand.

PHP with PHPMailer

use PHPMailerPHPMailerPHPMailer;
use PHPMailerPHPMailerException;

$mail = new PHPMailer(true);
try {
    $mail->isSMTP();
    $mail->Host       = 'smtp.migosmtp.com';
    $mail->SMTPAuth   = true;
    $mail->Username   = getenv('SMTP_USER');
    $mail->Password   = getenv('SMTP_PASS');
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
    $mail->Port       = 587;

    $mail->setFrom('[email protected]', 'Example');
    $mail->addAddress($to);
    $mail->Subject = $subject;
    $mail->isHTML(true);
    $mail->Body    = $html;
    $mail->AltBody = $text;
    $mail->send();
} catch (Exception $e) {
    error_log('Mail error: ' . $mail->ErrorInfo);
}

If your site runs on WordPress or another CMS, a plugin or built-in setting may be easier than code. See WordPress SMTP setup.

Sending through the HTTPS API

If you prefer HTTP requests to SMTP, use our email API. The exact endpoint, authentication header and JSON fields are documented in the API reference; follow that page, not examples from other services, because request formats differ between providers. For the pros and cons of each route, see email API vs SMTP relay.

Details that matter in every language

Always send a plain-text part

A multipart message with text and HTML is more compatible and tends to be treated more favourably than HTML only. Keep the two versions equivalent.

Use a real From and a Reply-To you monitor

Send from a domain you have authenticated. If replies should go elsewhere, set Reply-To explicitly. See no-reply addresses.

Prevent header injection

Never put raw user input into headers such as To, Subject or From. Validate addresses and strip line breaks. Most libraries reject newlines in headers, but check yours. Use email validation at signup, and see the free email validator.

Attachments

Keep attachments small, set the right MIME type and consider linking to a file instead for anything large. Providers and recipients impose size limits that differ; check them for your use case.

Timeouts, retries and idempotency

FailureWhat to do
Connection timeout or temporary error (4xx)Retry with exponential backoff and a limit
Permanent error (5xx) such as an invalid recipientDo not retry; record the failure and suppress the address if appropriate
Authentication failureAlert the team; do not retry
Unknown outcome after sendingCheck logs before retrying to avoid duplicates

Do the sending in a background job or queue, not inside a web request, so that a slow connection never blocks your user. See SMTP bounce codes explained for interpreting errors.

Logging

Log the message identifier, recipient domain and outcome, but not full bodies or secrets. Store the identifier in your database so you can match later events from webhooks. See webhooks.

Structure your sending code

Whichever language you use, resist calling the mail library from many places. Create one module with a function such as sendEmail that takes a small, provider-neutral object: recipient, subject, text, HTML and optional metadata such as a template name or user identifier. Everything else, including the host, port, credentials and library calls, lives behind that function. This gives you a single place to add logging, rate limiting, retries and suppression checks, and it means that a later change of provider, library or transport is a change to one file. It also makes testing simple, because your tests can replace the module with a fake that records messages instead of sending them.

Keep message construction separate from sending. Build subject and bodies in template functions that return text and HTML, then pass them to the sender. Store templates in version control, escape any user-supplied values inserted into HTML and keep a plain-text version that reads well on its own. When a message is triggered by a user action, such as a password reset, put it on a queue and return to the user straight away. A background worker then sends it, retries temporary failures and records the result. This pattern keeps your application responsive when the mail connection is slow and prevents duplicate sends when a user refreshes a page.

Rate limits and bursts

Sending thousands of messages in a tight loop invites throttling and can look abusive to receiving servers. Use a queue with a worker that sends at a steady pace, and spread large batches over time. If you are new to a sending domain, increase volume gradually; see the warm-up guide. For marketing sends, keep them away from your transactional queue so that a large campaign never delays a password reset.

Test safely

  • In development, use a sandbox or a mail-catching tool so that you never email real users. See testing email in staging.
  • Before launch, send to your own mailboxes at several providers and read the authentication results in the headers. See reading email headers.
  • Start with low volume and increase gradually. See warm-up guide.

Before you go live

  • Send a test message from the production environment to mailboxes at several providers and confirm SPF, DKIM and DMARC all pass in the headers.
  • Confirm that bounces and complaints reach your system, either through webhooks or by reviewing the dashboard, and that hard-bounced addresses are not retried.
  • Check that the reply address is monitored and that unsubscribe links work for any marketing mail.
  • Set an alert for authentication failures and for a sudden drop in successful sends, so that a rotated password or expired key is noticed in minutes, not days.
  • Write down where the credentials are stored and who can rotate them.

Common mistakes

  • Hard-coding credentials in the repository.
  • Disabling certificate checks to get past an error.
  • Using port 465 or 25 with settings meant for 587. Use the relay on 587 with STARTTLS as shown.
  • Creating a new connection for every email in a loop; reuse a connection or queue the work.
  • Sending HTML only.
  • Sending from an unauthenticated domain or a free mailbox address.
  • Ignoring errors, so failed sends disappear silently.

Send your first message Create an account, verify your domain and use the SMTP settings above, or follow the API reference for HTTPS requests.

Create an account

References and further reading

Frequently asked questions

Which port should I use to send email from my app?

Use port 587 with STARTTLS on smtp.migosmtp.com. Do not switch off certificate validation to work around errors.

Should I use the SMTP relay or the HTTPS API?

SMTP works with the standard library of nearly every language and keeps code portable. The HTTPS API offers richer per-message options. Follow the API reference for exact request formats.

Why should I send both text and HTML?

A multipart message is more compatible with email clients and filters than HTML only. Keep both versions equivalent.

How should I handle sending errors?

Retry temporary errors with exponential backoff, do not retry permanent errors, alert on authentication failures and check logs before retrying unknown outcomes to avoid duplicates.

How do I keep credentials safe?

Store them in environment variables or a secret manager, never in source control or client-side code, and use separate credentials for each environment.

Topics#send email#javascript#python#php#smtp#code examples#developers
Was this guide helpful?Your answer helps us improve our documentation.

Start sending in minutes.

Create an account, add your domain and send your first email.

Get started Talk to sales