Skip to content

My app's email did not arrive

When the app says it sent an email and it never shows up, the panel shows what the mail server saw. Open the project, Sends tab (and Email for the connection test). For all projects at once use Email → Sends → Outgoing.

On the project’s Sends tab, Last connection attempt from the app shows when the app last talked to the mail server and whether it was accepted or refused. Never seen means the mail server logged no attempt at all: the app is not reaching it (wrong variables, email turned off, project not restarted).

The Refused attempts section lists what the server refused before accepting a message: wrong password, a temporary ban for too many attempts, TLS failure, sender not allowed, sending limit. Each row shows the time, the reason and the login used. Only attempts coming from the internal network (the project’s app or the panel itself) with the project mailbox login appear on the project: wrong passwords that came from the internet, from bots, are never blamed on the app. They show up on the global E-mail > Envios > Saídas page, in a separate list called External attempts. The “Test connection from the app” button does not count as an app attempt. TLS refusals carry no login and appear only on the global page.

Click the date of an attempt to open its details: reason, what to do, login, project, origin (internal network or internet) and the submission listener that refused it. Because the attempt was refused before the message entered the queue, there is no recipient or delivery response: those exist only for accepted sends, in the sends section above. Attempts are kept for 30 days (up to 300 a day per project, and one per minute for the same reason and login); the list shows the 50 most recent and counts the total. If saving fails, the attempt is saved again on the next round (up to 5 rounds; then the panel gives up and logs it). If the panel cannot read the sending mailboxes, it uses the last list it read to tie the attempt to a project; with no list at all, the attempt is kept without a project. The “Test connection from the app” button is recognised only while the panel stays up: after a restart, a refusal caused by the test may show up as an app attempt.

ReasonWhat to do
Username or password refusedThe password the app uses does not match the mailbox on the server. In E-mail, use Sync with the mail server (or reset the mailbox password) and restart the project. Also check MAIL_USERNAME and MAIL_PASSWORD (or link them to the panel’s email, below).
TLS connection did not completeThe app does not trust the mail server certificate, or uses an old TLS version. Use host mail.<your domain> on port 587 with STARTTLS.
Sender not allowedThe MAIL_FROM_ADDRESS address does not belong to the project’s mailbox. Use the mailbox address.
Not authenticatedThe app tried to send without a username and password.

With no sends in the last 24 hours, the Sends tab shows the Why nothing arrived? card with whatever applies to the project:

  • the project’s email is off, or the mailbox has no password;
  • the test inbox is on: the messages are there, they were not delivered;
  • email was turned on or changed but the project was not redeployed;
  • MAIL_MAILER is log or array: Laravel only writes the email to a file or discards it;
  • MAIL_HOST, MAIL_USERNAME or MAIL_PASSWORD are not linked to the panel’s SMTP (in Email → Link variables, link them to SMTP_HOST, SMTP_USER and SMTP_PASS);
  • the queue without a worker (item 5);
  • the server refused recent attempts (item 2).

On the project’s Email tab, Test connection from the app follows the same path as the app: it connects to mail.<your domain>:587, runs STARTTLS with certificate verification and logs in with the project’s mailbox. It sends no message and never shows the password. It stops at the first failing step and explains why. The test runs in the panel itself, which sits on the same Docker network as the projects; a network rule that exists only inside the app’s container is not covered.

If QUEUE_CONNECTION is not sync (for example database or redis; the Laravel 11 default is database) and no process runs queue:work, queue:listen or horizon, the panel warns We could not find a queue worker — queued e-mails and jobs may stay stuck. The search is static (compose files, Dockerfiles, supervisor configs and scripts, including subfolders such as docker/ and scripts/, and the values set in environment:, env_file and Dockerfile ENV), so a worker started some other way is not seen. Without a worker the emails sit in the jobs table. Two fixes:

  1. Set QUEUE_CONNECTION=sync in the project Variables (sends during the request: simplest, slower for the user).
  2. Add a worker service to docker-compose.yml:
services:
worker:
build: .
command: php artisan queue:work --sleep=3 --tries=3 --max-time=3600
restart: unless-stopped
depends_on:
- app

Use the same image and variables as the app service. Containers get the project variables on the next deploy.

By default Laravel writes errors to storage/logs/laravel.log inside the container, and the project’s Logs tab cannot see them. Set LOG_CHANNEL=stderr in the Variables and exceptions show up in the Logs tab. This is only a suggestion, not an error.

If the problem looks like the mail server or the proxy, see Server logs.