# How to Send Transactional Email from a Quake AI Workload

Source: https://docs.quake.ai/docs/operate/how-to/send-transactional-email
Markdown: https://docs.quake.ai/docs/operate/how-to/send-transactional-email.md

---

# How to send transactional email from a Quake AI workload

Send password resets, receipts, and other per-user transactional email from an application running on a Quake AI instance by relaying through a third-party email provider over an authenticated submission port.



Quake AI does not run a managed transactional-email service. It applies an anti-abuse posture on outbound mail: port 25 (the server-to-server SMTP port) is restricted on instances. Send transactional email through a third-party provider over an authenticated submission port (587 or 465). This page covers that relay pattern.





This page covers relay through a third-party email provider. For the support-ticket path that sends mail from your instance's own IP, see [Sending mail (SMTP) with your virtual machine](/docs/operate/configuration/smtp).



## Before you start

This procedure assumes you have:

- An application running on a Quake AI instance that needs to send email.
- A domain you control, with access to edit its DNS records at your registrar or DNS host.
- An account with one of the email providers compared below.

The application sends outbound connections only; it does not receive inbound mail. For inbound delivery, point your domain's MX records at your provider and follow the provider's inbound routing documentation.

## Pick an email provider

Each provider exposes an authenticated SMTP submission endpoint and an HTTP API. The SMTP path works the same from any language, so this page uses it as the common shape. Pick based on the workload:

| Provider | When to choose it |
|---|---|
| `Postmark` | Transactional mail only, with separate message streams that stop transactional and broadcast traffic from sharing a sending reputation. |
| `SendGrid` | Transactional and marketing mail through one account, with event webhooks and per-message analytics. |
| `Mailgun` | Developer-focused service with inbound routing, address validation, and message tagging for analytics. |
| `AWS SES` | Low-level sending service that fits workloads already integrated with AWS; new accounts start in a sandbox that limits sending to verified addresses until you request production access. |

The steps below apply to any of the four. Where the provider-side setup diverges, the per-provider tabs call out the difference.

## Step 1. Create a sending credential at your provider

Create an SMTP credential (a username and password pair) in the provider console, then record the submission host and port. Treat the credential as a secret from the moment you create it.




1. In the Postmark console, open your server and select **API Tokens**.
2. Copy the **Server API Token**. Postmark uses this token as both the SMTP username and the SMTP password.
3. Note the submission endpoint: host `smtp.postmarkapp.com`, port `587`.




1. In the SendGrid console, select **Settings** > **API Keys** > **Create API Key**.
2. Give the key **Mail Send** permission and copy the generated value. You see it once.
3. For SMTP, the username is the literal string `apikey` and the password is the API key value.
4. Note the submission endpoint: host `smtp.sendgrid.net`, port `587`.




1. In the provider console, select **Sending** > **Domain settings** > **SMTP credentials** for your domain.
2. Create a credential and copy the password. The username is `postmaster@YOUR_SENDING_DOMAIN`.
3. Note the submission endpoint: host `smtp.mailgun.org`, port `587`.




1. In the SES console, select **SMTP settings** > **Create SMTP credentials**. This creates an IAM user whose SES SMTP password derives from its secret key.
2. Copy the **SMTP username** and **SMTP password**.
3. Note the regional submission endpoint, for example host `email-smtp.us-east-1.amazonaws.com`, port `587`.




The submission endpoints share a common shape:

| Provider | Submission host | Port | SMTP username |
|---|---|---|---|
| `Postmark` | `smtp.postmarkapp.com` | 587 | the Server API Token |
| `SendGrid` | `smtp.sendgrid.net` | 587 | `apikey` |
| `Mailgun` | `smtp.mailgun.org` | 587 | `postmaster@YOUR_SENDING_DOMAIN` |
| `AWS SES` | `email-smtp.YOUR_REGION.amazonaws.com` | 587 | the SES SMTP username |

Port 587 uses STARTTLS to upgrade the connection to TLS. Port 465 carries TLS from the first byte. Both are authenticated submission ports. Use 587 unless your client library only supports implicit TLS, in which case use 465.

## Step 2. Store the credential as an application secret

Keep the SMTP username and password out of your source tree and out of your instance image. Inject them at runtime as environment variables, or read them from a secrets manager your application already uses.

```bash
# Set on the instance or in your deployment pipeline, not in source control.
export SMTP_HOST="smtp.postmarkapp.com"
export SMTP_PORT="587"
export SMTP_USERNAME="YOUR_SMTP_USERNAME"
export SMTP_PASSWORD="YOUR_SMTP_PASSWORD"
```



Do not commit the credential to a repository, bake it into a snapshot or custom image, or paste it into a cloud-init script that lands in instance metadata. A leaked submission credential lets a third party send mail as your domain and burn its reputation.



## Step 3. Allow outbound submission traffic

By default, a Quake AI security group permits the outbound traffic your instance needs to reach the provider. If you have tightened egress rules, add an egress rule that allows TCP to the submission port your provider uses (587 or 465).

```bash
openstack security group rule create \
  --egress --protocol tcp --dst-port 587 \
  --remote-ip 0.0.0.0/0 \
  YOUR_SECURITY_GROUP
```

For the Console procedure and the full set of options, see [Create security group rules](/docs/network/how-to/create-security-group-rules). Confirm that no rule on your instance blocks outbound port 587 or 465 before you move on.

