KAHIBARO
Discord Login Register

Sending Emails

Why Sending Emails Matters in Backend Development

Most real applications need to send emails. Examples:

Backend code is usually responsible for triggering and sending these emails, often using an external email service.

This chapter focuses on how to actually send emails from backend code, what information an email needs, and how to avoid common pitfalls. It assumes you already understand the basics of “How Email Works” and “SMTP”.

Key idea: Your backend rarely sends email “directly” to the recipient. It usually hands the email to an SMTP server or email API, which then delivers it.


Core Concepts: What You Need To Send an Email

To send an email from a backend application, you always need three things:

  1. Transport configuration
    How and where to send the email, for example:
    • SMTP server hostname, port, username, password
    • Or an HTTP API endpoint and API key (like SendGrid, Mailgun)
  2. Message data
    The content and metadata of the email:
    • From address
    • To addresses
    • Subject
    • Body (plain text and/or HTML)
    • Optional: CC, BCC, Reply-To, attachments, headers
  3. Client library or implementation
    Something that connects to the SMTP server or email API and passes the message.

A simple mental model:


StepQuestionExample answer
1Where do I send this email?smtp.gmail.com:587 with user and password
2What is the email content?From: app@example.com, To: you@example.com
3How do I send it?Use smtplib in Python, or a service’s SDK

Email Message Structure in Practice

An email message can be split into:

  1. Headers: metadata
  2. Body: the actual content, which can be:
    • Plain text
    • HTML
    • Multipart (both text and HTML, plus attachments)

Example raw email structure (simplified):

text
From: "My App" <no-reply@myapp.com>
To: alice@example.com
Subject: Welcome to My App!
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Hello Alice,
Welcome to My App!
Thanks,
The My App Team

For HTML:

text
Content-Type: text/html; charset="utf-8"
<html>
  <body>
    <p>Hello <b>Alice</b>,</p>
    <p>Welcome to <strong>My App</strong>!</p>
  </body>
</html>

Most real emails are multipart and include both text and HTML so that:

Sending Emails with SMTP (Conceptual Flow)

When your backend sends an email via SMTP, this is the simplified sequence:

  1. Open a connection to the SMTP server (for example smtp.example.com on port 587).
  2. Start TLS encryption if required.
  3. Authenticate with a username and password or special SMTP credentials.
  4. Provide sender and recipients.
  5. Send the message content.
  6. Close the connection.

In pseudocode:

python
connect_to_smtp(server, port)
use_tls()
login(username, password)
send_mail(from_address, to_addresses, message)
disconnect()

Important rule: Never hard-code SMTP passwords or API keys in your code. Store them in environment variables or a secrets manager.


A Minimal Python SMTP Example

This example shows a simple email send using Python’s standard library. In a real project you would move this into a function or a dedicated module.

python
import smtplib
from email.message import EmailMessage
SMTP_HOST = "smtp.example.com"
SMTP_PORT = 587
SMTP_USER = "smtp-user"
SMTP_PASSWORD = "smtp-password"
def send_welcome_email(recipient_email: str) -> None:
    msg = EmailMessage()
    msg["From"] = "My App <no-reply@myapp.com>"
    msg["To"] = recipient_email
    msg["Subject"] = "Welcome to My App!"
    msg.set_content(
        "Hello,\n\n"
        "Welcome to My App!\n\n"
        "Thanks,\n"
        "The My App Team"
    )
    # Optional HTML alternative
    msg.add_alternative(
        """\
        <html>
          <body>
            <p>Hello,</p>
            <p>Welcome to <strong>My App</strong>!</p>
            <p>Thanks,<br>The My App Team</p>
          </body>
        </html>
        """,
        subtype="html"
    )
    with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as smtp:
        smtp.starttls()
        smtp.login(SMTP_USER, SMTP_PASSWORD)
        smtp.send_message(msg)

Key points:

Using an Email Service Provider (API-Based)

Many production systems use Email Service Providers (ESPs) instead of raw SMTP, for example:

Common advantages:

Typical workflow:

  1. Create an account and verify your domain.
  2. Get an API key.
  3. Use the provider’s HTTP API or SDK from your backend.

A very generic example of sending an email via a fictitious email API:

python
import requests
API_KEY = "your-api-key"
API_URL = "https://api.emailservice.com/v1/send"
def send_reset_email(to_email: str, reset_link: str) -> None:
    data = {
        "from": "My App <no-reply@myapp.com>",
        "to": [to_email],
        "subject": "Reset your password",
        "text": f"Click this link to reset your password: {reset_link}",
        "html": f"<p>Click <a href='{reset_link}'>this link</a> to reset your password.</p>",
    }
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    }
    response = requests.post(API_URL, json=data, headers=headers)
    response.raise_for_status()

You do not need to manage SMTP details here. The provider handles them.


Generating Dynamic Email Content

Most application emails contain dynamic data, for example:

Instead of building strings by hand, you usually use templates, similar to HTML templates for web pages.

Simple string formatting:

python
body = (
    "Hello {name},\n\n"
    "Your order #{order_id} has been shipped.\n"
    "Track it here: {tracking_url}\n\n"
    "Thanks,\n"
    "The Store Team"
).format(
    name=user_name,
    order_id=order_id,
    tracking_url=tracking_url,
)

Template example using a Jinja2-style template (conceptually):

html
<!-- order_shipped_email.html -->
<p>Hello {{ name }},</p>
<p>Your order <strong>#{{ order_id }}</strong> has been shipped.</p>
<p>You can track it here: <a href="{{ tracking_url }}">{{ tracking_url }}</a></p>
<p>Thanks,<br>The Store Team</p>

Pseudocode rendering:

