Some DBMS require you to name all derived tables. Your query (I removed the unnessesary derived table T):
select * 
from (select a.* from sessions as a)
join (select b.customer_id, min(b.timestamp)
           , b.marketing_source as first_touch_source
           , b.marketing_medium as first_touch_medium 
      from sessions as b
      group by b.customer_id
     )
  on a.customer_id = b=customer_id
can be changed to:
select * 
from (select a.* from sessions as a) AS c
join (select b.customer_id, min(b.timestamp)
           , b.marketing_source as first_touch_source
           , b.marketing_medium as first_touch_medium 
      from sessions as b
      group by b.customer_id
     ) AS d
  on c.customer_id = d.customer_id
To avoid confusion, you should choose another alias at the outer level, despite that the inner alias is not visible there.
Side note: The derived table d may or may not be valid SQL. It is not allowed in SQL92, but it is allowed in SQL99 if marketing_* is functionally dependent of customer_id.
You can further simplify it as:
select * 
from sessions AS c
join (select b.customer_id, min(b.timestamp) as ts
           , b.marketing_source as first_touch_source
           , b.marketing_medium as first_touch_medium 
      from sessions as b
      group by b.customer_id
     ) AS d
  on c.customer_id = d.customer_id
I assume you meant to also join with c.timestamp =  d.ts. If that is the case and you are on a recent version of MySQL (8+) you can use a window function instead of a self join
select customer_id, ...
from (
    select b.customer_id
         , b.marketing_source
         , b.marketing_medium
         , row_number() over (partition by customer_id
                              order by b.timestamp) as rn 
    from sessions as b
) as T
where rn = 1