Supabase
Supabase Auth users become contacts, and authentication emails go out through Postal with the Send Email Hook.
The Supabase integration has two parts, on the same connection: Supabase Auth users become contacts (with sign-up, confirmation and deletion events), and your app's authentication emails go out through Postal with the Send Email Hook.
#Create the connection
In the dashboard, open Integrations › Your app and click Connect on the Supabase card. The connection has its own URL, which you paste into the provider, and it shows the step-by-step instructions on the same screen.
https://api.postal.so/integrations/crm/01a1c2d3-e4f5-7a6b-8c9d-0e1f2a3b4c5d/webhook
In Supabase, open Integrations › Database Webhooks and create a webhook on the auth.users table with Insert, Update and Delete. Under HTTP Request, choose POST and paste the connection URL. Under HTTP Headers, add:
X-Postal-Secret: whsec_… (the secret shown on the connection)
The secret belongs to the connection and can be rotated on its screen (the old one stops working). Authorization: Bearer <secret> works too.
App integrations count toward your plan limit (app_integrations). If they are not in your plan, the cards appear locked with an Upgrade button.
#Events
| Change in auth.users | Postal event | Default |
|---|---|---|
INSERT | supabase.user.created | on |
UPDATE that confirms the email | supabase.user.confirmed | on |
UPDATE that only changes the last sign-in | supabase.user.signed_in | off |
Any other UPDATE (email, metadata) | supabase.user.updated | on |
DELETE | supabase.user.deleted (a deletion) | on |
The properties are user_id, provider, email_confirmed, created_at and last_sign_in_at. Changes to other tables are ignored.
#Which contact
The user is matched by the Supabase ID, stored in the contact's external_id: if the email changes, the same contact is updated. The name comes from the user metadata, from first_name and last_name (or from full_name / name):
await supabase.auth.signUp({
email: '[email protected]',
password,
options: { data: { first_name: 'Jane', last_name: 'Smith' } },
})
For deletions, the connection says what to do with the contact: only record the event (the default), unsubscribe the contact (it stays in the audience, but receives no marketing) or delete the contact (no event, since the contact is gone). A deletion never creates a contact that Postal did not have.
#Connection settings
- Events: only the checked ones become a contact and an event; the others are ignored (the response is still
202). - Sync the name: updates the contact's first and last name with what the provider sends.
- Add to segment and Subscribe to topic: a static segment and a topic for every contact that arrives.
- When the user is deleted in the app: only record the event, unsubscribe the contact or delete it.
Everything the connection changes on a contact carries the source crm:<connection id> (last_change_source), and each delivery appears in the connection's log, with the result for each person. A delivery that the provider repeats (same ID) is not processed twice.
#Login emails (Send Email Hook)
With the Send Email Hook, Supabase hands each authentication email to Postal instead of sending it. To turn it on:
- On the connection, under Login emails through Postal, turn sending on, choose the sender (from a verified domain of your team) and, if you want, the project URL (
https://abcd.supabase.co). - In Supabase, open Authentication › Hooks, add a Send Email hook of type HTTPS with the hook URL shown on the connection, and generate the secret.
- Copy the secret (
v1,whsec_…) and paste it into the connection. Save.
https://api.postal.so/integrations/supabase/01a1c2d3-e4f5-7a6b-8c9d-0e1f2a3b4c5d/send-email
| email_action_type | Default template (subject, in Portuguese) |
|---|---|
signup | Confirme seu cadastro (“Confirm your sign-up”) |
magiclink | Seu link de acesso (“Your sign-in link”) |
recovery | Redefina sua senha (“Reset your password”) |
invite | Você recebeu um convite (“You have been invited”) |
email_change | Confirme a troca de e-mail (“Confirm the email change”; with secure email change, one email goes to the current address and another to the new one) |
reauthentication | Seu código de confirmação (“Your confirmation code”) |
Each type has a template, written in Portuguese, that you edit on the connection (subject and HTML). Variables:
| Variable | What it is |
|---|---|
{{{CONFIRMATION_URL}}} | The confirmation link. With the project URL: https://abcd.supabase.co/auth/v1/verify?token=…&type=…&redirect_to=…. Without it: {site_url}/auth/confirm?token_hash=…&type=…&next=…, for those who confirm in their own app (SSR). |
{{{TOKEN}}} | The 6-digit code (OTP). |
{{{TOKEN_HASH}}} | The token hash, to build your own link. |
{{{EMAIL}}}, {{{NEW_EMAIL}}} | The user's email and, on an email change, the new email. |
{{{SITE_URL}}}, {{{REDIRECT_TO}}} | The project's Site URL and the destination after confirmation. |
The emails take the same path as POST /emails: verified domain, daily and monthly quotas and a priority queue. They carry the supabase_auth tag with the type and show up under Emails. Security notification types (password_changed_notification and others) are accepted without being sent.
The response follows what Supabase expects: {} with 200 when the email was queued, and, on errors, the status with the reason, which shows up under Logs › Auth in Supabase:
Whoever receives these login emails is also kept in the Audience, as an unconfirmed contact that gets no marketing until you confirm: see Automatic audience from transactional emails.
{
"error": {
"http_code": 403,
"message": "The example.com domain is not verified. Please, add and verify your domain."
}
}
| Status | When |
|---|---|
401 | Missing or invalid signature: the secret pasted into the connection is not the hook's. |
403 | The sender's domain is not verified, sending is paused or the account is suspended. |
409 | Login emails are turned off on the connection, or the connection is paused. |
422 | The connection has no sender, or the hook did not include an email. |
429 | The daily or monthly sending quota is used up. |
#Trigger automations
Each event is stored on the contact under the name supabase.user.created, with the properties from the table above. On the connection screen, the Create automation button next to each event opens a new automation with the Event received trigger already filled in. Through the automations endpoints, the trigger is {"type": "event", "event": "supabase.user.created"}.
Example: a welcome sequence starting at sign-up, and a reminder for anyone who has not confirmed their email after 2 days (with a condition on supabase.user.confirmed). In the emails, the event properties are available as {{event.amount}} in the steps or as template variables ({{{amount}}}).