python
html_body = template_engine.render(
    "order_shipped_email.html",
    {"name": name, "order_id": order_id, "tracking_url": tracking_url},
)

This keeps your email designs out of your Python code and makes them easier to edit.


Handling Common Email Fields

Backend code should know when and how to use these fields:

FieldPurposeExample
FromSender address, often no-replyMy App <no-reply@myapp.com>
ToMain recipient(s)alice@example.com
CCVisible copy to othersmanager@example.com
BCCHidden copy to othersaudit@example.com
Reply-ToAddress that gets repliessupport@myapp.com
SubjectShort summaryYour order has shipped

Example of adding CC and BCC with EmailMessage:

python
msg["From"] = "My App <no-reply@myapp.com>"
msg["To"] = "alice@example.com"
msg["Cc"] = "manager@example.com"
msg["Bcc"] = "audit@example.com"
msg["Subject"] = "Monthly report"

Note that some libraries treat BCC separately. For SMTP, recipients in Bcc must still be passed to the SMTP client, even if the header is not visible in the message.


Attachments (High-Level Overview)

Sometimes you need to send files:

Conceptually:

  1. Load file content.
  2. Attach it to the email with a MIME type and a filename.

Python example:

python
from email.message import EmailMessage
from pathlib import Path
def send_invoice_email(to_email: str, invoice_path: Path) -> None:
    msg = EmailMessage()
    msg["From"] = "Billing <billing@myapp.com>"
    msg["To"] = to_email
    msg["Subject"] = "Your invoice"
    msg.set_content("Please find your invoice attached.")
    file_bytes = invoice_path.read_bytes()
    msg.add_attachment(
        file_bytes,
        maintype="application",
        subtype="pdf",
        filename=invoice_path.name,
    )
    # Send via SMTP (omitting boilerplate for brevity)

Make sure you respect file size limits and security rules from the “Working with Files” and “File Upload Security” topics.


Testing Email Sending During Development

Sending real emails every time during development is slow and can be unsafe. Use these approaches:

  1. Local SMTP debug server
    Many languages and frameworks allow you to run a dummy SMTP server that prints emails to the console or a UI instead of delivering them.

Example with Python (from command line):

bash
   python -m smtpd -c DebuggingServer -n localhost:1025

Then configure your app:

python
   SMTP_HOST = "localhost"
   SMTP_PORT = 1025
  1. Sandbox mode / test mode
    Many email providers offer a sandbox mode where emails are not actually sent to real inboxes.
  2. Log-only mode
    For development, you can log email content instead of sending it:
python
   def send_email(msg: EmailMessage) -> None:
       if settings.ENV == "development":
           print("EMAIL WOULD BE SENT:")
           print(msg)
           return
       # production sending logic here

Important rule: Never send real user emails for automated tests or local development. Use test addresses or sandbox modes.


Handling Errors and Retries

Email sending can fail for several reasons:

In code you should:

  1. Catch exceptions around the send call.
  2. Log the error with enough context.
  3. Decide whether to:
    • Show a friendly message to the user.
    • Queue the email for retry in a background job.
    • Mark email as failed in the database.

Simple pattern:

python
import logging
logger = logging.getLogger(__name__)
def send_email_safe(msg: EmailMessage) -> bool:
    try:
        # send via SMTP or API here
        return True
    except Exception as exc:
        logger.exception("Failed to send email to %s", msg["To"])
        return False

In a production system, email sending is frequently moved to background jobs, so slow or failing email does not block user-facing requests. That connects directly to the “Background Processing” section in this course.


Configuration and Environment Separation

You will often run your application in different environments:

Each environment can have different:

Typical configuration table:

EnvironmentSMTP/ProviderExample from address
DevLocal debug SMTPdev-no-reply@myapp.local
StagingProvider sandbox accountstaging@myapp.com
ProdProvider production accountno-reply@myapp.com

Keep them in environment variables:

text
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_USER=myapp-smtp-user
EMAIL_PASSWORD=super-secret
EMAIL_FROM="My App <no-reply@myapp.com>"

Then load them in code. This keeps secrets out of your repository and makes deployments configurable.


Putting It All Together: A Simple Email Service Class

This is a more structured example you might use in a backend project:

python
import smtplib
from email.message import EmailMessage
class EmailService:
    def __init__(self, host: str, port: int, user: str, password: str, default_from: str):
        self.host = host
        self.port = port
        self.user = user
        self.password = password
        self.default_from = default_from
    def send(
        self,
        to: list[str],
        subject: str,
        text_body: str,
        html_body: str | None = None,
        from_address: str | None = None,
    ) -> None:
        msg = EmailMessage()
        msg["From"] = from_address or self.default_from
        msg["To"] = ", ".join(to)
        msg["Subject"] = subject
        msg.set_content(text_body)
        if html_body:
            msg.add_alternative(html_body, subtype="html")
        with smtplib.SMTP(self.host, self.port) as smtp:
            smtp.starttls()
            smtp.login(self.user, self.password)
            smtp.send_message(msg)
# Usage example
email_service = EmailService(
    host="smtp.example.com",
    port=587,
    user="smtp-user",
    password="smtp-password",
    default_from="My App <no-reply@myapp.com>",
)
email_service.send(
    to=["alice@example.com"],
    subject="Welcome!",
    text_body="Hello Alice,\nWelcome to My App!",
    html_body="<p>Hello <b>Alice</b>,</p><p>Welcome to My App!</p>",
)

This keeps email sending logic in one place and makes it easier to:

Summary

In backend applications, sending emails involves:

With these concepts, you can implement welcome emails, password resets, order confirmations, and other common email features in backend systems.

Views: 8

Comments

Please login to add a comment.

Don't have an account? Register now!