There is much field to cover between immutable types and exposing all the internals of your class through public setters.
A typical construct used for mutating immutable types is having methods akin to setters that return a copy of the object with updated values of certain fields - that is instead of having this:
public void setName (string value) {
this.name = value;
}
you have something like this:
public Customer withName (string value) {
Customer cust = this.Clone();
cust.name = value;
return cust;
}
That way you can have an immutable object that you can essentially update. Languages like F# have syntax sugar for similar operations on records, but in less functional OOP languages this seems unnatural. But anyway, this is probably not what you want to do.
What makes immutable state appealing is the fact that it makes lot of things easier to reason about, since the state won't change through a side effect in a piece of unrelated code - less places for things to go wrong. But it's just a means to the same end OOP languages encourage encapsulation for.
Instead of relying on setters, keep the updating logic inside the class. Same effect - less buttons to push and levers to pull to put your object in some unexpected and undesired state. Keep a well-defined interface for the consumers of your object to use and there will be no need for generic getters and setters.
It's more work than having your domain objects as simple Data Transfer Objects and it may sometimes be not worth it, so it's a judgement call. But if you're doing anything remotely complex with your object (and seeing you're looking at 30+ fields, you likely are), it might be well worth the effort.