Skip to content

Documentation navigation

Email triggers

Run an agent when mail arrives, with trusted senders and an email gateway.

#Start an agent when email arrives

An email trigger connects an inbox to an agent you run. You choose which senders can start it, register the declaration, and keep the agent serving. For local development, the mock gateway can replay signed email fixtures without a mail account, SMTP server, tunnel, or paid service.

#Create the connection

In Studio, open your app's email trigger and use Add a connection in its setup checklist. You can also use Config → Triggers → Connections. In the Developer Portal, open your project, then Integrations → Connections → Add connection. Select Email inbox, enter a name, and create it.

Save the connection ID and the signing secret before dismissing the setup panel. The secret is shown once; give it only to the gateway that forwards mail. Email senders do not need it. The connection's dropzone_local_part is a stable inbox identity of the form p- followed by 32 hexadecimal characters.

The displayed inbox address requires a configured inbound email domain. If the screen says the domain is not configured, you can still run local signed fixtures, but there is no usable public mailbox yet. A configured address also needs working mail routing and a gateway; displaying an address alone does not prove that mail can reach it.

Without either UI, create the connection with a private password prompt:

Shell
maivn connections create --project-id YOUR_PROJECT_ID --email YOU@example.com --name "Support inbox" --kind email_inbox --secret-file ~/.maivn-private/inbox.json

The terminal receipt includes the inbox identity and nullable email address; the private JSON file also holds the one-time signing secret. An address of null means the inbound domain is not configured. See the private-directory requirements and remote HTTPS options in Terminal connection setup.

You can also use the existing signed-in connection API described in Terminal connection setup. Create with this JSON body at POST /v1/projects/{project_id}/connections:

JSON
{"display_name":"Support inbox","setup_kind":"email_inbox"}

The create response contains connection.connection_id, connection.dropzone_local_part, nullable connection.email_address, one_time_secret, and the gateway callback path. Connection management uses a human session and CSRF protection; the agent's project API key serves a different purpose.

#Declare the trigger

Add this to the module containing your configured Agent. Replace the connection and target IDs with values from the same project:

Python
email_trigger = (
    agent.on_email("conn_your_inbox", sender_allowlist=("alerts@example.com",))
    .key("inbox-triage")
    .named("Inbox triage")
    .target("your-agent-id", version=1)
    .asking("Summarize this email: {{event.data.text_excerpt}}")
    .display_in_app()
)

Use actual trusted senders. A pattern such as *@your-company.example allows that domain. The example address is a placeholder, not an invitation to accept arbitrary mail. The gateway also supplies sender authentication results; a matching allowlist alone is insufficient for mail that fails authentication.

Declaring the trigger makes it discoverable without registering it. In Studio, reload the app and follow its setup checklist to start the agent and turn the trigger on. Without Studio, first follow Serve your agent: start maivn serve with the same project, agent ID, and version, and wait for Announced. A new target must be announced before the platform can register its trigger.

Keep that terminal running. In a second terminal, call email_trigger.invoke() once from a registration script with the same project's API key configured. Do not call it on every import of your serving module.

The platform operator must enable email trigger delivery for your project. Registration and connection creation do not change that rollout setting. If setup reports that delivery is disabled or could not be checked, resolve that condition before relying on incoming mail to start runs.

Keep the registered agent ID and version running. Follow Serve your agent for the terminal command and local plane addresses, or use Studio's serving action.

#Test locally

From tooling/mock-gateway in a platform source checkout, run:

Shell
uv run --frozen maivn-mock-gateway replay-email --connection-id conn_your_inbox --dropzone p_your_dropzone --sender alerts@example.com --message-id "<local-email-1@example.com>" --repeat 2

Replace p_your_dropzone with the exact p-… value returned during setup. The command prompts for the signing secret without echoing it. For automation, provide one line on stdin or use --secret-env NAME to name an environment variable containing the secret. Never put the secret in a command-line argument.

The first delivery should return 202 with accepted: true, duplicate: false, and suppressed: false. Its repeat should return 200 with duplicate: true. Then open the trigger's run history: one completed run proves that the agent received it. An accepted HTTP response alone does not prove execution.

Use a fresh Message-ID for each new test. To check the allowlist, use a sender that is not allowed by any trigger on this inbox. To check authentication suppression, add --sender-authentication fail. These requests return a suppressed receipt and should create no run. Changing MIME content while reusing an existing Message-ID returns 409; that identity belongs to the original message.

The fixtures contain synthetic authentication assertions. They test the signed gateway path, allowlist, receipt, and agent execution; they do not test real SPF, DKIM, DMARC, SMTP, or MX routing.

#Connect real mail

Your platform operator configures MAIVN_INBOUND_EMAIL_DOMAIN, routes mail for the resulting inbox addresses, and supplies a gateway that verifies sender authentication and forwards the signed email envelope. The gateway sends the original MIME message and authentication results to the connection callback. It signs the exact JSON bytes using HMAC-SHA256 and sends X-Maivn-Email-Signature: sha256=<hex digest> plus X-Maivn-Email-Message-ID. The replay command is a runnable local example of this envelope and signature format.

For an external gateway reaching a local platform, use the free receiver tunnel with the email connection ID. Give the gateway the complete HTTPS callback printed by maivn dev-tunnel. Keep its path unchanged. The tunnel forwards HTTP; it does not receive SMTP or configure a domain's MX records.

#Find the cause of a missing run

ObservationCheck next
404 from the callbackExact connection ID, active connection, signing key, raw bytes, and required headers. Refusals intentionally do not reveal which check failed.
409Use a new Message-ID for changed message content.
suppressed: trueSender allowlists and the gateway's authentication results.
Accepted, but no runProject email rollout, enabled trigger, matching connection and sender, then serving agent ID/version.
Run is pendingThe agent must be announced and claiming work from the same address.
Run completed, but no public mail arrivedLocal replay proves no SMTP or mailbox delivery. Check the real gateway and domain routing separately.

Turning a trigger off stops future runs. Revoking the connection prevents future gateway deliveries from being admitted and preserves its record for inspection.