Events
What Fromly records on a contact's timeline by itself, and what each event carries. Your own events, sent through the API, sit beside these with the names and properties you give them.
How events work
Every event belongs to one contact and lands on their timeline. A live flow can start from most of them: by name for the lifecycle events, or through a trigger of its own for tags and lists.
An event recorded while importing history (a CSV import, the first sync of an integration) carries backfilled: true. It shows on the timeline and never starts a flow, so connecting an integration does not email everyone it brings in.
To send your own, see the API reference.
Contact
user.signed_upSigned up
Recorded by: The Supabase integration, for each new user. Your own code can also send it through the API.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
provider | string | `supabase` when the Supabase integration recorded it. |
userId | string | The id of the user in your Supabase project. |
{
"provider": "supabase",
"userId": "8f14e45f-ceea-467a-9c3b-2f1a4e1b5c6d"
}email.unsubscribedUnsubscribed
Recorded by: Fromly, when someone unsubscribes from every email, from the unsubscribe page or because their mailbox provider says they did.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
by | string | What did it: `ses` (a delivery report), `recipient` (the person, from an email), `admin` (the dashboard), `api` (the API, SDK or MCP). |
reason | string, optional | Why, as a short token (`hard-bounce`, `repeated-soft-bounce`, `spam-report`) or an admin's words. |
broadcastId | string, optional | The broadcast whose link they used, when it was one. |
{
"by": "recipient",
"broadcastId": "2b1c9d4e-7a3f-4c1e-9b8d-5f6a7e8c9d0a"
}contact.resubscribedResubscribed
Recorded by: Fromly, when someone who unsubscribed subscribes again from the unsubscribe page. Only they can.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
by | string | What did it: `ses` (a delivery report), `recipient` (the person, from an email), `admin` (the dashboard), `api` (the API, SDK or MCP). |
from | string | The status they left: `UNSUBSCRIBED`. |
to | string | The status they are in now, usually `SUBSCRIBED`. |
{
"by": "recipient",
"from": "UNSUBSCRIBED",
"to": "SUBSCRIBED"
}contact.bouncedEmail bounced
Recorded by: Fromly, when an address turns out not to exist, or failed three times in a row. Nothing is emailed to it any more.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
by | string | What did it: `ses` (a delivery report), `recipient` (the person, from an email), `admin` (the dashboard), `api` (the API, SDK or MCP). |
reason | string, optional | Why, as a short token (`hard-bounce`, `repeated-soft-bounce`, `spam-report`) or an admin's words. |
messageId | string, optional | The email that bounced. |
bounceSubType | string, optional | The kind of bounce, as the delivery report names it. |
softBounces | number, optional | Soft bounces in a row, when that is the reason. |
{
"by": "ses",
"reason": "hard-bounce",
"messageId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"bounceSubType": "NoEmail"
}contact.complainedReported spam
Recorded by: Fromly, when someone reports an email as spam. Nothing is emailed to them ever again.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
by | string | What did it: `ses` (a delivery report), `recipient` (the person, from an email), `admin` (the dashboard), `api` (the API, SDK or MCP). |
reason | string, optional | Why, as a short token (`hard-bounce`, `repeated-soft-bounce`, `spam-report`) or an admin's words. |
messageId | string, optional | The email they reported. |
complaintType | string, optional | The feedback type, when the mailbox provider sent one. |
{
"by": "ses",
"reason": "spam-report",
"messageId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"complaintType": "abuse"
}contact.suppressedBlocked
Recorded by: Fromly, when an admin blocks an address from the dashboard or the API.
Starts a flow: Never: history only
| Property | Type | Description |
|---|---|---|
by | string | What did it: `ses` (a delivery report), `recipient` (the person, from an email), `admin` (the dashboard), `api` (the API, SDK or MCP). |
reason | string, optional | Why, as a short token (`hard-bounce`, `repeated-soft-bounce`, `spam-report`) or an admin's words. |
{
"by": "admin",
"reason": "Asked us by phone"
}contact.restoredRestored
Recorded by: Fromly, when an admin unblocks an address or clears a bounce.
Starts a flow: Never: history only
| Property | Type | Description |
|---|---|---|
by | string | What did it: `ses` (a delivery report), `recipient` (the person, from an email), `admin` (the dashboard), `api` (the API, SDK or MCP). |
from | string | The status that was lifted: `SUPPRESSED` or `BOUNCED`. |
to | string | The status the contact is in now. |
{
"by": "admin",
"from": "BOUNCED",
"to": "SUBSCRIBED"
}Billing
trial.startedStarted a trial
Recorded by: The Stripe integration.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
subscriptionId | string | The Stripe subscription id. |
customerId | string | The Stripe customer id. |
status | string | The subscription status after the change, as Stripe names it. |
mrrCents | number | Monthly revenue after the change, in cents. |
currency | string | Three-letter currency code, lowercase. |
plan | string | The plan name. |
previousStatus | string, optional | The status before the change, when one was known. |
previousMrrCents | number, optional | Monthly revenue before the change, in cents. |
backfilled | boolean, optional | True when the event was recorded while importing history (an import, a first sync). It shows on the timeline and never starts a flow. |
{
"subscriptionId": "sub_1Q2w3E",
"customerId": "cus_9Kp2Lm",
"status": "trialing",
"mrrCents": 0,
"currency": "usd",
"plan": "Pro"
}trial.endingTrial ending soon
Recorded by: Nothing yet: a flow listening for it never starts.
Starts a flow: Not yet: nothing records it
No properties.
trial.endedTrial ended
Recorded by: The Stripe integration.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
subscriptionId | string | The Stripe subscription id. |
customerId | string | The Stripe customer id. |
status | string | The subscription status after the change, as Stripe names it. |
mrrCents | number | Monthly revenue after the change, in cents. |
currency | string | Three-letter currency code, lowercase. |
plan | string | The plan name. |
previousStatus | string, optional | The status before the change, when one was known. |
previousMrrCents | number, optional | Monthly revenue before the change, in cents. |
backfilled | boolean, optional | True when the event was recorded while importing history (an import, a first sync). It shows on the timeline and never starts a flow. |
{
"subscriptionId": "sub_1Q2w3E",
"customerId": "cus_9Kp2Lm",
"status": "active",
"mrrCents": 2900,
"currency": "usd",
"plan": "Pro",
"previousStatus": "trialing",
"previousMrrCents": 0
}subscription.createdSubscribed
Recorded by: The Stripe integration.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
subscriptionId | string | The Stripe subscription id. |
customerId | string | The Stripe customer id. |
status | string | The subscription status after the change, as Stripe names it. |
mrrCents | number | Monthly revenue after the change, in cents. |
currency | string | Three-letter currency code, lowercase. |
plan | string | The plan name. |
previousStatus | string, optional | The status before the change, when one was known. |
previousMrrCents | number, optional | Monthly revenue before the change, in cents. |
backfilled | boolean, optional | True when the event was recorded while importing history (an import, a first sync). It shows on the timeline and never starts a flow. |
{
"subscriptionId": "sub_1Q2w3E",
"customerId": "cus_9Kp2Lm",
"status": "active",
"mrrCents": 2900,
"currency": "usd",
"plan": "Pro"
}subscription.updatedSubscription changed
Recorded by: The Stripe integration, when the status, price, quantity or revenue changes.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
subscriptionId | string | The Stripe subscription id. |
customerId | string | The Stripe customer id. |
status | string | The subscription status after the change, as Stripe names it. |
mrrCents | number | Monthly revenue after the change, in cents. |
currency | string | Three-letter currency code, lowercase. |
plan | string | The plan name. |
previousStatus | string, optional | The status before the change, when one was known. |
previousMrrCents | number, optional | Monthly revenue before the change, in cents. |
backfilled | boolean, optional | True when the event was recorded while importing history (an import, a first sync). It shows on the timeline and never starts a flow. |
{
"subscriptionId": "sub_1Q2w3E",
"customerId": "cus_9Kp2Lm",
"status": "active",
"mrrCents": 4900,
"currency": "usd",
"plan": "Team",
"previousStatus": "active",
"previousMrrCents": 2900
}subscription.canceledCancelled
Recorded by: The Stripe integration.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
subscriptionId | string | The Stripe subscription id. |
customerId | string | The Stripe customer id. |
status | string | The subscription status after the change, as Stripe names it. |
mrrCents | number | Monthly revenue after the change, in cents. |
currency | string | Three-letter currency code, lowercase. |
plan | string | The plan name. |
previousStatus | string, optional | The status before the change, when one was known. |
previousMrrCents | number, optional | Monthly revenue before the change, in cents. |
backfilled | boolean, optional | True when the event was recorded while importing history (an import, a first sync). It shows on the timeline and never starts a flow. |
{
"subscriptionId": "sub_1Q2w3E",
"customerId": "cus_9Kp2Lm",
"status": "canceled",
"mrrCents": 2900,
"currency": "usd",
"plan": "Pro",
"previousStatus": "active",
"previousMrrCents": 2900
}payment.succeededPaid
Recorded by: The Stripe integration, for each paid invoice or charge.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
invoiceId | string, optional | The Stripe invoice id, for a subscription payment. |
chargeId | string, optional | The Stripe charge id, for a payment found while importing history. |
amountCents | number | The amount paid, in cents. |
currency | string | Three-letter currency code, lowercase. |
attemptCount | number, optional | How many attempts the invoice took. |
refunded | boolean, optional | Whether the charge was refunded since. |
backfilled | boolean, optional | True when the event was recorded while importing history (an import, a first sync). It shows on the timeline and never starts a flow. |
{
"invoiceId": "in_1Q2w3E4r",
"amountCents": 2900,
"currency": "usd",
"attemptCount": 1
}payment.failedPayment failed
Recorded by: The Stripe integration, once per failed attempt.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
invoiceId | string | The Stripe invoice id. |
amountCents | number | The amount due, in cents. |
currency | string | Three-letter currency code, lowercase. |
attemptCount | number | Which attempt failed. |
{
"invoiceId": "in_1Q2w3E4r",
"amountCents": 2900,
"currency": "usd",
"attemptCount": 2
}Lists
list.addedAdded to a list
Recorded by: Fromly, when someone is put on a list: by the rule for new contacts, by hand, through the API or by a flow.
Starts a flow: Yes, through “When added to a list”
| Property | Type | Description |
|---|---|---|
listId | string | The list id. |
listName | string | The list name when it happened. |
by | string | What did it: `auto` (the workspace rule for new contacts), `manual` (the dashboard), `api` (the API, SDK or MCP), `automation` (a flow step), `import` (a CSV import). |
backfilled | boolean, optional | True when the event was recorded while importing history (an import, a first sync). It shows on the timeline and never starts a flow. |
{
"listId": "5d41402a-bc4b-4a76-b9c7-19a0e1b2c3d4",
"listName": "Newsletter",
"by": "auto"
}list.subscribedJoined a list
Recorded by: Fromly, when someone puts themselves back on a list from the unsubscribe page.
Starts a flow: Yes, through “When added to a list”
| Property | Type | Description |
|---|---|---|
listId | string | The list id. |
listName | string | The list name when it happened. |
broadcastId | string, optional | The broadcast whose link they used. |
{
"listId": "5d41402a-bc4b-4a76-b9c7-19a0e1b2c3d4",
"listName": "Newsletter"
}list.unsubscribedLeft a list
Recorded by: Fromly, when someone leaves a list from the unsubscribe page or a mail client's unsubscribe button.
Starts a flow: Yes, as an event trigger
| Property | Type | Description |
|---|---|---|
listId | string | The list id. |
listName | string | The list name when it happened. |
broadcastId | string, optional | The broadcast whose link they used. |
{
"listId": "5d41402a-bc4b-4a76-b9c7-19a0e1b2c3d4",
"listName": "Newsletter",
"broadcastId": "2b1c9d4e-7a3f-4c1e-9b8d-5f6a7e8c9d0a"
}list.removedRemoved from a list
Recorded by: Fromly, when someone is taken off a list by hand, through the API or by a flow.
Starts a flow: Never: history only
| Property | Type | Description |
|---|---|---|
listId | string | The list id. |
listName | string | The list name when it happened. |
by | string | What did it: `auto` (the workspace rule for new contacts), `manual` (the dashboard), `api` (the API, SDK or MCP), `automation` (a flow step), `import` (a CSV import). |
{
"listId": "5d41402a-bc4b-4a76-b9c7-19a0e1b2c3d4",
"listName": "Newsletter",
"by": "manual"
}