## Step 4. Configure your application or mail server to relay

Point your application at the submission endpoint using the credential from Step 2. Configure it at the application layer if your code sends mail directly, or at the system layer if your services hand mail to a local mail server such as Postfix.




Most SMTP client libraries accept the host, port, username, and password directly. This example uses Python's standard library:

```python
import os
import smtplib
from email.message import EmailMessage

message = EmailMessage()
message["From"] = "no-reply@example.com"
message["To"] = "user@example.com"
message["Subject"] = "Your receipt"
message.set_content("Thanks for your order.")

with smtplib.SMTP(os.environ["SMTP_HOST"], int(os.environ["SMTP_PORT"])) as smtp:
    smtp.starttls()
    smtp.login(os.environ["SMTP_USERNAME"], os.environ["SMTP_PASSWORD"])
    smtp.send_message(message)
```




To route mail from local services through the provider, configure Postfix as a relay. Set the relay host and enable SASL authentication in `/etc/postfix/main.cf`:

```ini
relayhost = [smtp.postmarkapp.com]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = encrypt
```

Store the credential in `/etc/postfix/sasl_passwd`, hash it, restrict its permissions, and reload Postfix:

```bash
echo "[smtp.postmarkapp.com]:587 YOUR_SMTP_USERNAME:YOUR_SMTP_PASSWORD" \
  | sudo tee /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd
sudo chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
sudo systemctl reload postfix
```




## Step 5. Authenticate your sending domain in DNS

Mailbox providers check three DNS records to decide whether to accept your mail. Add them at the DNS host for your sending domain. Your email provider generates the exact values; the records below show the shape.

- **SPF** authorizes the provider to send for your domain. Publish one TXT record at the domain root that includes the provider's SPF host:

```dns
example.com.  IN  TXT  "v=spf1 include:PROVIDER_SPF_HOST -all"
```

- **DKIM** signs each message so the receiver can verify it was not altered. Your provider gives you one or more CNAME or TXT records to publish under a selector subdomain:

```dns
SELECTOR._domainkey.example.com.  IN  CNAME  SELECTOR.dkim.PROVIDER_DOMAIN.
```

- **DMARC** tells receivers what to do when SPF or DKIM fails and where to send reports. Start with a monitoring policy, then tighten to `quarantine` or `reject` once your reports are clean:

```dns
_dmarc.example.com.  IN  TXT  "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com"
```

After you publish the records, verify them in your provider's domain settings. Each provider shows a verified status once the records resolve. DNS changes take effect after the record's TTL elapses.



If you manage this domain elsewhere and have not added records for it yet, follow [How to point a domain at a Quake AI resource](/docs/network/how-to/point-domain-to-quake-ai) for registrar `A` and `CNAME` records.





Authentication records get your mail accepted; sender behavior keeps it out of the spam folder. Work through this list before you send to real recipients at volume:

- **Use a subdomain for application mail**, for example `mail.example.com`, so transactional sending does not affect the reputation of your primary domain.
- **Set a reply-to address that a person reads**, even when the From address is `no-reply@example.com`.
- **Warm up new sending volume gradually.** A new domain that sends thousands of messages on day one looks like a spam source. Ramp volume over the first weeks.
- **Honor unsubscribe and bounce signals.** Stop sending to addresses that hard-bounce or mark you as spam. Most providers expose these as suppression lists and webhooks.
- **Separate transactional and marketing streams.** Keep receipts and password resets on a different stream or subdomain from bulk campaigns so one does not damage the other's reputation.
- **Set a PTR record where you send from your own IP.** This applies to the support-ticket path on the [SMTP from your VM](/docs/operate/configuration/smtp) page, not to provider relay, where the provider owns the sending IP.
- **Monitor provider reports.** Track bounce, complaint, and delivery rates in the provider dashboard and act on spikes.



## Step 6. Send a test message and verify delivery

Send one message to an address you control, then confirm it arrived and authenticated correctly.

```bash
# Quick check with swaks, an SMTP test client.
swaks --to user@example.com \
  --from no-reply@example.com \
  --server "$SMTP_HOST:$SMTP_PORT" \
  --auth-user "$SMTP_USERNAME" \
  --auth-password "$SMTP_PASSWORD" \
  --tls
```

A successful run ends with a `250` response code from the provider. In the received message, view the original headers and confirm that SPF, DKIM, and DMARC each show a `pass` result. Your provider's dashboard also records the message as delivered.

## Networking and floating IPs

This pattern allocates zero floating IPs. The workload makes outbound connections only, to the provider's submission endpoint. An instance on a private network reaches the internet through its router's external gateway using source NAT, which covers outbound submission traffic without a floating IP. Allocate a floating IP only if a separate part of your application needs inbound reachability; sending transactional email does not.

## See also

- [Sending mail (SMTP) with your virtual machine](/docs/operate/configuration/smtp): request an exception to send directly from your instance's IP through a support ticket.
- [Create security group rules](/docs/network/how-to/create-security-group-rules): manage egress and ingress rules for your instance.
- [Create a router](/docs/network/how-to/create-router): give a private network outbound internet access through an external gateway.
- [Shared responsibility model](/docs/security/shared-responsibility): what Quake AI operates and what you operate.
