# Mail

Scaleway Transactional Email as the default mailer, Lettermint as the other, the DNS records, copies, bounces and the Mail page.


A production environment with a domain of its own sends mail through Scaleway Transactional Email, in your own Scaleway project, the way Vapor gives an application SES. You can choose Lettermint instead, with your own account, or keep the mailer your application has. Brewless never replaces a mailer you set yourself.

## Which mailer an environment gets

- **A production environment with a domain** gets Transactional Email at its next deploy, sending from the first of its `domains` that is not a wildcard, unless `brewless.yml` says otherwise.
- **Another kind of environment** gets a mailer only when `brewless.yml` names one under `mail`.
- **An environment with a `MAIL_MAILER` of its own**, in its variables or under `variables` in `brewless.yml`, keeps it. The default leaves it alone, and a file that asks for `mail` next to it is refused, with that reason, before anything is made.
- **`mail: false`** gets no mailer from Brewless.

`brewless plan` shows a `mail` line before a deploy, with a note when mail comes by default. `brewless init` asks which mailer a new production environment should have, or takes `--mail=scaleway`, `--mail=lettermint` or `--mail=none`, and writes the answer into `brewless.yml`.

## In brewless.yml

```yaml brewless.yml
environments:
  production:
    kind: production
    domains: [shop.example.eu]
    # Left out: Transactional Email from shop.example.eu, copies kept 7 days.
    mail:
      mailer: scaleway
      domain: mail.example.eu
      keep_days: 7
  staging:
    mail: lettermint
```

`mail` is `scaleway`, `lettermint`, `false`, or a map with `mailer`, `domain` and `keep_days`. Once Brewless has given an environment a mailer, another mailer or domain in the file is refused: change it in the variables yourself if you mean to.

## What Brewless sets up at Transactional Email

Transactional Email is only in Paris (`fr-par`). It sends for environments in every region from there.

- **An offer.** A project needs one before it can have a domain. Brewless asks for Essential, which has no monthly fee, when the project has none, and leaves an offer the project has alone. It never asks for Scale: that is yours to choose at Scaleway.
- **The domain**, without Scaleway's autoconfiguration, which would also write a blackhole MX record and take the mail of a domain that receives mail.
- **An identity that may only send** (`TransactionalEmailEmailFullAccess` on the project), whose key goes into the environment's Secret Manager.
- **The variables** of Laravel's own `smtp` mailer, so the application needs no package: `MAIL_MAILER=smtp`, `MAIL_HOST=smtp.tem.scaleway.com`, `MAIL_PORT=587`, `MAIL_USERNAME` (the ID of the project), `MAIL_PASSWORD` (the key of that identity) and `MAIL_FROM_ADDRESS=hello@` the domain. A variable you set yourself keeps its value.

A Scaleway key that may not use Transactional Email, or may not make identities, sets the environment up without a mailer and says why; the application keeps the mailer it has. Mail never stops an environment from being set up, and the default does not try again on every deploy. Writing `mail` in `brewless.yml` does.

> [!NOTE]
> Essential includes 300 mails a month and costs €0.25 per 1,000 after that, billed by Scaleway. Every recipient of a mail, CC included, counts as one mail. A new project can send 10,000 mails a month at first; Scaleway raises that after a support request.

## The DNS records

Transactional Email sends from a domain once it has found three records there: SPF, DKIM and DMARC. Brewless works them out against the zone of the domain:

- **SPF**: the include of Transactional Email goes into the SPF record the domain has, because a domain may have only one; a new one when it has none.
- **DKIM**: the key of your project, at its own name.
- **DMARC**: only when the domain has none. A policy you chose stays.
- **MX** is never written, so the mail the domain receives keeps arriving.

When the zone is one Brewless manages (see [DNS records](/running/dns-records)), a deploy names the records that are missing and writes them after a yes, or with `brewless deploy --mail-records`. The Mail page in the console has the same button. Each change goes to the audit trail, and Scaleway is asked to check the domain again; that takes a few minutes. When the zone is kept elsewhere, the deploy and the console name the records for you to add there. A record Scaleway already found counts as there.

## Copies of the mails

Transactional Email keeps no content and no headers of a mail. So the environment gets a private bucket of its own that Object Storage empties after `keep_days` (7 by default, at most 30, `0` keeps none), and a key that may only put there. `brewless/laravel` 0.15 or later puts the whole mail there after it is sent, headers and content as it went out, named by the ID Transactional Email queued it under. Storing a copy never stops a mail: when it fails, it is only logged.

The copies stay in your own bucket. The console reads one when you open a mail, and Brewless keeps nothing of it.

## Bounces and complaints in your application

Transactional Email reports what became of each mail to a topic of Scaleway Topics and Events that Brewless makes for the environment, with a webhook of the domain. Once a release answers, the deploy subscribes the topic to `/_ops/mail-events` of the application, and asks again on every deploy until the application has confirmed it. `brewless/laravel` takes a message there only from that topic and only with Scaleway's signature, and turns each into a `MailEvent`:

```php app/Providers/AppServiceProvider.php
use Brewless\Laravel\Mail\MailEvent;
use Illuminate\Support\Facades\Event;

Event::listen(MailEvent::class, function (MailEvent $event): void {
    if ($event->bounced() || $event->complained()) {
        // $event->to, $event->type, $event->error
    }
});
```

`bounced()` is true for a mail to an address without a mailbox, one that was dropped or one on Scaleway's blocklist; `complained()` for one marked as spam. Nothing of it passes through Brewless.

## Lettermint

With `mail: lettermint`, Brewless sets `MAIL_MAILER=lettermint` and `MAIL_FROM_ADDRESS`, and names `LETTERMINT_PROJECT_TOKEN` as a variable for you to set: push it with `brewless env:push`. The application needs the `lettermint/lettermint-laravel` package and a `lettermint` mailer in `config/mail.php`. Brewless makes nothing at Lettermint: the account, the project, the domain and its records are yours.

Connect a Lettermint Team API token on the Providers page to see that mail on the Mail page too (see [Providers](/applications/providers#lettermint)). Lettermint keeps the content of a mail for 28 days itself, so no copy is made.

## The Mail page

**Mail**, under your Scaleway project in the console, lists every domain your environments send from, at Transactional Email or at Lettermint: whether it sends, its reputation, and what the provider counted over the last 30 days. A domain's own page shows:

- its DNS records and whether the provider found them;
- the 50 newest mails, with their status and what came back, such as a hard bounce;
- the addresses the provider no longer sends to, and why.

A mail opens beside the list with every attempt to deliver it and the answer of the receiving server, all its headers, its attachments by name, and a preview. The preview is a page of its own in a sandboxed frame: no script runs and no image or font is loaded from elsewhere, so opening a mail tells no sender it was read.

All of it is read from the provider when you open the page. Brewless caches the totals and the state of a domain for a minute, and keeps none of the mails or addresses, not even in a cache.
